| // Copyright 2018 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. |
| |
| #include "third_party/blink/renderer/modules/serial/serial_port.h" |
| |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_function.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_serial_input_signals.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_serial_options.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_serial_output_signals.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_serial_port_info.h" |
| #include "third_party/blink/renderer/core/dom/dom_exception.h" |
| #include "third_party/blink/renderer/core/dom/events/event.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/modules/event_target_modules_names.h" |
| #include "third_party/blink/renderer/modules/serial/serial.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/platform/bindings/v8_throw_exception.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| using device::mojom::SerialReceiveError; |
| using device::mojom::SerialSendError; |
| |
| const char kResourcesExhaustedReadBuffer[] = |
| "Resources exhausted allocating read buffer."; |
| const char kResourcesExhaustedWriteBuffer[] = |
| "Resources exhausted allocation write buffer."; |
| const char kNoSignals[] = |
| "Signals dictionary must contain at least one member."; |
| const char kPortClosed[] = "The port is closed."; |
| const char kOpenError[] = "Failed to open serial port."; |
| const char kDeviceLostError[] = "The device has been lost."; |
| const char kSystemError[] = "An unknown system error has occurred."; |
| const int kMaxBufferSize = 16 * 1024 * 1024; /* 16 MiB */ |
| |
| bool SendErrorIsFatal(SerialSendError error) { |
| switch (error) { |
| case SerialSendError::NONE: |
| NOTREACHED(); |
| return false; |
| case SerialSendError::SYSTEM_ERROR: |
| return false; |
| case SerialSendError::DISCONNECTED: |
| return true; |
| } |
| } |
| |
| DOMException* DOMExceptionFromSendError(SerialSendError error) { |
| switch (error) { |
| case SerialSendError::NONE: |
| NOTREACHED(); |
| return nullptr; |
| case SerialSendError::DISCONNECTED: |
| return MakeGarbageCollected<DOMException>(DOMExceptionCode::kNetworkError, |
| kDeviceLostError); |
| case SerialSendError::SYSTEM_ERROR: |
| return MakeGarbageCollected<DOMException>(DOMExceptionCode::kUnknownError, |
| kSystemError); |
| } |
| } |
| |
| bool ReceiveErrorIsFatal(SerialReceiveError error) { |
| switch (error) { |
| case SerialReceiveError::NONE: |
| NOTREACHED(); |
| return false; |
| case SerialReceiveError::BREAK: |
| case SerialReceiveError::FRAME_ERROR: |
| case SerialReceiveError::OVERRUN: |
| case SerialReceiveError::BUFFER_OVERFLOW: |
| case SerialReceiveError::PARITY_ERROR: |
| case SerialReceiveError::SYSTEM_ERROR: |
| return false; |
| case SerialReceiveError::DISCONNECTED: |
| case SerialReceiveError::DEVICE_LOST: |
| return true; |
| } |
| } |
| |
| DOMException* DOMExceptionFromReceiveError(SerialReceiveError error) { |
| switch (error) { |
| case SerialReceiveError::NONE: |
| NOTREACHED(); |
| return nullptr; |
| case SerialReceiveError::DISCONNECTED: |
| case SerialReceiveError::DEVICE_LOST: |
| return MakeGarbageCollected<DOMException>(DOMExceptionCode::kNetworkError, |
| kDeviceLostError); |
| case SerialReceiveError::BREAK: |
| return MakeGarbageCollected<DOMException>(DOMExceptionCode::kBreakError); |
| case SerialReceiveError::FRAME_ERROR: |
| return MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kFramingError); |
| case SerialReceiveError::OVERRUN: |
| case SerialReceiveError::BUFFER_OVERFLOW: |
| return MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kBufferOverrunError); |
| case SerialReceiveError::PARITY_ERROR: |
| return MakeGarbageCollected<DOMException>(DOMExceptionCode::kParityError); |
| case SerialReceiveError::SYSTEM_ERROR: |
| return MakeGarbageCollected<DOMException>(DOMExceptionCode::kUnknownError, |
| kSystemError); |
| } |
| } |
| |
| // A ScriptFunction that calls ContinueClose() on the provided SerialPort. |
| class ContinueCloseFunction : public ScriptFunction { |
| public: |
| static v8::Local<v8::Function> Create(ScriptState* script_state, |
| SerialPort* port) { |
| auto* self = |
| MakeGarbageCollected<ContinueCloseFunction>(script_state, port); |
| return self->BindToV8Function(); |
| } |
| |
| ContinueCloseFunction(ScriptState* script_state, SerialPort* port) |
| : ScriptFunction(script_state), port_(port) {} |
| |
| ScriptValue Call(ScriptValue) override { |
| return port_->ContinueClose(GetScriptState()).AsScriptValue(); |
| } |
| |
| void Trace(Visitor* visitor) const override { |
| visitor->Trace(port_); |
| ScriptFunction::Trace(visitor); |
| } |
| |
| private: |
| Member<SerialPort> port_; |
| }; |
| |
| // A ScriptFunction that calls AbortClose() on the provided SerialPort. |
| class AbortCloseFunction : public ScriptFunction { |
| public: |
| static v8::Local<v8::Function> Create(ScriptState* script_state, |
| SerialPort* port) { |
| auto* self = MakeGarbageCollected<AbortCloseFunction>(script_state, port); |
| return self->BindToV8Function(); |
| } |
| |
| AbortCloseFunction(ScriptState* script_state, SerialPort* port) |
| : ScriptFunction(script_state), port_(port) {} |
| |
| ScriptValue Call(ScriptValue) override { |
| port_->AbortClose(); |
| return ScriptValue(); |
| } |
| |
| void Trace(Visitor* visitor) const override { |
| visitor->Trace(port_); |
| ScriptFunction::Trace(visitor); |
| } |
| |
| private: |
| Member<SerialPort> port_; |
| }; |
| } // namespace |
| |
| SerialPort::SerialPort(Serial* parent, mojom::blink::SerialPortInfoPtr info) |
| : info_(std::move(info)), |
| parent_(parent), |
| port_(parent->GetExecutionContext()), |
| client_receiver_(this, parent->GetExecutionContext()) {} |
| |
| SerialPort::~SerialPort() = default; |
| |
| SerialPortInfo* SerialPort::getInfo() { |
| auto* info = MakeGarbageCollected<SerialPortInfo>(); |
| if (info_->has_usb_vendor_id) |
| info->setUsbVendorId(info_->usb_vendor_id); |
| if (info_->has_usb_product_id) |
| info->setUsbProductId(info_->usb_product_id); |
| return info; |
| } |
| |
| ScriptPromise SerialPort::open(ScriptState* script_state, |
| const SerialOptions* options, |
| ExceptionState& exception_state) { |
| if (open_resolver_) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidStateError, |
| "A call to open() is already in progress."); |
| return ScriptPromise(); |
| } |
| |
| if (port_.is_bound()) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, |
| "The port is already open."); |
| return ScriptPromise(); |
| } |
| |
| auto mojo_options = device::mojom::blink::SerialConnectionOptions::New(); |
| |
| if (options->baudRate() == 0) { |
| exception_state.ThrowTypeError( |
| "Requested baud rate must be greater than zero."); |
| return ScriptPromise(); |
| } |
| mojo_options->bitrate = options->baudRate(); |
| |
| switch (options->dataBits()) { |
| case 7: |
| mojo_options->data_bits = device::mojom::blink::SerialDataBits::SEVEN; |
| break; |
| case 8: |
| mojo_options->data_bits = device::mojom::blink::SerialDataBits::EIGHT; |
| break; |
| default: |
| exception_state.ThrowTypeError( |
| "Requested number of data bits must be 7 or 8."); |
| return ScriptPromise(); |
| } |
| |
| if (options->parity() == "none") { |
| mojo_options->parity_bit = device::mojom::blink::SerialParityBit::NO_PARITY; |
| } else if (options->parity() == "even") { |
| mojo_options->parity_bit = device::mojom::blink::SerialParityBit::EVEN; |
| } else if (options->parity() == "odd") { |
| mojo_options->parity_bit = device::mojom::blink::SerialParityBit::ODD; |
| } else { |
| NOTREACHED(); |
| } |
| |
| switch (options->stopBits()) { |
| case 1: |
| mojo_options->stop_bits = device::mojom::blink::SerialStopBits::ONE; |
| break; |
| case 2: |
| mojo_options->stop_bits = device::mojom::blink::SerialStopBits::TWO; |
| break; |
| default: |
| exception_state.ThrowTypeError( |
| "Requested number of stop bits must be 1 or 2."); |
| return ScriptPromise(); |
| } |
| |
| if (options->bufferSize() == 0) { |
| exception_state.ThrowTypeError(String::Format( |
| "Requested buffer size (%d bytes) must be greater than zero.", |
| options->bufferSize())); |
| return ScriptPromise(); |
| } |
| |
| if (options->bufferSize() > kMaxBufferSize) { |
| exception_state.ThrowTypeError( |
| String::Format("Requested buffer size (%d bytes) is greater than " |
| "the maximum allowed (%d bytes).", |
| options->bufferSize(), kMaxBufferSize)); |
| return ScriptPromise(); |
| } |
| buffer_size_ = options->bufferSize(); |
| |
| mojo_options->has_cts_flow_control = true; |
| mojo_options->cts_flow_control = options->flowControl() == "hardware"; |
| |
| mojo::PendingRemote<device::mojom::blink::SerialPortClient> client; |
| open_resolver_ = MakeGarbageCollected<ScriptPromiseResolver>(script_state); |
| auto callback = WTF::Bind(&SerialPort::OnOpen, WrapPersistent(this), |
| client.InitWithNewPipeAndPassReceiver()); |
| |
| parent_->OpenPort(info_->token, std::move(mojo_options), std::move(client), |
| std::move(callback)); |
| |
| return open_resolver_->Promise(); |
| } |
| |
| ReadableStream* SerialPort::readable(ScriptState* script_state, |
| ExceptionState& exception_state) { |
| if (readable_) |
| return readable_; |
| |
| if (!port_.is_bound() || open_resolver_ || closing_ || read_fatal_) |
| return nullptr; |
| |
| mojo::ScopedDataPipeProducerHandle producer; |
| mojo::ScopedDataPipeConsumerHandle consumer; |
| if (!CreateDataPipe(&producer, &consumer)) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kQuotaExceededError, |
| kResourcesExhaustedReadBuffer); |
| return nullptr; |
| } |
| |
| port_->StartReading(std::move(producer)); |
| |
| DCHECK(!underlying_source_); |
| underlying_source_ = MakeGarbageCollected<SerialPortUnderlyingSource>( |
| script_state, this, std::move(consumer)); |
| // Ideally the stream would report the number of bytes that can be read from |
| // the underlying Mojo data pipe. As an approximation the high water mark is |
| // set to 0 so that data remains in the pipe rather than being queued in the |
| // stream and thus adding an extra layer of buffering. |
| readable_ = ReadableStream::CreateWithCountQueueingStrategy( |
| script_state, underlying_source_, /*high_water_mark=*/0); |
| return readable_; |
| } |
| |
| WritableStream* SerialPort::writable(ScriptState* script_state, |
| ExceptionState& exception_state) { |
| if (writable_) |
| return writable_; |
| |
| if (!port_.is_bound() || open_resolver_ || closing_ || write_fatal_) |
| return nullptr; |
| |
| mojo::ScopedDataPipeProducerHandle producer; |
| mojo::ScopedDataPipeConsumerHandle consumer; |
| if (!CreateDataPipe(&producer, &consumer)) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kQuotaExceededError, |
| kResourcesExhaustedWriteBuffer); |
| return nullptr; |
| } |
| |
| port_->StartWriting(std::move(consumer)); |
| |
| DCHECK(!underlying_sink_); |
| underlying_sink_ = |
| MakeGarbageCollected<SerialPortUnderlyingSink>(this, std::move(producer)); |
| // Ideally the stream would report the number of bytes that could be written |
| // to the underlying Mojo data pipe. As an approximation the high water mark |
| // is set to 1 so that the stream appears ready but producers observing |
| // backpressure won't queue additional chunks in the stream and thus add an |
| // extra layer of buffering. |
| writable_ = WritableStream::CreateWithCountQueueingStrategy( |
| script_state, underlying_sink_, /*high_water_mark=*/1); |
| return writable_; |
| } |
| |
| ScriptPromise SerialPort::getSignals(ScriptState* script_state, |
| ExceptionState& exception_state) { |
| if (!port_.is_bound()) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, |
| kPortClosed); |
| return ScriptPromise(); |
| } |
| |
| 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, |
| ExceptionState& exception_state) { |
| if (!port_.is_bound()) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, |
| kPortClosed); |
| return ScriptPromise(); |
| } |
| |
| if (!signals->hasDataTerminalReady() && !signals->hasRequestToSend() && |
| !signals->hasBrk()) { |
| exception_state.ThrowTypeError(kNoSignals); |
| return ScriptPromise(); |
| } |
| |
| auto mojo_signals = device::mojom::blink::SerialHostControlSignals::New(); |
| if (signals->hasDataTerminalReady()) { |
| mojo_signals->has_dtr = true; |
| mojo_signals->dtr = signals->dataTerminalReady(); |
| } |
| if (signals->hasRequestToSend()) { |
| mojo_signals->has_rts = true; |
| mojo_signals->rts = signals->requestToSend(); |
| } |
| if (signals->hasBrk()) { |
| mojo_signals->has_brk = true; |
| mojo_signals->brk = signals->brk(); |
| } |
| |
| 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(); |
| } |
| |
| ScriptPromise SerialPort::close(ScriptState* script_state, |
| ExceptionState& exception_state) { |
| if (!port_.is_bound()) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, |
| "The port is already closed."); |
| return ScriptPromise(); |
| } |
| |
| if (closing_) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidStateError, |
| "A call to close() is already in progress."); |
| return ScriptPromise(); |
| } |
| |
| closing_ = true; |
| |
| HeapVector<ScriptPromise> promises; |
| if (readable_) { |
| promises.push_back(readable_->cancel(script_state, exception_state)); |
| if (exception_state.HadException()) { |
| closing_ = false; |
| return ScriptPromise(); |
| } |
| } |
| if (writable_) { |
| auto* reason = MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kInvalidStateError, kPortClosed); |
| promises.push_back(writable_->abort(script_state, |
| ScriptValue::From(script_state, reason), |
| exception_state)); |
| if (exception_state.HadException()) { |
| closing_ = false; |
| return ScriptPromise(); |
| } |
| } |
| |
| return ScriptPromise::All(script_state, promises) |
| .Then(ContinueCloseFunction::Create(script_state, this), |
| AbortCloseFunction::Create(script_state, this)); |
| } |
| |
| ScriptPromise SerialPort::ContinueClose(ScriptState* script_state) { |
| DCHECK(closing_); |
| DCHECK(!close_resolver_); |
| |
| if (!port_.is_bound()) |
| return ScriptPromise::CastUndefined(script_state); |
| |
| close_resolver_ = MakeGarbageCollected<ScriptPromiseResolver>(script_state); |
| |
| // readable.cancel() and writable.abort() can resolve before |readable_| or |
| // |writable_| are set to null if the streams were already erroring. Wait |
| // until UnderlyingSourceClosed() and UnderlyingSinkClosed() have been called |
| // before continuing. |
| if (!readable_ && !writable_) { |
| StreamsClosed(); |
| } |
| |
| return close_resolver_->Promise(); |
| } |
| |
| void SerialPort::AbortClose() { |
| DCHECK(closing_); |
| closing_ = false; |
| } |
| |
| void SerialPort::StreamsClosed() { |
| DCHECK(!readable_); |
| DCHECK(!writable_); |
| port_->Close(WTF::Bind(&SerialPort::OnClose, WrapPersistent(this))); |
| } |
| |
| void SerialPort::Flush( |
| device::mojom::blink::SerialPortFlushMode mode, |
| device::mojom::blink::SerialPort::FlushCallback callback) { |
| DCHECK(port_.is_bound()); |
| port_->Flush(mode, std::move(callback)); |
| } |
| |
| void SerialPort::Drain( |
| device::mojom::blink::SerialPort::DrainCallback callback) { |
| DCHECK(port_.is_bound()); |
| port_->Drain(std::move(callback)); |
| } |
| |
| void SerialPort::UnderlyingSourceClosed() { |
| DCHECK(readable_); |
| readable_ = nullptr; |
| underlying_source_ = nullptr; |
| |
| if (close_resolver_ && !writable_) { |
| StreamsClosed(); |
| } |
| } |
| |
| void SerialPort::UnderlyingSinkClosed() { |
| DCHECK(writable_); |
| writable_ = nullptr; |
| underlying_sink_ = nullptr; |
| |
| if (close_resolver_ && !readable_) { |
| StreamsClosed(); |
| } |
| } |
| |
| void SerialPort::ContextDestroyed() { |
| // Release connection-related resources as quickly as possible. |
| port_.reset(); |
| } |
| |
| void SerialPort::Trace(Visitor* visitor) const { |
| visitor->Trace(parent_); |
| visitor->Trace(port_); |
| visitor->Trace(client_receiver_); |
| visitor->Trace(readable_); |
| visitor->Trace(underlying_source_); |
| visitor->Trace(writable_); |
| visitor->Trace(underlying_sink_); |
| visitor->Trace(open_resolver_); |
| visitor->Trace(signal_resolvers_); |
| visitor->Trace(close_resolver_); |
| ScriptWrappable::Trace(visitor); |
| } |
| |
| bool SerialPort::HasPendingActivity() const { |
| // There is no need to check if the execution context has been destroyed, this |
| // is handled by the common tracing logic. |
| // |
| // This object should be considered active as long as it is open so that any |
| // chain of streams originating from this port are not closed prematurely. |
| return port_.is_bound(); |
| } |
| |
| ExecutionContext* SerialPort::GetExecutionContext() const { |
| return parent_->GetExecutionContext(); |
| } |
| |
| const AtomicString& SerialPort::InterfaceName() const { |
| return event_target_names::kSerialPort; |
| } |
| |
| DispatchEventResult SerialPort::DispatchEventInternal(Event& event) { |
| event.SetTarget(this); |
| |
| // Events fired on a SerialPort instance bubble to the parent Serial instance. |
| event.SetEventPhase(Event::kCapturingPhase); |
| event.SetCurrentTarget(parent_); |
| parent_->FireEventListeners(event); |
| if (event.PropagationStopped()) |
| goto doneDispatching; |
| |
| event.SetEventPhase(Event::kAtTarget); |
| event.SetCurrentTarget(this); |
| FireEventListeners(event); |
| if (event.PropagationStopped() || !event.bubbles()) |
| goto doneDispatching; |
| |
| event.SetEventPhase(Event::kBubblingPhase); |
| event.SetCurrentTarget(parent_); |
| parent_->FireEventListeners(event); |
| |
| doneDispatching: |
| event.SetCurrentTarget(nullptr); |
| event.SetEventPhase(Event::kNone); |
| return EventTarget::GetDispatchEventResult(event); |
| } |
| |
| void SerialPort::OnReadError(device::mojom::blink::SerialReceiveError error) { |
| if (ReceiveErrorIsFatal(error)) |
| read_fatal_ = true; |
| if (underlying_source_) |
| underlying_source_->SignalErrorOnClose(DOMExceptionFromReceiveError(error)); |
| } |
| |
| void SerialPort::OnSendError(device::mojom::blink::SerialSendError error) { |
| if (SendErrorIsFatal(error)) |
| write_fatal_ = true; |
| if (underlying_sink_) |
| underlying_sink_->SignalErrorOnClose(DOMExceptionFromSendError(error)); |
| } |
| |
| bool SerialPort::CreateDataPipe(mojo::ScopedDataPipeProducerHandle* producer, |
| mojo::ScopedDataPipeConsumerHandle* consumer) { |
| MojoCreateDataPipeOptions options; |
| options.struct_size = sizeof(MojoCreateDataPipeOptions); |
| options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE; |
| options.element_num_bytes = 1; |
| options.capacity_num_bytes = buffer_size_; |
| |
| MojoResult result = mojo::CreateDataPipe(&options, *producer, *consumer); |
| if (result == MOJO_RESULT_OK) |
| return true; |
| |
| DCHECK_EQ(result, MOJO_RESULT_RESOURCE_EXHAUSTED); |
| return false; |
| } |
| |
| void SerialPort::OnConnectionError() { |
| closing_ = false; |
| read_fatal_ = false; |
| write_fatal_ = false; |
| port_.reset(); |
| client_receiver_.reset(); |
| |
| // 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; |
| ScriptPromiseResolver* close_resolver = close_resolver_; |
| close_resolver_ = nullptr; |
| |
| if (open_resolver) { |
| open_resolver->Reject(MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kNetworkError, kOpenError)); |
| } |
| for (ScriptPromiseResolver* resolver : signal_resolvers) { |
| resolver->Reject(MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kNetworkError, kDeviceLostError)); |
| } |
| if (underlying_source) { |
| underlying_source->SignalErrorOnClose( |
| DOMExceptionFromReceiveError(SerialReceiveError::DISCONNECTED)); |
| } |
| if (underlying_sink) { |
| underlying_sink->SignalErrorOnClose( |
| DOMExceptionFromSendError(SerialSendError::DISCONNECTED)); |
| } |
| if (close_resolver) |
| close_resolver->Resolve(); |
| } |
| |
| void SerialPort::OnOpen( |
| mojo::PendingReceiver<device::mojom::blink::SerialPortClient> |
| client_receiver, |
| mojo::PendingRemote<device::mojom::blink::SerialPort> port) { |
| ScriptState* script_state = open_resolver_->GetScriptState(); |
| if (!script_state->ContextIsValid()) |
| return; |
| |
| if (!port) { |
| ScriptPromiseResolver* resolver = open_resolver_; |
| open_resolver_ = nullptr; |
| resolver->Reject(MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kNetworkError, kOpenError)); |
| return; |
| } |
| |
| port_.Bind(std::move(port), |
| GetExecutionContext()->GetTaskRunner(TaskType::kMiscPlatformAPI)); |
| port_.set_disconnect_handler( |
| WTF::Bind(&SerialPort::OnConnectionError, WrapWeakPersistent(this))); |
| client_receiver_.Bind( |
| std::move(client_receiver), |
| GetExecutionContext()->GetTaskRunner(TaskType::kMiscPlatformAPI)); |
| |
| open_resolver_->Resolve(); |
| open_resolver_ = nullptr; |
| } |
| |
| 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->setDataCarrierDetect(mojo_signals->dcd); |
| signals->setClearToSend(mojo_signals->cts); |
| signals->setRingIndicator(mojo_signals->ri); |
| signals->setDataSetReady(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(); |
| } |
| |
| void SerialPort::OnClose() { |
| DCHECK(close_resolver_); |
| closing_ = false; |
| read_fatal_ = false; |
| write_fatal_ = false; |
| port_.reset(); |
| client_receiver_.reset(); |
| |
| ScriptPromiseResolver* close_resolver = close_resolver_; |
| close_resolver_ = nullptr; |
| close_resolver->Resolve(); |
| } |
| |
| } // namespace blink |