Commit 9e614918 authored by Reilly Grant's avatar Reilly Grant Committed by Commit Bot

[serial] Add getSignals() and setSignals() methods

This change adds getSignals() and setSignals() methods to the SerialPort
interface which call the existing GetControlSignals() and
SetControlSignals() Mojo methods.

Bug: 884928
Change-Id: I5aace2e5fbd5ccad0c5b88a588ddd3de3f1a30d7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1747979
Commit-Queue: Reilly Grant <reillyg@chromium.org>
Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Reviewed-by: default avatarOvidio de Jesús Ruiz-Henríquez <odejesush@chromium.org>
Cr-Commit-Position: refs/heads/master@{#687380}
parent 90db5e66
...@@ -880,7 +880,9 @@ modules_dictionary_idl_files = ...@@ -880,7 +880,9 @@ modules_dictionary_idl_files =
if (!is_android) { if (!is_android) {
modules_dictionary_idl_files += modules_dictionary_idl_files +=
get_path_info([ get_path_info([
"serial/serial_input_signals.idl",
"serial/serial_options.idl", "serial/serial_options.idl",
"serial/serial_output_signals.idl",
"serial/serial_port_request_options.idl", "serial/serial_port_request_options.idl",
], ],
"abspath") "abspath")
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// https://wicg.github.io/serial
dictionary SerialInputSignals {
// DCD (Data Carrier Detect) or RLSD (Receive Line Signal Detect)
required boolean dcd;
// CTS (Clear to Send)
required boolean cts;
// RI (Ring Indicator)
required boolean ri;
// DSR (Data Set Ready)
required boolean dsr;
};
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// https://wicg.github.io/serial
dictionary SerialOutputSignals {
// DTR (Data Terminal Ready)
boolean dtr;
// RTS (Request to Send)
boolean rts;
};
...@@ -9,7 +9,9 @@ ...@@ -9,7 +9,9 @@
#include "third_party/blink/renderer/core/streams/readable_stream.h" #include "third_party/blink/renderer/core/streams/readable_stream.h"
#include "third_party/blink/renderer/core/streams/writable_stream.h" #include "third_party/blink/renderer/core/streams/writable_stream.h"
#include "third_party/blink/renderer/modules/serial/serial.h" #include "third_party/blink/renderer/modules/serial/serial.h"
#include "third_party/blink/renderer/modules/serial/serial_input_signals.h"
#include "third_party/blink/renderer/modules/serial/serial_options.h" #include "third_party/blink/renderer/modules/serial/serial_options.h"
#include "third_party/blink/renderer/modules/serial/serial_output_signals.h"
#include "third_party/blink/renderer/modules/serial/serial_port_underlying_sink.h" #include "third_party/blink/renderer/modules/serial/serial_port_underlying_sink.h"
#include "third_party/blink/renderer/modules/serial/serial_port_underlying_source.h" #include "third_party/blink/renderer/modules/serial/serial_port_underlying_source.h"
#include "third_party/blink/renderer/platform/bindings/v8_throw_exception.h" #include "third_party/blink/renderer/platform/bindings/v8_throw_exception.h"
...@@ -25,6 +27,7 @@ const char kResourcesExhaustedReadBuffer[] = ...@@ -25,6 +27,7 @@ const char kResourcesExhaustedReadBuffer[] =
"Resources exhausted allocating read buffer."; "Resources exhausted allocating read buffer.";
const char kResourcesExhaustedWriteBuffer[] = const char kResourcesExhaustedWriteBuffer[] =
"Resources exhausted allocation write buffer."; "Resources exhausted allocation write buffer.";
const char kPortClosed[] = "The port is closed.";
const char kOpenError[] = "Failed to open serial port."; const char kOpenError[] = "Failed to open serial port.";
const char kDeviceLostError[] = "The device has been lost."; const char kDeviceLostError[] = "The device has been lost.";
const char kSystemError[] = "An unknown system error has occurred."; const char kSystemError[] = "An unknown system error has occurred.";
...@@ -245,6 +248,48 @@ WritableStream* SerialPort::writable(ScriptState* script_state, ...@@ -245,6 +248,48 @@ WritableStream* SerialPort::writable(ScriptState* script_state,
return writable_; return writable_;
} }
ScriptPromise SerialPort::getSignals(ScriptState* script_state) {
if (!port_) {
return ScriptPromise::RejectWithDOMException(
script_state, MakeGarbageCollected<DOMException>(
DOMExceptionCode::kInvalidStateError, kPortClosed));
}
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
signal_resolvers_.insert(resolver);
port_->GetControlSignals(WTF::Bind(&SerialPort::OnGetSignals,
WrapPersistent(this),
WrapPersistent(resolver)));
return resolver->Promise();
}
ScriptPromise SerialPort::setSignals(ScriptState* script_state,
const SerialOutputSignals* signals) {
if (!port_) {
return ScriptPromise::RejectWithDOMException(
script_state, MakeGarbageCollected<DOMException>(
DOMExceptionCode::kInvalidStateError, kPortClosed));
}
auto mojo_signals = device::mojom::blink::SerialHostControlSignals::New();
if (signals->hasDtr()) {
mojo_signals->has_dtr = true;
mojo_signals->dtr = signals->dtr();
}
if (signals->hasRts()) {
mojo_signals->has_rts = true;
mojo_signals->rts = signals->rts();
}
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
signal_resolvers_.insert(resolver);
port_->SetControlSignals(
std::move(mojo_signals),
WTF::Bind(&SerialPort::OnSetSignals, WrapPersistent(this),
WrapPersistent(resolver)));
return resolver->Promise();
}
void SerialPort::close() { void SerialPort::close() {
if (underlying_source_) { if (underlying_source_) {
// The ReadableStream will report "done" when the data pipe is closed. // The ReadableStream will report "done" when the data pipe is closed.
...@@ -288,6 +333,7 @@ void SerialPort::Trace(Visitor* visitor) { ...@@ -288,6 +333,7 @@ void SerialPort::Trace(Visitor* visitor) {
visitor->Trace(writable_); visitor->Trace(writable_);
visitor->Trace(underlying_sink_); visitor->Trace(underlying_sink_);
visitor->Trace(open_resolver_); visitor->Trace(open_resolver_);
visitor->Trace(signal_resolvers_);
ScriptWrappable::Trace(visitor); ScriptWrappable::Trace(visitor);
} }
...@@ -328,21 +374,35 @@ bool SerialPort::CreateDataPipe(mojo::ScopedDataPipeProducerHandle* producer, ...@@ -328,21 +374,35 @@ bool SerialPort::CreateDataPipe(mojo::ScopedDataPipeProducerHandle* producer,
void SerialPort::OnConnectionError() { void SerialPort::OnConnectionError() {
port_.reset(); port_.reset();
if (open_resolver_) { if (client_binding_.is_bound())
open_resolver_->Reject(MakeGarbageCollected<DOMException>( client_binding_.Unbind();
// Move fields since rejecting a Promise can execute script.
ScriptPromiseResolver* open_resolver = open_resolver_;
open_resolver_ = nullptr;
HeapHashSet<Member<ScriptPromiseResolver>> signal_resolvers;
signal_resolvers_.swap(signal_resolvers);
SerialPortUnderlyingSource* underlying_source = underlying_source_;
underlying_source_ = nullptr;
SerialPortUnderlyingSink* underlying_sink = underlying_sink_;
underlying_sink_ = nullptr;
if (open_resolver) {
open_resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNetworkError, kOpenError)); DOMExceptionCode::kNetworkError, kOpenError));
open_resolver_ = nullptr;
} }
if (underlying_source_) { for (ScriptPromiseResolver* resolver : signal_resolvers) {
underlying_source_->SignalErrorOnClose( resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNetworkError, kDeviceLostError));
}
if (underlying_source) {
underlying_source->SignalErrorOnClose(
DOMExceptionFromReceiveError(SerialReceiveError::DISCONNECTED)); DOMExceptionFromReceiveError(SerialReceiveError::DISCONNECTED));
} }
if (underlying_sink_) { if (underlying_sink) {
underlying_sink_->SignalErrorOnClose( underlying_sink->SignalErrorOnClose(
DOMExceptionFromSendError(SerialSendError::DISCONNECTED)); DOMExceptionFromSendError(SerialSendError::DISCONNECTED));
} }
if (client_binding_.is_bound())
client_binding_.Unbind();
} }
void SerialPort::OnOpen( void SerialPort::OnOpen(
...@@ -355,9 +415,10 @@ void SerialPort::OnOpen( ...@@ -355,9 +415,10 @@ void SerialPort::OnOpen(
return; return;
if (!success) { if (!success) {
open_resolver_->Reject(MakeGarbageCollected<DOMException>( ScriptPromiseResolver* resolver = open_resolver_;
DOMExceptionCode::kNetworkError, kOpenError));
open_resolver_ = nullptr; open_resolver_ = nullptr;
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNetworkError, kOpenError));
return; return;
} }
...@@ -391,4 +452,37 @@ void SerialPort::InitializeWritableStream( ...@@ -391,4 +452,37 @@ void SerialPort::InitializeWritableStream(
script_state, underlying_sink_, 0); script_state, underlying_sink_, 0);
} }
void SerialPort::OnGetSignals(
ScriptPromiseResolver* resolver,
device::mojom::blink::SerialPortControlSignalsPtr mojo_signals) {
DCHECK(signal_resolvers_.Contains(resolver));
signal_resolvers_.erase(resolver);
if (!mojo_signals) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNetworkError, "Failed to get control signals."));
return;
}
auto* signals = MakeGarbageCollected<SerialInputSignals>();
signals->setDcd(mojo_signals->dcd);
signals->setCts(mojo_signals->cts);
signals->setRi(mojo_signals->ri);
signals->setDsr(mojo_signals->dsr);
resolver->Resolve(signals);
}
void SerialPort::OnSetSignals(ScriptPromiseResolver* resolver, bool success) {
DCHECK(signal_resolvers_.Contains(resolver));
signal_resolvers_.erase(resolver);
if (!success) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNetworkError, "Failed to set control signals."));
return;
}
resolver->Resolve();
}
} // namespace blink } // namespace blink
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h" #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/platform/bindings/script_wrappable.h" #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
#include "third_party/blink/renderer/platform/heap/handle.h" #include "third_party/blink/renderer/platform/heap/handle.h"
#include "third_party/blink/renderer/platform/heap/heap_allocator.h"
namespace base { namespace base {
class UnguessableToken; class UnguessableToken;
...@@ -23,6 +24,7 @@ class ScriptPromiseResolver; ...@@ -23,6 +24,7 @@ class ScriptPromiseResolver;
class ScriptState; class ScriptState;
class Serial; class Serial;
class SerialOptions; class SerialOptions;
class SerialOutputSignals;
class SerialPortUnderlyingSink; class SerialPortUnderlyingSink;
class SerialPortUnderlyingSource; class SerialPortUnderlyingSource;
class WritableStream; class WritableStream;
...@@ -40,6 +42,8 @@ class SerialPort final : public ScriptWrappable, ...@@ -40,6 +42,8 @@ class SerialPort final : public ScriptWrappable,
ScriptPromise open(ScriptState*, const SerialOptions* options); ScriptPromise open(ScriptState*, const SerialOptions* options);
ReadableStream* readable(ScriptState*, ExceptionState&); ReadableStream* readable(ScriptState*, ExceptionState&);
WritableStream* writable(ScriptState*, ExceptionState&); WritableStream* writable(ScriptState*, ExceptionState&);
ScriptPromise getSignals(ScriptState*);
ScriptPromise setSignals(ScriptState*, const SerialOutputSignals*);
void close(); void close();
const base::UnguessableToken& token() const { return info_->token; } const base::UnguessableToken& token() const { return info_->token; }
...@@ -67,6 +71,9 @@ class SerialPort final : public ScriptWrappable, ...@@ -67,6 +71,9 @@ class SerialPort final : public ScriptWrappable,
mojo::ScopedDataPipeConsumerHandle); mojo::ScopedDataPipeConsumerHandle);
void InitializeWritableStream(ScriptState*, void InitializeWritableStream(ScriptState*,
mojo::ScopedDataPipeProducerHandle); mojo::ScopedDataPipeProducerHandle);
void OnGetSignals(ScriptPromiseResolver*,
device::mojom::blink::SerialPortControlSignalsPtr);
void OnSetSignals(ScriptPromiseResolver*, bool success);
mojom::blink::SerialPortInfoPtr info_; mojom::blink::SerialPortInfoPtr info_;
Member<Serial> parent_; Member<Serial> parent_;
...@@ -82,6 +89,9 @@ class SerialPort final : public ScriptWrappable, ...@@ -82,6 +89,9 @@ class SerialPort final : public ScriptWrappable,
// Resolver for the Promise returned by open(). // Resolver for the Promise returned by open().
Member<ScriptPromiseResolver> open_resolver_; Member<ScriptPromiseResolver> open_resolver_;
// Resolvers for the Promises returned by getSignals() and setSignals() to
// reject them on Mojo connection failure.
HeapHashSet<Member<ScriptPromiseResolver>> signal_resolvers_;
}; };
} // namespace blink } // namespace blink
......
...@@ -15,7 +15,10 @@ ...@@ -15,7 +15,10 @@
[CallWith=ScriptState, MeasureAs=SerialPortOpen] [CallWith=ScriptState, MeasureAs=SerialPortOpen]
Promise<void> open(SerialOptions options); Promise<void> open(SerialOptions options);
[CallWith=ScriptState]
Promise<SerialInputSignals> getSignals();
[CallWith=ScriptState]
Promise<void> setSignals(SerialOutputSignals signals);
[MeasureAs=SerialPortClose] [MeasureAs=SerialPortClose]
void close(); void close();
}; };
...@@ -129,13 +129,18 @@ class DataPipeSink { ...@@ -129,13 +129,18 @@ class DataPipeSink {
// Implementation of blink.mojom.SerialPort. // Implementation of blink.mojom.SerialPort.
class FakeSerialPort { class FakeSerialPort {
constructor() {} constructor() {
this.inputSignals_ = { dcd: false, cts: false, ri: false, dsr: false };
this.outputSignals_ = { dtr: false, rts: false };
}
bind(request) { bind(request) {
assert_equals(this.binding, undefined, 'Port is still open'); assert_equals(this.binding, undefined, 'Port is still open');
this.binding = new mojo.Binding(device.mojom.SerialPort, this.binding = new mojo.Binding(device.mojom.SerialPort,
this, request); this, request);
this.binding.setConnectionErrorHandler(() => { this.binding.setConnectionErrorHandler(() => {
// OS typically clears DTR on close.
this.outputSignals_.dtr = false;
this.writable_.getWriter().close(); this.writable_.getWriter().close();
this.binding = undefined; this.binding = undefined;
}); });
...@@ -160,6 +165,14 @@ class FakeSerialPort { ...@@ -160,6 +165,14 @@ class FakeSerialPort {
this.client_.onReadError(device.mojom.SerialReceiveError.PARITY_ERROR); this.client_.onReadError(device.mojom.SerialReceiveError.PARITY_ERROR);
} }
simulateInputSignals(signals) {
this.inputSignals_ = signals;
}
get outputSignals() {
return this.outputSignals_;
}
waitForErrorCleared() { waitForErrorCleared() {
if (this.writable_) if (this.writable_)
return Promise.resolve(); return Promise.resolve();
...@@ -178,6 +191,8 @@ class FakeSerialPort { ...@@ -178,6 +191,8 @@ class FakeSerialPort {
this.client_ = client; this.client_ = client;
this.readable_ = new ReadableStream(new DataPipeSource(in_stream)); this.readable_ = new ReadableStream(new DataPipeSource(in_stream));
this.writable_ = new WritableStream(new DataPipeSink(out_stream)); this.writable_ = new WritableStream(new DataPipeSink(out_stream));
// OS typically sets DTR on open.
this.outputSignals_.dtr = true;
return { success: true }; return { success: true };
} }
...@@ -194,11 +209,17 @@ class FakeSerialPort { ...@@ -194,11 +209,17 @@ class FakeSerialPort {
} }
async getControlSignals() { async getControlSignals() {
return { signals: { dcd: false, cts: false, ri: false, dsr: false } }; return { signals: this.inputSignals_ };
} }
async setControlSignals(signals) { async setControlSignals(signals) {
return { success: false }; if (signals.hasDtr) {
this.outputSignals_.dtr = signals.dtr;
}
if (signals.hasRts) {
this.outputSignals_.rts = signals.rts;
}
return { success: true };
} }
async configurePort(options) { async configurePort(options) {
......
<!DOCTYPE html>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
<script src="file:///gen/mojo/public/mojom/base/unguessable_token.mojom.js"></script>
<script src="file:///gen/third_party/blink/public/mojom/serial/serial.mojom.js"></script>
<script src="resources/serial-test-utils.js"></script>
<script>
serial_test(async (t, fake) => {
const { port, fakePort } = await getFakeSerialPort(fake);
await promise_rejects(t, 'InvalidStateError', port.getSignals());
}, 'getSignals() rejects if the port is not open');
serial_test(async (t, fake) => {
const { port, fakePort } = await getFakeSerialPort(fake);
await port.open({ baudrate: 9600 });
let expectedSignals = { dcd: false, cts: false, ri: false, dsr: false };
fakePort.simulateInputSignals(expectedSignals);
let signals = await port.getSignals();
assert_object_equals(signals, expectedSignals);
expectedSignals.dcd = true;
fakePort.simulateInputSignals(expectedSignals);
signals = await port.getSignals();
assert_object_equals(signals, expectedSignals, 'DCD set');
expectedSignals.cts = true;
fakePort.simulateInputSignals(expectedSignals);
signals = await port.getSignals();
assert_object_equals(signals, expectedSignals, 'CTS set');
expectedSignals.ri = true;
fakePort.simulateInputSignals(expectedSignals);
signals = await port.getSignals();
assert_object_equals(signals, expectedSignals, 'RI set');
expectedSignals.dsr = true;
fakePort.simulateInputSignals(expectedSignals);
signals = await port.getSignals();
assert_object_equals(signals, expectedSignals, 'DSR set');
}, 'getSignals() returns the current state of input control signals');
</script>
<!DOCTYPE html>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
<script src="file:///gen/mojo/public/mojom/base/unguessable_token.mojom.js"></script>
<script src="file:///gen/third_party/blink/public/mojom/serial/serial.mojom.js"></script>
<script src="resources/serial-test-utils.js"></script>
<script>
serial_test(async (t, fake) => {
const { port, fakePort } = await getFakeSerialPort(fake);
await promise_rejects(t, 'InvalidStateError', port.setSignals({}));
}, 'setSignals() rejects if the port is not open');
serial_test(async (t, fake) => {
const { port, fakePort } = await getFakeSerialPort(fake);
await port.open({ baudrate: 9600 });
let expectedSignals = { dtr: true, rts: false };
assert_object_equals(fakePort.outputSignals, expectedSignals, "initial");
await port.setSignals({});
assert_object_equals(fakePort.outputSignals, expectedSignals, "no-op");
await port.setSignals({ dtr: false });
expectedSignals.dtr = false;
assert_object_equals(fakePort.outputSignals, expectedSignals, "clear DTR");
await port.setSignals({ rts: true });
expectedSignals.rts = true;
assert_object_equals(fakePort.outputSignals, expectedSignals, "set RTS");
await port.setSignals({ dtr: true, rts: false });
expectedSignals.dtr = true;
expectedSignals.rts = false;
assert_object_equals(fakePort.outputSignals, expectedSignals, "invert");
}, 'setSignals() modifies the state of the port');
</script>
...@@ -1204,7 +1204,9 @@ Starting worker: resources/global-interface-listing-worker.js ...@@ -1204,7 +1204,9 @@ Starting worker: resources/global-interface-listing-worker.js
[Worker] getter writable [Worker] getter writable
[Worker] method close [Worker] method close
[Worker] method constructor [Worker] method constructor
[Worker] method getSignals
[Worker] method open [Worker] method open
[Worker] method setSignals
[Worker] interface ServiceWorkerRegistration : EventTarget [Worker] interface ServiceWorkerRegistration : EventTarget
[Worker] attribute @@toStringTag [Worker] attribute @@toStringTag
[Worker] getter active [Worker] getter active
......
...@@ -7545,7 +7545,9 @@ interface SerialPort ...@@ -7545,7 +7545,9 @@ interface SerialPort
getter writable getter writable
method close method close
method constructor method constructor
method getSignals
method open method open
method setSignals
interface ServiceWorker : EventTarget interface ServiceWorker : EventTarget
attribute @@toStringTag attribute @@toStringTag
getter onerror getter onerror
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment