Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/dom/webserial/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 34 kB image not shown  

Quelle  SerialPort.cpp

  Sprache: C
 

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "mozilla/dom/SerialPort.h"

#include "SerialLogging.h"
#include "SerialPortPumps.h"
#include "SerialPortStreamAlgorithms.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/dom/DOMException.h"
#include "mozilla/dom/DOMExceptionBinding.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/Promise-inl.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/ReadableStream.h"
#include "mozilla/dom/Serial.h"
#include "mozilla/dom/SerialPortBinding.h"
#include "mozilla/dom/SerialPortChild.h"
#include "mozilla/dom/SerialPortIPCTypes.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/dom/UnderlyingSourceCallbackHelpers.h"
#include "mozilla/dom/WorkerCommon.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WritableStream.h"
#include "mozilla/ipc/DataPipe.h"

namespace mozilla::dom {

NS_IMPL_CYCLE_COLLECTION_CLASS(SerialPort)

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SerialPort,
                                                  DOMEventTargetHelper)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSerial)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReadable)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWritable)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChild)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOpenPromise)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mClosePromise)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SerialPort,
                                                DOMEventTargetHelper)
  tmp->Shutdown();
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSerial)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadable)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWritable)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mChild)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mOpenPromise)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mClosePromise)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_ADDREF_INHERITED(SerialPort, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(SerialPort, DOMEventTargetHelper)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SerialPort)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)

SerialPort::SerialPort(const IPCSerialPortInfo& aInfo, Serial* aSerial)
    : DOMEventTargetHelper(aSerial->GetRelevantGlobal()),
      mSerial(aSerial),
      mInfo(aInfo) {
  nsPIDOMWindowInner* window = aSerial->GetOwnerWindow();
  if (window) {
    if (Document* doc = window->GetExtantDoc()) {
      // Disallow putting this page in the bfcache to ensure that
      // when we navigate away the OS resources associated with this
      // SerialPort get properly cleaned up.
      doc->DisallowBFCaching();
    }
  }
  MOZ_LOG(
      gWebSerialLog, LogLevel::Info,
      ("SerialPort[%p] created for port '%s' (%s)", this,
       NS_ConvertUTF16toUTF8(mInfo.id()).get(), window ? "window" : "worker"));
}

void SerialPort::UpdateWorkerRef() {
  if (NS_IsMainThread()) {
    return;
  }

  bool needsRef = false;
  if (!mHasShutdown && mState != State::Forgetting &&
      mState != State::Forgotten) {
    EventListenerManager* elm = GetExistingListenerManager();
    bool hasListeners = elm && (elm->HasListenersFor(u"connect"_ns) ||
                                elm->HasListenersFor(u"disconnect"_ns));
    // The port effectively holds OS resources while opened, and while closing
    // is in progress. Both states require the worker to remain alive so that
    // close IPC callbacks can complete.
    bool isActive = (mState == State::Opened) || (mState == State::Closing);
    needsRef = isActive || hasListeners;
  }

  if (needsRef && !mWorkerRef) {
    WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
    if (workerPrivate) {
      RefPtr<SerialPort> self = this;
      mWorkerRef = StrongWorkerRef::Create(workerPrivate, "SerialPort",
                                           [self]() { self->Shutdown(); });
    }
  } else if (!needsRef && mWorkerRef) {
    mWorkerRef = nullptr;
  }
}

void SerialPort::EventListenerAdded(nsAtom* aType) {
  DOMEventTargetHelper::EventListenerAdded(aType);
  UpdateWorkerRef();
}

void SerialPort::EventListenerRemoved(nsAtom* aType) {
  DOMEventTargetHelper::EventListenerRemoved(aType);
  UpdateWorkerRef();
}

SerialPort::~SerialPort() {
  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("SerialPort[%p] destroyed for port '%s'", this,
           NS_ConvertUTF16toUTF8(mInfo.id()).get()));
  MOZ_ASSERT(mHasShutdown);
}

void SerialPort::Shutdown() {
  if (mHasShutdown) {
    return;
  }
  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("SerialPort[%p] shutting down port '%s'", this,
           NS_ConvertUTF16toUTF8(mInfo.id()).get()));
  mHasShutdown = true;

  if (mState == State::Opened || mState == State::Closing) {
    mState = State::Closed;
    // Don't have to wait for this
    RefPtr<Promise> ignoredPromise = CloseStreams(StreamCloseMode::Forced);
  }

  if (mOpenPromise) {
    mOpenPromise->MaybeRejectWithAbortError("Port was shut down");
    mOpenPromise = nullptr;
  }
  if (mClosePromise) {
    mClosePromise->MaybeRejectWithNetworkError("Port was shut down");
    mClosePromise = nullptr;
  }

  if (mChild) {
    mChild->Shutdown();
    mChild = nullptr;
  }

  mWorkerRef = nullptr;
}

void SerialPort::DisconnectFromOwner() {
  Shutdown();
  DOMEventTargetHelper::DisconnectFromOwner();
}

JSObject* SerialPort::WrapObject(JSContext* aCx,
                                 JS::Handle<JSObject*> aGivenProto) {
  return SerialPort_Binding::Wrap(aCx, this, aGivenProto);
}

void SerialPort::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
  aVisitor.mCanHandle = true;
  aVisitor.SetParentTarget(mSerial, false);
}

already_AddRefed<Promise> SerialPort::Open(const SerialOptions& aOptions,
                                           ErrorResult& aRv) {
  nsIGlobalObject* global = GetRelevantGlobal();
  if (!global) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  // https://wicg.github.io/serial/#dom-serialport-open
  // Step 1: Let promise be a new promise.
  RefPtr<Promise> promise = Promise::Create(global, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("SerialPort[%p]::Open called for port '%s' with baudRate=%u, "
           "dataBits=%u, stopBits=%u, parity=%u, bufferSize=%u, flowControl=%u",
           this, NS_ConvertUTF16toUTF8(mInfo.id()).get(), aOptions.mBaudRate,
           aOptions.mDataBits, aOptions.mStopBits,
           static_cast<uint8_t>(aOptions.mParity), aOptions.mBufferSize,
           static_cast<uint8_t>(aOptions.mFlowControl)));

  // Step 2: If this.[[state]] is not "closed", reject promise with an
  // InvalidStateError DOMException and return promise.
  switch (mState) {
    case State::Closed:
      break;
    case State::Opening:
      promise->MaybeRejectWithInvalidStateError("Port is being opened");
      return promise.forget();
    case State::Opened:
      promise->MaybeRejectWithInvalidStateError("Port is already open");
      return promise.forget();
    case State::Closing:
      promise->MaybeRejectWithInvalidStateError("Port is being closed");
      return promise.forget();
    case State::Forgetting:
    case State::Forgotten:
      promise->MaybeRejectWithInvalidStateError("Port has been forgotten");
      return promise.forget();
  }
  MOZ_ASSERT(mState == State::Closed);

  // Step 3: If options["baudRate"] is 0, reject promise with a
  // TypeError and return promise.
  if (aOptions.mBaudRate == 0) {
    promise->MaybeRejectWithTypeError("Invalid baud rate");
    return promise.forget();
  }

  // Step 4: If options["dataBits"] is not 7 or 8, reject promise with a
  // TypeError and return promise.
  if (aOptions.mDataBits != 7 && aOptions.mDataBits != 8) {
    promise->MaybeRejectWithTypeError("Data bits must be 7 or 8");
    return promise.forget();
  }

  // Step 5: If options["stopBits"] is not 1 or 2, reject promise with a
  // TypeError and return promise.
  if (aOptions.mStopBits != 1 && aOptions.mStopBits != 2) {
    promise->MaybeRejectWithTypeError("Stop bits must be 1 or 2");
    return promise.forget();
  }

  // Step 6: If options["bufferSize"] is 0, reject promise with a TypeError
  // and return promise.
  if (aOptions.mBufferSize == 0) {
    promise->MaybeRejectWithTypeError("Invalid buffer size");
    return promise.forget();
  }

  // Step 7: Optionally, if options["bufferSize"] is larger than the
  // implementation is able to support, reject promise with a TypeError.
  if (aOptions.mBufferSize > kMaxSerialBufferSize) {
    promise->MaybeRejectWithTypeError(
        "Requested buffer size exceeds the maximum supported size");
    return promise.forget();
  }

  if (!mChild) {
    promise->MaybeRejectWithNotSupportedError("Port actor not available");
    return promise.forget();
  }

  // Step 8: Set this.[[state]] to "opening".
  mState = State::Opening;
  mOpenPromise = promise;

  IPCSerialOptions options{aOptions.mBaudRate,   aOptions.mDataBits,
                           aOptions.mStopBits,   aOptions.mParity,
                           aOptions.mBufferSize, aOptions.mFlowControl};

  // Step 9 (in parallel): Request the operating system to open the serial port
  // with the given connection parameters. The PSerialPort actor was created
  // eagerly when the port was granted, so we can send Open directly.
  RefPtr<SerialPortChild> child = mChild;
  RefPtr<SerialPort> self = this;

  child->SendOpen(options)->Then(
      GetCurrentSerialEventTarget(), __func__,
      [self, bufferSize = options.bufferSize()](nsresult aResult) {
        if (self->mHasShutdown) {
          return;
        }
        if (NS_SUCCEEDED(aResult)) {
          MOZ_LOG(gWebSerialLog, LogLevel::Info,
                  ("SerialPort[%p] opened successfully for port '%s'",
                   self.get(), NS_ConvertUTF16toUTF8(self->mInfo.id()).get()));
          if (self->mState == State::Opening) {
            // Step 9.3: Set this.[[state]] to "opened".
            self->mState = State::Opened;
            self->UpdateWorkerRef();
            self->NotifySharingStateChanged(true);
            // Step 9.4: Set this.[[bufferSize]].
            self->mBufferSize = bufferSize;
            self->mPipeCapacity = std::max(bufferSize, kMinSerialPortPumpSize);
            // Streams are created lazily by GetReadable()/GetWritable().
            // Step 9.5: Resolve promise with undefined.
            if (self->mOpenPromise) {
              self->mOpenPromise->MaybeResolveWithUndefined();
            }
          } else if (self->mOpenPromise) {
            // Forget()/MarkForgotten() moved us out of Opening while the IPC
            // was in flight. The promise is rejected rather than resolved
            // because the port is no longer usable.
            self->mOpenPromise->MaybeRejectWithAbortError(
                "Port was forgotten while opening");
          }
          self->mOpenPromise = nullptr;
        } else {
          // Step 9.2: Reject promise with a NetworkError.
          MOZ_LOG(gWebSerialLog, LogLevel::Error,
                  ("SerialPort[%p] failed to open port '%s': error 0x%08x",
                   self.get(), NS_ConvertUTF16toUTF8(self->mInfo.id()).get(),
                   static_cast<uint32_t>(aResult)));
          if (self->mState == State::Opening) {
            self->mState = State::Closed;
          }
          if (self->mOpenPromise) {
            self->mOpenPromise->MaybeRejectWithNetworkError(
                "Failed to open port");
            self->mOpenPromise = nullptr;
          }
        }
      },
      [self](mozilla::ipc::ResponseRejectReason aReason) {
        if (self->mHasShutdown) {
          return;
        }
        MOZ_LOG(gWebSerialLog, LogLevel::Error,
                ("SerialPort[%p] failed to open port '%s': IPC error "
                 "(reason: %d)",
                 self.get(), NS_ConvertUTF16toUTF8(self->mInfo.id()).get(),
                 static_cast<int>(aReason)));
        if (self->mState == State::Opening) {
          self->mState = State::Closed;
        }
        if (self->mOpenPromise) {
          self->mOpenPromise->MaybeRejectWithNetworkError(
              "Failed to open port: IPC communication error");
          self->mOpenPromise = nullptr;
        }
      });

  // Step 10: Return promise.
  return promise.forget();
}

already_AddRefed<Promise> SerialPort::SetSignals(
    const SerialOutputSignals& aSignals, ErrorResult& aRv) {
  nsIGlobalObject* global = GetRelevantGlobal();
  if (!global) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  RefPtr<Promise> promise = Promise::Create(global, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("SerialPort[%p]::SetSignals called for port '%s'", this,
           NS_ConvertUTF16toUTF8(mInfo.id()).get()));

  if (mState == State::Forgetting || mState == State::Forgotten) {
    promise->MaybeRejectWithInvalidStateError("Port has been forgotten");
    return promise.forget();
  }

  if (mState != State::Opened) {
    promise->MaybeRejectWithInvalidStateError("Port is not open");
    return promise.forget();
  }

  if (!aSignals.mDataTerminalReady.WasPassed() &&
      !aSignals.mRequestToSend.WasPassed() && !aSignals.mBreak.WasPassed()) {
    promise->MaybeRejectWithTypeError(
        "At least one signal must be specified in setSignals()");
    return promise.forget();
  }

  if (!mChild) {
    promise->MaybeRejectWithInvalidStateError("Port not initialized");
    return promise.forget();
  }

  IPCSerialOutputSignals signals{
      aSignals.mDataTerminalReady.WasPassed()
          ? Some(aSignals.mDataTerminalReady.Value())
          : Nothing(),
      aSignals.mRequestToSend.WasPassed()
          ? Some(aSignals.mRequestToSend.Value())
          : Nothing(),
      aSignals.mBreak.WasPassed() ? Some(aSignals.mBreak.Value()) : Nothing()};

  RefPtr<SerialPortChild> child = mChild;
  nsISerialEventTarget* actorTarget = child->GetActorEventTarget();

  if (!actorTarget) {
    promise->MaybeRejectWithNetworkError("Actor not available");
    return promise.forget();
  }

  InvokeAsync(actorTarget, "SerialPort::SendSetSignals",
              [child = std::move(child), signals = std::move(signals)]() {
                return child->SendSetSignals(signals);
              })
      ->Then(
          GetCurrentSerialEventTarget(), __func__,
          [promise](nsresult aResult) {
            if (NS_SUCCEEDED(aResult)) {
              promise->MaybeResolveWithUndefined();
            } else {
              promise->MaybeRejectWithNetworkError(
                  nsPrintfCString("Failed to set signals: 0x%08x",
                                  static_cast<uint32_t>(aResult)));
            }
          },
          [promise](mozilla::ipc::ResponseRejectReason) {
            promise->MaybeRejectWithNetworkError(
                "Failed to set signals: IPC communication error");
          });

  return promise.forget();
}

already_AddRefed<Promise> SerialPort::GetSignals(ErrorResult& aRv) {
  nsIGlobalObject* global = GetRelevantGlobal();
  if (!global) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  RefPtr<Promise> promise = Promise::Create(global, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("SerialPort[%p]::GetSignals called for port '%s'", this,
           NS_ConvertUTF16toUTF8(mInfo.id()).get()));

  if (mState == State::Forgetting || mState == State::Forgotten) {
    promise->MaybeRejectWithInvalidStateError("Port has been forgotten");
    return promise.forget();
  }

  if (mState != State::Opened) {
    promise->MaybeRejectWithInvalidStateError("Port is not open");
    return promise.forget();
  }

  if (!mChild) {
    promise->MaybeRejectWithInvalidStateError("Port not initialized");
    return promise.forget();
  }

  RefPtr<SerialPortChild> child = mChild;
  nsISerialEventTarget* actorTarget = child->GetActorEventTarget();

  if (!actorTarget) {
    promise->MaybeRejectWithNetworkError("Actor not available");
    return promise.forget();
  }

  InvokeAsync(actorTarget, "SerialPort::SendGetSignals",
              [child]() { return child->SendGetSignals(); })
      ->Then(
          GetCurrentSerialEventTarget(), __func__,
          [promise](
              const std::tuple<nsresult, IPCSerialInputSignals>& aResult) {
            nsresult rv = std::get<0>(aResult);
            if (NS_SUCCEEDED(rv)) {
              const IPCSerialInputSignals& ipcSignals = std::get<1>(aResult);
              SerialInputSignals result;
              result.mDataCarrierDetect = ipcSignals.dataCarrierDetect();
              result.mClearToSend = ipcSignals.clearToSend();
              result.mRingIndicator = ipcSignals.ringIndicator();
              result.mDataSetReady = ipcSignals.dataSetReady();
              promise->MaybeResolve(result);
            } else {
              promise->MaybeRejectWithNetworkError(nsPrintfCString(
                  "Failed to get signals: 0x%08x", static_cast<uint32_t>(rv)));
            }
          },
          [promise](mozilla::ipc::ResponseRejectReason) {
            promise->MaybeRejectWithNetworkError(
                "Failed to get signals: IPC communication error");
          });

  return promise.forget();
}

// https://wicg.github.io/serial/#dom-serialport-close
already_AddRefed<Promise> SerialPort::Close(ErrorResult& aRv) {
  nsIGlobalObject* global = GetRelevantGlobal();
  if (!global) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  // Step 1: Let promise be a new promise.
  RefPtr<Promise> promise = Promise::Create(global, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("SerialPort[%p]::Close called for port '%s'", this,
           NS_ConvertUTF16toUTF8(mInfo.id()).get()));

  // Step 2: If this.[[state]] is not "opened", reject promise with an
  // InvalidStateError DOMException and return promise.
  switch (mState) {
    case State::Opened:
      break;
    case State::Closing:
      promise->MaybeRejectWithInvalidStateError("Port is being closed");
      return promise.forget();
    case State::Forgetting:
    case State::Forgotten:
      promise->MaybeRejectWithInvalidStateError("Port has been forgotten");
      return promise.forget();
    case State::Closed:
    case State::Opening:
      promise->MaybeRejectWithInvalidStateError("Port is not open");
      return promise.forget();
  }
  MOZ_ASSERT(mState == State::Opened);

  // Steps 3-8: Cancel the readable stream (step 3), abort the writable
  // stream (step 4), and let combinedPromise be the result of getting a
  // promise to wait for all (steps 5-8). Our pendingClosePromise is
  // effectively resolved once both streams are nulled (step 6), so the
  // combined promise just waits for the cancel and abort to finish.
  RefPtr<Promise> combinedPromise = CloseStreams(StreamCloseMode::Graceful);
  if (!combinedPromise) {
    combinedPromise = Promise::CreateResolvedWithUndefined(global, aRv);
    if (NS_WARN_IF(aRv.Failed())) {
      return nullptr;
    }
  }

  // Step 9: Set this.[[state]] to "closing".
  mState = State::Closing;
  mClosePromise = promise;

  // Step 10: Upon fulfillment of combinedPromise, run the following steps
  // in parallel: request the operating system close the serial port.
  // Step 10 (rejection): Upon rejection of combinedPromise with reason r,
  // reject promise with r.
  combinedPromise->AddCallbacksWithCycleCollectedArgs(
      [](JSContext*, JS::Handle<JS::Value>, ErrorResult&, SerialPort* aSelf) {
        aSelf->CloseAfterStreamsClosed();
      },
      [](JSContext*, JS::Handle<JS::Value> aReason, ErrorResult&,
         SerialPort* aSelf) {
        if (aSelf->mHasShutdown) {
          return;
        }
        // Forget()/MarkForgotten() may have transitioned to a terminal state.
        if (aSelf->mState == State::Closing) {
          aSelf->mState = State::Closed;
        }
        aSelf->UpdateWorkerRef();
        aSelf->NotifySharingStateChanged(false);
        if (RefPtr<Promise> closePromise = aSelf->mClosePromise.forget()) {
          closePromise->MaybeReject(aReason);
        }
      },
      RefPtr(this));

  // Step 11: Return promise.
  return promise.forget();
}

already_AddRefed<Promise> SerialPort::Forget(ErrorResult& aRv) {
  nsIGlobalObject* global = GetRelevantGlobal();
  if (!global) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  RefPtr<Promise> promise = Promise::Create(global, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("SerialPort[%p]::Forget called for port '%s'", this,
           NS_ConvertUTF16toUTF8(mInfo.id()).get()));

  // Step 2.1: Set this.[[state]] to "forgetting".
  const bool wasActive =
      (mState == State::Opened) || (mState == State::Closing);
  mState = State::Forgetting;

  if (mSerial) {
    RefPtr<Serial> serial = mSerial;
    serial->ForgetPort(mInfo.id());
  }

  if (wasActive) {
    // Don't have to wait for this
    RefPtr<Promise> ignoredPromise = CloseStreams(StreamCloseMode::Graceful);
  }

  UpdateWorkerRef();
  NotifySharingStateChanged(false);

  if (mChild) {
    RefPtr<SerialPortChild> child = mChild;
    nsISerialEventTarget* actorTarget = child->GetActorEventTarget();

    if (!actorTarget) {
      // Step 2.3: Set this.[[state]] to "forgotten".
      mState = State::Forgotten;
      promise->MaybeResolveWithUndefined();
      return promise.forget();
    }

    RefPtr<SerialPort> self = this;
    InvokeAsync(actorTarget, "SerialPort::SendForget",
                [child = std::move(child)]() { return child->SendClose(); })
        ->Then(
            GetCurrentSerialEventTarget(), __func__,
            [promise, self](nsresult aResult) {
              // Step 2.3: Set this.[[state]] to "forgotten".
              self->mState = State::Forgotten;
              promise->MaybeResolveWithUndefined();
            },
            [promise, self](mozilla::ipc::ResponseRejectReason aReason) {
              self->mState = State::Forgotten;
              promise->MaybeResolveWithUndefined();
            });
  } else {
    mState = State::Forgotten;
    promise->MaybeResolveWithUndefined();
  }

  return promise.forget();
}

void SerialPort::MarkForgotten() {
  if (mState == State::Forgetting || mState == State::Forgotten) {
    return;
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("SerialPort[%p]::MarkForgotten for port '%s'", this,
           NS_ConvertUTF16toUTF8(mInfo.id()).get()));

  const bool wasActive =
      (mState == State::Opened) || (mState == State::Closing);
  mState = State::Forgotten;

  if (wasActive) {
    // Don't have to wait for this
    RefPtr<Promise> ignoredPromise = CloseStreams(StreamCloseMode::Graceful);
  }

  UpdateWorkerRef();
  NotifySharingStateChanged(false);
}

void SerialPort::GetInfo(SerialPortInfo& aRetVal, ErrorResult& aRv) {
  if (mInfo.usbVendorId().isSome()) {
    aRetVal.mUsbVendorId.Construct(mInfo.usbVendorId().value());
  }

  if (mInfo.usbProductId().isSome()) {
    aRetVal.mUsbProductId.Construct(mInfo.usbProductId().value());
  }

  if (mInfo.bluetoothServiceClassId().isSome()) {
    OwningStringOrUnsignedLong uuid;
    uuid.SetAsString() = mInfo.bluetoothServiceClassId().value();
    aRetVal.mBluetoothServiceClassId.Construct(uuid);
  }
}

ReadableStream* SerialPort::GetReadable() {
  if (mState != State::Opened) {
    return nullptr;
  }
  // Per spec, readable becomes null after reader.cancel(). Detect the
  // closed state and clear the reference so a fresh stream is created.
  if (mReadable && mReadable->State() == ReadableStream::ReaderState::Closed) {
    mReadable = nullptr;
  }
  if (!mReadable) {
    return CreateReadableStream();
  }
  return mReadable;
}

WritableStream* SerialPort::GetWritable() {
  if (mState != State::Opened) {
    return nullptr;
  }
  // Per spec, writable becomes null after writer.close() or writer.abort().
  // Detect any non-writable state and clear the reference so a fresh
  // stream is created.
  if (mWritable &&
      mWritable->State() != WritableStream::WriterState::Writable) {
    mWritable = nullptr;
  }
  if (!mWritable) {
    return CreateWritableStream();
  }
  return mWritable;
}

void SerialPort::NotifySharingStateChanged(bool aConnected) {
  if (!mChild) {
    return;
  }

  RefPtr<SerialPortChild> child = mChild;
  nsISerialEventTarget* actorTarget = child->GetActorEventTarget();
  if (actorTarget) {
    actorTarget->Dispatch(NS_NewRunnableFunction(
        "SerialPort::SendUpdateSharingState",
        [child, aConnected]() { child->SendUpdateSharingState(aConnected); }));
  }
}

void SerialPort::OnActorDestroyed() {
  if (mHasShutdown) {
    return;
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("SerialPort[%p]::OnActorDestroyed for port '%s'", this,
           NS_ConvertUTF16toUTF8(mInfo.id()).get()));

  // Clear the child reference first since the actor is already destroyed.
  // This prevents MarkForgotten/NotifySharingStateChanged from trying to
  // use the dead actor.
  mChild = nullptr;

  // Mark the port as forgotten (closes streams, updates worker ref).
  MarkForgotten();

  // Remove from Serial's port list so it no longer appears in getPorts().
  if (mSerial) {
    RefPtr<Serial> serial = mSerial;
    serial->ForgetPort(mInfo.id());
  }
}

void SerialPort::NotifyConnected() {
  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("SerialPort[%p] connected for port '%s'", this,
           NS_ConvertUTF16toUTF8(mInfo.id()).get()));

  mPhysicallyPresent = true;

  auto event = MakeRefPtr<Event>(this, nullptr, nullptr);
  event->InitEvent(u"connect"_ns, true, false);
  event->SetTrusted(true);
  DispatchTrustedEvent(event);
}

void SerialPort::NotifyDisconnected() {
  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("SerialPort[%p] disconnected for port '%s'", this,
           NS_ConvertUTF16toUTF8(mInfo.id()).get()));
  if (mState == State::Opened || mState == State::Closing) {
    mState = State::Closed;
  }
  mPhysicallyPresent = false;
  // Don't have to wait for this
  RefPtr<Promise> ignoredPromise = CloseStreams(StreamCloseMode::Graceful);
  UpdateWorkerRef();
  NotifySharingStateChanged(false);

  auto event = MakeRefPtr<Event>(this, nullptr, nullptr);
  event->InitEvent(u"disconnect"_ns, true, false);
  event->SetTrusted(true);
  DispatchTrustedEvent(event);
}

// Thin subclass to access the protected SetUpByteNative.
class SerialByteReadableStream final : public ReadableStream {
 public:
  explicit SerialByteReadableStream(nsIGlobalObject* aGlobal)
      : ReadableStream(aGlobal, HoldDropJSObjectsCaller::Implicit) {}

  void SetUp(JSContext* aCx, UnderlyingSourceAlgorithmsWrapper& aAlgorithms,
             Maybe<double> aHighWaterMark, ErrorResult& aRv) {
    SetUpByteNative(aCx, aAlgorithms, aHighWaterMark, aRv);
  }
};

ReadableStream* SerialPort::CreateReadableStream() {
  MOZ_ASSERT(mState == State::Opened);
  MOZ_ASSERT(!mReadable);

  // Create a DataPipe pair locally. The child keeps the receiver (for the
  // ReadableStream) and sends the sender to the parent (for the read pump).
  RefPtr<mozilla::ipc::DataPipeSender> sender;
  RefPtr<mozilla::ipc::DataPipeReceiver> receiver;
  nsresult rv = mozilla::ipc::NewDataPipe(mPipeCapacity, getter_AddRefs(sender),
                                          getter_AddRefs(receiver));
  if (NS_FAILED(rv)) {
    return nullptr;
  }

  // Send the sender endpoint to the parent so it can start the read pump.
  if (mChild) {
    RefPtr<SerialPortChild> child = mChild;
    nsISerialEventTarget* actorTarget = child->GetActorEventTarget();
    if (actorTarget) {
      actorTarget->Dispatch(NS_NewRunnableFunction(
          "SerialPort::AttachReadPipe", [child, sender = std::move(sender)]() {
            child->SendAttachReadPipe(sender);
          }));
    }
  }

  AutoJSAPI jsapi;
  if (!jsapi.Init(GetRelevantGlobal())) {
    return nullptr;
  }

  JSContext* cx = jsapi.cx();
  ErrorResult erv;

  nsCOMPtr<nsIAsyncInputStream> readInput = receiver.get();
  auto readableStream =
      MakeRefPtr<SerialByteReadableStream>(GetRelevantGlobal());
  RefPtr readAlgorithms =
      MakeRefPtr<SerialPortReadAlgorithms>(cx, readInput, readableStream, this);
  // Use a zero high water mark: the DataPipe itself provides buffering
  // (sized to mBufferSize), so the stream shouldn't eagerly pull before a
  // reader is acquired.
  readableStream->SetUp(cx, *readAlgorithms, Some(0.0), erv);
  if (erv.Failed()) {
    return nullptr;
  }
  mReadable = readableStream;

  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("SerialPort[%p]::CreateReadableStream created readable=%p", this,
           mReadable.get()));
  return mReadable;
}

WritableStream* SerialPort::CreateWritableStream() {
  MOZ_ASSERT(mState == State::Opened);
  MOZ_ASSERT(!mWritable);

  // Create a DataPipe pair locally. The child keeps the sender (for the
  // WritableStream) and sends the receiver to the parent (for the write pump).
  RefPtr<mozilla::ipc::DataPipeSender> sender;
  RefPtr<mozilla::ipc::DataPipeReceiver> receiver;
  nsresult rv = mozilla::ipc::NewDataPipe(mPipeCapacity, getter_AddRefs(sender),
                                          getter_AddRefs(receiver));
  if (NS_FAILED(rv)) {
    return nullptr;
  }

  // Send the receiver endpoint to the parent so it can start the write pump.
  if (mChild) {
    RefPtr<SerialPortChild> child = mChild;
    nsISerialEventTarget* actorTarget = child->GetActorEventTarget();
    if (actorTarget) {
      actorTarget->Dispatch(
          NS_NewRunnableFunction("SerialPort::AttachWritePipe",
                                 [child, receiver = std::move(receiver)]() {
                                   child->SendAttachWritePipe(receiver);
                                 }));
    }
  }

  AutoJSAPI jsapi;
  if (!jsapi.Init(GetRelevantGlobal())) {
    return nullptr;
  }

  JSContext* cx = jsapi.cx();
  ErrorResult erv;

  nsCOMPtr<nsIAsyncOutputStream> writeOutput = sender.get();
  RefPtr writeAlgorithms = MakeRefPtr<SerialPortWriteAlgorithms>(
      GetRelevantGlobal(), writeOutput, this);
  mWritable = WritableStream::CreateNative(
      cx, *GetRelevantGlobal(), *writeAlgorithms,
      Some(static_cast<double>(mBufferSize)), nullptr, erv);
  if (erv.Failed()) {
    return nullptr;
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("SerialPort[%p]::CreateWritableStream created writable=%p", this,
           mWritable.get()));
  return mWritable;
}

void SerialPort::CloseAfterStreamsClosed() {
  if (mHasShutdown) {
    return;
  }

  nsresult rv = NS_OK;
  // Every synchronous exit path marks the port as closed.
  auto markClosed = MakeScopeExit([&]() { SettleClosePromise(rv); });

  RefPtr<SerialPortChild> child = mChild;
  if (!child) {
    return;
  }

  nsISerialEventTarget* actorTarget = child->GetActorEventTarget();
  if (!actorTarget) {
    // Make SettleClosePromise reject the promise with an error.
    rv = NS_ERROR_FAILURE;
    return;
  }

  // Async path: SettleClosePromise handles cleanup in the callbacks.
  markClosed.release();

  RefPtr<SerialPort> self = this;
  nsCOMPtr<nsISerialEventTarget> owningThread = GetCurrentSerialEventTarget();
  InvokeAsync(actorTarget, "SerialPort::SendClose",
              [child]() { return child->SendClose(); })
      ->Then(
          owningThread, "SerialPort::Close::SendClose",
          [self](nsresult aResult) { self->SettleClosePromise(aResult); },
          [self](mozilla::ipc::ResponseRejectReason aReason) {
            self->SettleClosePromise(NS_ERROR_DOM_NETWORK_ERR);
          });
}

void SerialPort::SettleClosePromise(nsresult aResult) {
  if (mHasShutdown) {
    return;
  }
  // Set this.[[state]] to "closed", unless Forget()/MarkForgotten() raced
  // ahead and moved us into a terminal state.
  if (mState == State::Closing) {
    mState = State::Closed;
  }
  UpdateWorkerRef();
  NotifySharingStateChanged(false);
  if (RefPtr<Promise> closePromise = mClosePromise.forget()) {
    if (NS_SUCCEEDED(aResult)) {
      closePromise->MaybeResolveWithUndefined();
    } else {
      closePromise->MaybeRejectWithNetworkError(
          "Failed to close port: IPC communication error");
    }
  }
}

already_AddRefed<Promise> SerialPort::CloseStreams(StreamCloseMode aMode) {
  nsIGlobalObject* global = GetRelevantGlobal();
  if (!global) {
    return nullptr;
  }

  if (!mReadable && !mWritable) {
    return nullptr;
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("SerialPort[%p]::CloseStreams closing streams "
           "(readable=%p, writable=%p, mode=%s)",
           this, mReadable.get(), mWritable.get(),
           aMode == StreamCloseMode::Forced ? "forced" : "graceful"));

  AutoJSAPI jsapi;
  if (!jsapi.Init(global)) {
    return nullptr;
  }

  JSContext* cx = jsapi.cx();

  RefPtr<DOMException> exception =
      DOMException::Create(NS_ERROR_DOM_NETWORK_ERR, "Port has been closed"_ns);
  JS::Rooted<JS::Value> errorVal(cx);
  bool hasError = ToJSValue(cx, exception, &errorVal);

  // Error the streams instead of aborting, so we don't dispatchevents
  // or runauthor-visible algorithms. The OS resources are released by
  // mChild->Shutdown() in Shutdown().
  if (aMode == StreamCloseMode::Forced) {
    if (mReadable && hasError) {
      IgnoredErrorResult rv;
      RefPtr readable = mReadable;
      readable->ErrorNative(cx, errorVal, rv);
    }
    if (mWritable && hasError) {
      IgnoredErrorResult rv;
      RefPtr writable = mWritable;
      writable->ErrorNative(cx, errorVal, rv);
    }
    mReadable = nullptr;
    mWritable = nullptr;
    return nullptr;
  }

  // Cancel the readable and abort the writable, collecting their promises.
  nsTArray<RefPtr<Promise>> streamPromises;

  if (mReadable && hasError) {
    IgnoredErrorResult rv;
    RefPtr readable = mReadable;
    if (RefPtr<Promise> cancelPromise =
            readable->CancelNative(cx, errorVal, rv)) {
      streamPromises.AppendElement(std::move(cancelPromise));
    }
  }

  if (mWritable && hasError) {
    IgnoredErrorResult rv;
    RefPtr writable = mWritable;
    if (RefPtr<Promise> abortPromise =
            writable->AbortNative(cx, errorVal, rv)) {
      streamPromises.AppendElement(std::move(abortPromise));
    }
  }

  if (streamPromises.IsEmpty()) {
    mReadable = nullptr;
    mWritable = nullptr;
    return nullptr;
  }

  IgnoredErrorResult rv;
  RefPtr<Promise> combined = Promise::All(cx, streamPromises, rv);
  if (!combined) {
    mReadable = nullptr;
    mWritable = nullptr;
    return nullptr;
  }

  // Hold mReadable and mWritable until the cancel/abort promises settle.
  // Otherwise the streams may be cycle collected before finishing and those
  // promises may never resolve.
  combined->AddCallbacksWithCycleCollectedArgs(
      [](JSContext*, JS::Handle<JS::Value>, ErrorResult&, SerialPort* aSelf) {
        aSelf->mReadable = nullptr;
        aSelf->mWritable = nullptr;
      },
      [](JSContext*, JS::Handle<JS::Value>, ErrorResult&, SerialPort* aSelf) {
        aSelf->mReadable = nullptr;
        aSelf->mWritable = nullptr;
      },
      RefPtr(this));

  return combined.forget();
}

}  // namespace mozilla::dom

Messung V0.5 in Prozent
C=91 H=95 G=92

¤ Dauer der Verarbeitung: 0.3 Sekunden  (vorverarbeitet am  2026-08-25) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.