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 28 kB image not shown  

Quelle  Serial.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/Serial.h"

#include "Navigator.h"
#include "SerialLogging.h"
#include "mozilla/Preferences.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/FeaturePolicyUtils.h"
#include "mozilla/dom/PSerialPort.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/SerialBinding.h"
#include "mozilla/dom/SerialManagerChild.h"
#include "mozilla/dom/SerialPort.h"
#include "mozilla/dom/SerialPortChild.h"
#include "mozilla/dom/WindowContext.h"
#include "mozilla/dom/WindowGlobalChild.h"
#include "mozilla/dom/WorkerCommon.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerRef.h"
#include "nsContentUtils.h"
#include "nsPIDOMWindow.h"
#include "nsThreadUtils.h"

namespace mozilla::dom {

// Forward declaration for use in RequestPort below; the implementation lives
// further down with the other Bluetooth UUID helpers.
static bool ResolveBluetoothServiceUUID(const OwningStringOrUnsignedLong& aName,
                                        nsAutoString& aResult);

static Serial* FindWindowSerialForWorkerPrivate(WorkerPrivate* aWorkerPrivate) {
  AssertIsOnMainThread();
  MOZ_ASSERT(aWorkerPrivate);
  nsPIDOMWindowInner* inner = aWorkerPrivate->GetAncestorWindow();
  if (!inner) {
    return nullptr;
  }
  return inner->Navigator()->GetExistingSerial();
}

using TestingIpcPromise =
    MozPromise<nsresult, mozilla::ipc::ResponseRejectReason, true>;

// Runs aSendFn(child) on the main thread, where |child| is the
// SerialManagerChild owned by aMainThreadSerial.
template <typename SendFn>
static RefPtr<TestingIpcPromise> InvokeTestingSendOnMainThread(
    Serial* aMainThreadSerial, const SendFn&& aSendFn) {
  AssertIsOnMainThread();
  if (!aMainThreadSerial) {
    return TestingIpcPromise::CreateAndReject(
        mozilla::ipc::ResponseRejectReason::ChannelClosed, __func__);
  }
  SerialManagerChild* child = aMainThreadSerial->GetOrCreateManagerChild();
  if (!child) {
    return TestingIpcPromise::CreateAndReject(
        mozilla::ipc::ResponseRejectReason::ChannelClosed, __func__);
  }
  return aSendFn(child);
}

// Common implementation for the four test-only methods. SerialManagerChild
// is a main-thread-only IPC actor, so when called from a worker, the Send*
// call has to be bounced to the main thread (via the window's Serial),
// and the resulting JS Promise resolved back on the worker.
//
// aSendFn is a callable taking SerialManagerChild* and returning
// RefPtr<TestingIpcPromise> (i.e. the result of one of the Send* methods).
template <typename SendFn>
static already_AddRefed<Promise> RunTestingIpc(
    Serial* aSerial, ErrorResult& aRv, const nsLiteralCString& aIpcErrorMessage,
    SendFn&& aSendFn) {
  if (!StaticPrefs::dom_webserial_testing_enabled()) {
    aRv.ThrowNotSupportedError("Testing is not enabled");
    return nullptr;
  }

  nsIGlobalObject* global = aSerial->GetRelevantGlobal();
  if (!global) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

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

  if (NS_IsMainThread()) {
    InvokeTestingSendOnMainThread(aSerial, std::forward<SendFn>(aSendFn))
        ->Then(
            GetMainThreadSerialEventTarget(), __func__,
            [promise](nsresult) { promise->MaybeResolveWithUndefined(); },
            [promise, aIpcErrorMessage](mozilla::ipc::ResponseRejectReason) {
              promise->MaybeRejectWithAbortError(aIpcErrorMessage);
            });
    return promise.forget();
  }

  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
  if (!workerPrivate) {
    promise->MaybeRejectWithNotSupportedError("Worker context not available");
    return promise.forget();
  }

  RefPtr<StrongWorkerRef> strongRef =
      StrongWorkerRef::Create(workerPrivate, "Serial::RunTestingIpc");
  if (!strongRef) {
    promise->MaybeRejectWithAbortError("Worker is shutting down");
    return promise.forget();
  }
  auto tsRef = MakeRefPtr<ThreadSafeWorkerRef>(strongRef);

  InvokeAsync(GetMainThreadSerialEventTarget(), __func__,
              [tsRef, sendFn = std::forward<SendFn>(aSendFn)]() {
                Serial* windowSerial =
                    FindWindowSerialForWorkerPrivate(tsRef->Private());
                return InvokeTestingSendOnMainThread(windowSerial,
                                                     std::move(sendFn));
              })
      ->Then(
          GetCurrentSerialEventTarget(), __func__,
          [promise](nsresult) { promise->MaybeResolveWithUndefined(); },
          [promise, aIpcErrorMessage](mozilla::ipc::ResponseRejectReason) {
            promise->MaybeRejectWithAbortError(aIpcErrorMessage);
          });

  return promise.forget();
}

NS_IMPL_CYCLE_COLLECTION_WEAK_PTR_INHERITED(Serial, DOMEventTargetHelper,
                                            mPorts)

NS_IMPL_ADDREF_INHERITED(Serial, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(Serial, DOMEventTargetHelper)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Serial)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)

LazyLogModule gWebSerialLog("WebSerial");

Serial::Serial(nsPIDOMWindowInner* aWindow) : DOMEventTargetHelper(aWindow) {
  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("Serial[%p] created for window", this));
  AssertIsOnMainThread();
}

Serial::Serial(nsIGlobalObject* aGlobal) : DOMEventTargetHelper(aGlobal) {
  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("Serial[%p] created for global", this));
  MOZ_ASSERT(!NS_IsMainThread());
}

Serial::~Serial() {
  MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("Serial[%p] destroyed", this));
  MOZ_ASSERT(mHasShutdown);
}

void Serial::Shutdown() {
  if (mHasShutdown) {
    return;
  }
  MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("Serial[%p] shutting down", this));
  mHasShutdown = true;
  mManagerChild = nullptr;
  for (const auto& port : mPorts) {
    port->Shutdown();
  }
  mPorts.Clear();
}

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

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

SerialManagerChild* Serial::GetOrCreateManagerChild() {
  if (mManagerChild) {
    return mManagerChild;
  }

  AssertIsOnMainThread();

  nsPIDOMWindowInner* window = GetOwnerWindow();
  if (!window) {
    return nullptr;
  }

  WindowGlobalChild* wgc = window->GetWindowGlobalChild();
  if (!wgc) {
    return nullptr;
  }

  auto child = MakeRefPtr<SerialManagerChild>(this);
  if (!wgc->SendPSerialManagerConstructor(child)) {
    return nullptr;
  }

  mManagerChild = child;
  return mManagerChild;
}

// Returns whether the security check was passed. If this method returns
// false, the promise has been rejected.
static bool PortSecurityCheck(Promise& aPromise, nsIGlobalObject* aGlobal,
                              const nsCString& aFunctionName) {
  if (nsPIDOMWindowInner* window = aGlobal->GetAsInnerWindow()) {
    Document* doc = window->GetExtantDoc();
    if (!doc) {
      aPromise.MaybeRejectWithSecurityError(
          aFunctionName + "() is not allowed without a document"_ns);
      return false;
    }

    // web-platform-tests seem to indicate this is necessary, but the spec does
    // not. spec issue: https://github.com/WICG/serial/issues/223
    if (doc->NodePrincipal()->GetIsNullPrincipal()) {
      aPromise.MaybeRejectWithSecurityError(
          aFunctionName + "() is not allowed for opaque origins"_ns);
      return false;
    }

    if (!FeaturePolicyUtils::IsFeatureAllowed(doc, u"serial"_ns)) {
      nsAutoString message;
      message.AssignLiteral("WebSerial access request was denied: ");
      message.Append(NS_ConvertUTF8toUTF16(aFunctionName));
      message.AppendLiteral("() is not allowed in this context");
      nsContentUtils::ReportToConsoleNonLocalized(
          message, nsIScriptError::errorFlag, "Security"_ns, doc);
      aPromise.MaybeRejectWithSecurityError(
          aFunctionName + "() is not allowed in this context"_ns);
      return false;
    }
    return true;
  }
  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
  if (!workerPrivate) {
    aPromise.MaybeRejectWithSecurityError(
        aFunctionName + "() is not allowed without a window or worker"_ns);
    return false;
  }
  if (!workerPrivate->SerialAllowed()) {
    aPromise.MaybeRejectWithSecurityError(
        aFunctionName + "() is not allowed in this context"_ns);
    return false;
  }
  return true;
}

bool Serial::ValidatePortFilter(bool aHasUsbVendorId, bool aHasUsbProductId,
                                bool aHasBluetoothServiceClassId,
                                nsACString& aFailureReason) {
  // https://wicg.github.io/serial/#ref-for-dom-serialportrequestoptions-filters-1
  if (aHasBluetoothServiceClassId) {
    if (aHasUsbVendorId || aHasUsbProductId) {
      aFailureReason =
          "A filter cannot specify both bluetoothServiceClassId and "
          "usbVendorId or usbProductId."_ns;
      return false;
    }
  } else {
    if (!aHasUsbVendorId) {
      if (!aHasUsbProductId) {
        aFailureReason = "A filter must provide a property to filter by."_ns;
      } else {
        aFailureReason =
            "A filter containing a usbProductId must also specify a usbVendorId."_ns;
      }
      return false;
    }
  }
  return true;
}

already_AddRefed<Promise> Serial::RequestPort(
    const SerialPortRequestOptions& aOptions, ErrorResult& aRv) {
  AssertIsOnMainThread();
  // RequestPort() doesn't work in workers, so we can skip straight to the
  // window.
  nsPIDOMWindowInner* window = GetOwnerWindow();
  if (!window) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("Serial[%p]::RequestPort failed: no window available", this));
    return nullptr;
  }

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

  MOZ_LOG(
      gWebSerialLog, LogLevel::Info,
      ("Serial[%p]::RequestPort called (filters: %s, allowedBluetoothUUIDs: "
       "%s)",
       this, aOptions.mFilters.WasPassed() ? "provided" : "none",
       aOptions.mAllowedBluetoothServiceClassIds.WasPassed() ? "provided"
                                                             : "none"));

  // Step 2: If this's relevant global object's associated Document is not
  // allowed to use the "serial" feature, reject with a SecurityError.
  if (!PortSecurityCheck(*promise, window->AsGlobal(), "requestPort"_ns)) {
    MOZ_LOG(gWebSerialLog, LogLevel::Warning,
            ("Serial[%p]::RequestPort failed security check", this));
    return promise.forget();
  }

  // Step 3: If the relevant global object does not have transient activation,
  // reject with a SecurityError.
  WindowContext* context = window->GetWindowContext();
  if (!context) {
    MOZ_LOG(
        gWebSerialLog, LogLevel::Error,
        ("Serial[%p]::RequestPort failed: no window context available", this));
    promise->MaybeRejectWithNotSupportedError("No window context available");
    return promise.forget();
  }
  if (!context->HasValidTransientUserGestureActivation()) {
    MOZ_LOG(gWebSerialLog, LogLevel::Warning,
            ("Serial[%p]::RequestPort failed: no user activation", this));
    promise->MaybeRejectWithSecurityError(
        "requestPort() requires user activation");
    return promise.forget();
  }

  // Step 4: If options["filters"] is present, validate each filter.
  if (aOptions.mFilters.WasPassed()) {
    MOZ_LOG(gWebSerialLog, LogLevel::Debug,
            ("Serial[%p]::RequestPort validating %zu filters", this,
             aOptions.mFilters.Value().Length()));
    nsAutoCString validatePortFailureReason;
    for (const auto& filter : aOptions.mFilters.Value()) {
      if (!ValidatePortFilter(filter.mUsbVendorId.WasPassed(),
                              filter.mUsbProductId.WasPassed(),
                              filter.mBluetoothServiceClassId.WasPassed(),
                              validatePortFailureReason)) {
        promise->MaybeRejectWithTypeError(validatePortFailureReason);
        MOZ_LOG(gWebSerialLog, LogLevel::Warning,
                ("Serial[%p]::RequestPort failed filter validation", this));
        return promise.forget();
      }
    }
  }

  // Step 5 (in parallel): ask the parent process to enumerate, prompt the
  // user, and record the granted port id.
  SerialManagerChild* child = GetOrCreateManagerChild();
  if (!child) {
    promise->MaybeRejectWithNotSupportedError("Request failed");
    return promise.forget();
  }

  nsTArray<IPCSerialPortFilter> ipcFilters;
  if (aOptions.mFilters.WasPassed()) {
    for (const auto& filter : aOptions.mFilters.Value()) {
      IPCSerialPortFilter ipcFilter;
      if (filter.mUsbVendorId.WasPassed()) {
        ipcFilter.usbVendorId() = Some(filter.mUsbVendorId.Value());
      }
      if (filter.mUsbProductId.WasPassed()) {
        ipcFilter.usbProductId() = Some(filter.mUsbProductId.Value());
      }
      if (filter.mBluetoothServiceClassId.WasPassed()) {
        nsAutoString uuid;
        if (!ResolveBluetoothServiceUUID(
                filter.mBluetoothServiceClassId.Value(), uuid)) {
          promise->MaybeRejectWithTypeError(
              "Invalid bluetoothServiceClassId in port filter");
          return promise.forget();
        }
        ipcFilter.bluetoothServiceClassId() = Some(uuid);
      }
      ipcFilters.AppendElement(std::move(ipcFilter));
    }
  }

  bool autoselect =
      StaticPrefs::dom_webserial_testing_enabled() && mAutoselectPorts;

  child->SendRequestPort(ipcFilters, autoselect)
      ->Then(
          GetMainThreadSerialEventTarget(), __func__,
          [promise, self = RefPtr{this}](
              std::tuple<IPCRequestPortResult,
                         mozilla::ipc::Endpoint<PSerialPortChild>>&& aTuple) {
            IPCRequestPortResult& result = std::get<0>(aTuple);
            mozilla::ipc::Endpoint<PSerialPortChild> endpoint =
                std::move(std::get<1>(aTuple));
            switch (result.reason()) {
              case RequestPortReason::Granted: {
                if (result.port().isNothing()) {
                  promise->MaybeRejectWithNotSupportedError(
                      "Granted port info missing");
                  return;
                }
                RefPtr<SerialPort> port = self->GetOrCreatePort(
                    result.port().value(), std::move(endpoint));
                if (!port) {
                  promise->MaybeRejectWithNotSupportedError(
                      "Failed to create port actor");
                  return;
                }
                port->MarkPhysicallyPresent();
                promise->MaybeResolve(port);
                return;
              }
              case RequestPortReason::UserCancelled:
              case RequestPortReason::AddonDenied:
                promise->MaybeRejectWithNotFoundError("No port selected");
                return;
              case RequestPortReason::InternalError:
                promise->MaybeRejectWithNotSupportedError("Request failed");
                return;
              case RequestPortReason::EndGuard_:
                MOZ_ASSERT_UNREACHABLE("Bad RequestPortReason");
                promise->MaybeRejectWithNotSupportedError("Request failed");
                return;
            }
            promise->MaybeRejectWithNotSupportedError("Request failed");
          },
          [promise](mozilla::ipc::ResponseRejectReason) {
            promise->MaybeRejectWithNotSupportedError("Request failed");
          });

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

already_AddRefed<Promise> Serial::GetPorts(ErrorResult& aRv) {
  nsIGlobalObject* global = GetRelevantGlobal();
  if (!global) {
    aRv.ThrowInvalidStateError("No global object available");
    return nullptr;
  }

  // https://wicg.github.io/serial/#dom-serial-getports
  // 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::Debug,
          ("Serial[%p]::GetPorts called", this));

  // Step 2: If this's relevant global object's associated Document is not
  // allowed to use the "serial" feature, reject with a SecurityError.
  if (!PortSecurityCheck(*promise, global, "getPorts"_ns)) {
    MOZ_LOG(gWebSerialLog, LogLevel::Warning,
            ("Serial[%p]::GetPorts failed security check", this));
    return promise.forget();
  }

  // Step 3 (in parallel): Get the sequence of available serial ports the user
  // has granted access to, then queue a task to resolve the promise.
  if (NS_IsMainThread()) {
    nsTArray<RefPtr<SerialPort>> result;
    for (const auto& port : mPorts) {
      if (!port->IsForgotten() && port->PhysicallyPresent()) {
        result.AppendElement(port);
      }
    }

    MOZ_LOG(
        gWebSerialLog, LogLevel::Info,
        ("Serial[%p]::GetPorts returning %zu ports", this, result.Length()));

    // Queue a task to resolve the promise per spec step 3.3
    NS_DispatchToCurrentThread(NS_NewRunnableFunction(
        "Serial::GetPorts resolve",
        [promise = RefPtr{promise}, result = std::move(result)]() mutable {
          promise->MaybeResolve(result);
        }));

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

  // Worker path: collect known port IDs, dispatch to main thread to get
  // new grants and clone their actors, then dispatch back to create
  // SerialPort objects for any new ports.
  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("Serial[%p]::GetPorts called from worker, dispatching to main "
           "thread",
           this));

  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
  if (!workerPrivate) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("Serial[%p]::GetPorts failed: no worker private", this));
    promise->MaybeRejectWithNotSupportedError("Worker context not available");
    return promise.forget();
  }

  nsTArray<nsString> knownPortIds;
  for (const auto& port : mPorts) {
    knownPortIds.AppendElement(port->Id());
  }

  RefPtr<StrongWorkerRef> strongRef =
      StrongWorkerRef::Create(workerPrivate, "Serial::GetPorts");
  if (!strongRef) {
    promise->MaybeRejectWithAbortError("Worker is shutting down");
    return promise.forget();
  }

  auto tsRef = MakeRefPtr<ThreadSafeWorkerRef>(strongRef);

  struct NewPortData {
    IPCSerialPortInfo mInfo;
    mozilla::ipc::Endpoint<PSerialPortChild> mEndpoint;
  };
  struct GetPortsData {
    nsTArray<NewPortData> mNewPorts;
    nsTArray<nsString> mIdsToForget;
  };
  using GetPortsPromise = MozPromise<GetPortsData, nsresult, true>;

  // InvokeAsync dispatches the lambda to the main thread and returns a
  // MozPromise. No non-threadsafe RefPtrs cross thread boundaries.
  InvokeAsync(
      GetMainThreadSerialEventTarget(), __func__,
      [tsRef, knownPortIds = std::move(knownPortIds)]() {
        Serial* windowSerial =
            FindWindowSerialForWorkerPrivate(tsRef->Private());

        GetPortsData getPortsData;
        if (windowSerial) {
          for (const auto& port : windowSerial->mPorts) {
            if (port->IsForgotten()) {
              continue;
            }

            bool alreadyKnown = false;
            for (const auto& id : knownPortIds) {
              if (id == port->Id()) {
                alreadyKnown = true;
                break;
              }
            }
            if (alreadyKnown) {
              continue;
            }

            RefPtr<SerialPortChild> child = port->GetChild();
            if (!child || !child->CanSend()) {
              continue;
            }

            mozilla::ipc::Endpoint<PSerialPortParent> parentEp;
            mozilla::ipc::Endpoint<PSerialPortChild> childEp;
            if (NS_FAILED(PSerialPort::CreateEndpoints(&parentEp, &childEp))) {
              continue;
            }

            child->SendClone(std::move(parentEp));

            NewPortData data;
            data.mInfo = port->GetPortInfo();
            data.mEndpoint = std::move(childEp);
            getPortsData.mNewPorts.AppendElement(std::move(data));
          }

          // Determine which ports the worker knows about that have been
          // forgotten or removed on the main thread.
          for (const auto& id : knownPortIds) {
            bool stillActive = false;
            for (const auto& port : windowSerial->mPorts) {
              if (!port->IsForgotten() && port->Id() == id) {
                stillActive = true;
                break;
              }
            }
            if (!stillActive) {
              getPortsData.mIdsToForget.AppendElement(id);
            }
          }
        } else {
          // Window Serial is gone; forget all known ports.
          getPortsData.mIdsToForget = knownPortIds.Clone();
        }

        return GetPortsPromise::CreateAndResolve(std::move(getPortsData),
                                                 __func__);
      })
      ->Then(
          GetCurrentSerialEventTarget(), __func__,
          // Capture tsRef here to avoid a race with the worker losing the
          // workerref.
          [self = RefPtr{this}, tsRef, promise](GetPortsData&& aData) {
            for (auto& data : aData.mNewPorts) {
              RefPtr<SerialPort> port =
                  MakeRefPtr<SerialPort>(data.mInfo, self);
              auto actor = MakeRefPtr<SerialPortChild>();
              if (data.mEndpoint.Bind(actor)) {
                actor->SetPort(port);
                port->SetChild(actor);
              }
              self->mPorts.AppendElement(std::move(port));
            }

            for (const auto& id : aData.mIdsToForget) {
              self->ForgetPort(id);
            }

            nsTArray<RefPtr<SerialPort>> result;
            for (const auto& port : self->mPorts) {
              if (!port->IsForgotten() && port->PhysicallyPresent()) {
                result.AppendElement(port);
              }
            }
            promise->MaybeResolve(result);
          },
          [promise](nsresult aRv) {
            promise->MaybeRejectWithNotSupportedError(
                "Failed to get ports from main thread");
          });

  return promise.forget();
}

// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothuuid-canonicaluuid
// Replaces the top 32 bits of 00000000-0000-1000-8000-00805f9b34fb with the
// alias. E.g. canonicalUUID(0xDEADBEEF) =>
// "deadbeef-0000-1000-8000-00805f9b34fb"
static nsAutoString BluetoothCanonicalUUID(uint32_t aAlias) {
  nsAutoString result;
  result.AppendPrintf("%08x-0000-1000-8000-00805f9b34fb", aAlias);
  return result;
}

// A valid UUID is a lowercase string matching
// /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
// https://webbluetoothcg.github.io/web-bluetooth/#valid-uuid
bool Serial::IsValidBluetoothUUID(const nsAString& aString) {
  // 8-4-4-4-12 = 32 hex chars + 4 dashes = 36 chars
  if (aString.Length() != 36) {
    return false;
  }
  const char16_t* data = aString.BeginReading();
  for (uint32_t i = 0; i < 36; i++) {
    if (i == 8 || i == 13 || i == 18 || i == 23) {
      if (data[i] != '-') {
        return false;
      }
    } else if (!((data[i] >= '0' && data[i] <= '9') ||
                 (data[i] >= 'a' && data[i] <= 'f'))) {
      return false;
    }
  }
  return true;
}

// Implements ResolveUUIDName(name, GATT assigned services) from the
// Web Bluetooth spec, used by BluetoothUUID.getService().
// https://webbluetoothcg.github.io/web-bluetooth/#resolveuuidname
// Returns true on success and sets aResult; returns false on error (step 4).
static bool ResolveBluetoothServiceUUID(const OwningStringOrUnsignedLong& aName,
                                        nsAutoString& aResult) {
  // Step 1: If name is an unsigned long, return canonicalUUID(name).
  if (aName.IsUnsignedLong()) {
    aResult = BluetoothCanonicalUUID(aName.GetAsUnsignedLong());
    return true;
  }

  const nsString& name = aName.GetAsString();

  // Step 2: If name is a valid UUID, return name.
  if (Serial::IsValidBluetoothUUID(name)) {
    aResult = name;
    return true;
  }

  // Step 3: If name is a valid name and maps to a UUID in GATT assigned
  // services, return canonicalUUID(alias).
  // We do not currently support GATT assigned service name lookup (bug 2013908)

  // Step 4: Otherwise, throw a TypeError.
  return false;
}

void Serial::ForgetAllPorts() {
  if (mHasShutdown) {
    return;
  }

  nsTArray<RefPtr<SerialPort>> portsToForget;
  for (const auto& port : mPorts) {
    if (!port->IsForgotten()) {
      portsToForget.AppendElement(port);
    }
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("Serial[%p]::ForgetAllPorts forgetting %zu ports", this,
           portsToForget.Length()));

  for (const RefPtr<SerialPort>& port : portsToForget) {
    RefPtr<SerialPort> strongPort = port;
    IgnoredErrorResult rv;
    RefPtr<Promise> promise = strongPort->Forget(rv);
  }
}

RefPtr<SerialPort> Serial::GetOrCreatePort(
    const IPCSerialPortInfo& aInfo,
    mozilla::ipc::Endpoint<PSerialPortChild>&& aEndpoint) {
  // Look for an existing port with the same ID. If present, drop the
  // newly-minted endpoint; letting it fall out of scope closes its
  // channel, which will destroy the orphan SerialPortParent on the
  // other side.
  for (const auto& existing : mPorts) {
    if (existing->Id() == aInfo.id() && !existing->IsForgotten()) {
      return existing;
    }
  }

  if (!aEndpoint.IsValid()) {
    return nullptr;
  }

  auto actor = MakeRefPtr<SerialPortChild>();
  if (!aEndpoint.Bind(actor)) {
    return nullptr;
  }
  RefPtr<SerialPort> port = MakeRefPtr<SerialPort>(aInfo, this);
  actor->SetPort(port);
  port->SetChild(actor);

  mPorts.AppendElement(port);
  return port;
}

void Serial::ForgetPort(const nsAString& aPortId) {
  for (const auto& port : mPorts) {
    if (port->Id() == aPortId && !port->IsForgotten()) {
      RefPtr<SerialPort> strongPort(port);
      strongPort->MarkForgotten();
    }
  }
  mPorts.RemoveElementsBy([&aPortId](const RefPtr<SerialPort>& port) {
    return port->Id() == aPortId;
  });

  // If on a worker, also remove from the main thread's Serial.
  if (!NS_IsMainThread()) {
    WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
    if (workerPrivate) {
      RefPtr<StrongWorkerRef> strongRef =
          StrongWorkerRef::Create(workerPrivate, "Serial::ForgetPort");
      if (strongRef) {
        auto tsRef = MakeRefPtr<ThreadSafeWorkerRef>(strongRef);
        nsString portId(aPortId);
        NS_DispatchToMainThread(NS_NewRunnableFunction(
            "Serial::ForgetPort cross-context",
            [tsRef = std::move(tsRef), portId]() {
              RefPtr<Serial> windowSerial =
                  FindWindowSerialForWorkerPrivate(tsRef->Private());
              if (windowSerial) {
                windowSerial->ForgetPort(portId);
              }
            }));
      }
    }
  }
}

already_AddRefed<Promise> Serial::SimulateDeviceConnection(
    const nsAString& aDeviceId, const nsAString& aDevicePath,
    uint16_t aVendorId, uint16_t aProductId, ErrorResult& aRv) {
  return RunTestingIpc(
      this, aRv, nsLiteralCString("SimulateDeviceConnection IPC error"),
      [deviceId = nsString(aDeviceId), devicePath = nsString(aDevicePath),
       aVendorId, aProductId](SerialManagerChild* aChild) {
        return aChild->SendSimulateDeviceConnection(deviceId, devicePath,
                                                    aVendorId, aProductId);
      });
}

already_AddRefed<Promise> Serial::SimulateDeviceDisconnection(
    const nsAString& aDeviceId, ErrorResult& aRv) {
  return RunTestingIpc(
      this, aRv, nsLiteralCString("SimulateDeviceDisconnection IPC error"),
      [deviceId = nsString(aDeviceId)](SerialManagerChild* aChild) {
        return aChild->SendSimulateDeviceDisconnection(deviceId);
      });
}

already_AddRefed<Promise> Serial::RemoveAllMockDevices(ErrorResult& aRv) {
  return RunTestingIpc(this, aRv,
                       nsLiteralCString("RemoveAllMockDevices IPC error"),
                       [](SerialManagerChild* aChild) {
                         return aChild->SendRemoveAllMockDevices();
                       });
}

already_AddRefed<Promise> Serial::ResetToDefaultMockDevices(ErrorResult& aRv) {
  return RunTestingIpc(this, aRv,
                       nsLiteralCString("ResetToDefaultMockDevices IPC error"),
                       [](SerialManagerChild* aChild) {
                         return aChild->SendResetToDefaultMockDevices();
                       });
}

bool Serial::GetAutoselectPorts(ErrorResult& aRv) const {
  if (!StaticPrefs::dom_webserial_testing_enabled()) {
    aRv.ThrowNotSupportedError("Testing is not enabled");
    return false;
  }
  return mAutoselectPorts;
}

void Serial::SetAutoselectPorts(bool aAutoselect, ErrorResult& aRv) {
  if (!StaticPrefs::dom_webserial_testing_enabled()) {
    aRv.ThrowNotSupportedError("Testing is not enabled");
    return;
  }
  mAutoselectPorts = aAutoselect;
}

}  // 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.