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

Impressum SerialPortParent.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/SerialPortParent.h"

#include "SerialLogging.h"
#include "SerialPortPumps.h"
#include "mozilla/Services.h"
#include "mozilla/dom/SerialManagerParent.h"
#include "mozilla/dom/SerialPlatformService.h"
#include "mozilla/dom/SerialPortIPCTypes.h"
#include "mozilla/ipc/DataPipe.h"
#include "mozilla/ipc/Endpoint.h"
#include "nsHashPropertyBag.h"
#include "nsIObserverService.h"
#include "nsStreamUtils.h"

namespace mozilla::dom {

SerialPortParent::SerialPortParent(const nsString& aPortId, uint64_t aBrowserId,
                                   SerialDeviceChangeProxy* aProxy)
    : mPortId(aPortId), mBrowserId(aBrowserId), mDeviceChangeProxy(aProxy) {
  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("SerialPortParent[%p] created for port '%s'", this,
           NS_ConvertUTF16toUTF8(mPortId).get()));
}

SerialPortParent::~SerialPortParent() {
  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("SerialPortParent[%p] destroyed for port '%s'", this,
           NS_ConvertUTF16toUTF8(mPortId).get()));
}

mozilla::ipc::IPCResult SerialPortParent::RecvOpen(
    const IPCSerialOptions& aOptions, OpenResolver&& aResolver) {
  if (mIsOpen) {
    MOZ_LOG(gWebSerialLog, LogLevel::Warning,
            ("SerialPortParent[%p]::RecvOpen failed: port '%s' already open",
             this, NS_ConvertUTF16toUTF8(mPortId).get()));
    aResolver(NS_ERROR_ALREADY_INITIALIZED);
    return IPC_OK();
  }

  if (aOptions.bufferSize() > kMaxSerialBufferSize) {
    MOZ_LOG(
        gWebSerialLog, LogLevel::Warning,
        ("SerialPortParent[%p]::RecvOpen rejecting oversized bufferSize "
         "%u for port '%s'",
         this, aOptions.bufferSize(), NS_ConvertUTF16toUTF8(mPortId).get()));
    return IPC_FAIL(this, "bufferSize exceeds maximum");
  }

  MOZ_LOG(
      gWebSerialLog, LogLevel::Info,
      ("SerialPortParent[%p]::RecvOpen opening port '%s' (baudRate=%u, "
       "dataBits=%u, stopBits=%u, parity=%u, bufferSize=%u, flowControl=%u)",
       this, NS_ConvertUTF16toUTF8(mPortId).get(), aOptions.baudRate(),
       aOptions.dataBits(), aOptions.stopBits(),
       static_cast<unsigned>(aOptions.parity()), aOptions.bufferSize(),
       static_cast<unsigned>(aOptions.flowControl())));

  RefPtr<SerialPlatformService> service = SerialPlatformService::GetInstance();
  nsresult rv = NS_ERROR_FAILURE;

  if (service) {
    service->AssertIsOnIOThread();

    // Validate portId against enumerated ports before opening.
    bool portFound = false;
    SerialPortList ports;
    nsresult enumRv = service->EnumeratePorts(ports);
    if (NS_SUCCEEDED(enumRv)) {
      for (const auto& portInfo : ports) {
        if (portInfo.id() == mPortId) {
          portFound = true;
          break;
        }
      }
    }

    if (portFound) {
      rv = service->Open(mPortId, aOptions);
    } else {
      MOZ_LOG(gWebSerialLog, LogLevel::Warning,
              ("SerialPortParent[%p]::RecvOpen portId '%s' not found in "
               "enumerated ports, rejecting open",
               this, NS_ConvertUTF16toUTF8(mPortId).get()));
      rv = NS_ERROR_NOT_AVAILABLE;
    }
  }

  if (NS_FAILED(rv)) {
    MOZ_LOG(
        gWebSerialLog, LogLevel::Error,
        ("SerialPortParent[%p]::RecvOpen failed for port '%s': 0x%08x", this,
         NS_ConvertUTF16toUTF8(mPortId).get(), static_cast<uint32_t>(rv)));
    aResolver(rv);
    return IPC_OK();
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("SerialPortParent[%p]::RecvOpen succeeded for port '%s'", this,
           NS_ConvertUTF16toUTF8(mPortId).get()));

  mIsOpen = true;
  mPipeCapacity = std::max(aOptions.bufferSize(), kMinSerialPortPumpSize);

  aResolver(NS_OK);
  return IPC_OK();
}

mozilla::ipc::IPCResult SerialPortParent::RecvClose(CloseResolver&& aResolver) {
  if (!mIsOpen) {
    MOZ_LOG(gWebSerialLog, LogLevel::Debug,
            ("SerialPortParent[%p]::RecvClose: port '%s' already closed", this,
             NS_ConvertUTF16toUTF8(mPortId).get()));
    aResolver(NS_OK);
    return IPC_OK();
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("SerialPortParent[%p]::RecvClose closing port '%s'", this,
           NS_ConvertUTF16toUTF8(mPortId).get()));

  StopPumpsBeforeClose();

  RefPtr<SerialPlatformService> service = SerialPlatformService::GetInstance();
  nsresult rv = NS_ERROR_FAILURE;
  if (service) {
    service->AssertIsOnIOThread();
    rv = service->Close(mPortId);
  }

  if (NS_SUCCEEDED(rv)) {
    MOZ_LOG(gWebSerialLog, LogLevel::Info,
            ("SerialPortParent[%p]::RecvClose succeeded for port '%s'", this,
             NS_ConvertUTF16toUTF8(mPortId).get()));
  } else {
    MOZ_LOG(
        gWebSerialLog, LogLevel::Error,
        ("SerialPortParent[%p]::RecvClose failed for port '%s': 0x%08x", this,
         NS_ConvertUTF16toUTF8(mPortId).get(), static_cast<uint32_t>(rv)));
  }
  mIsOpen = false;

  aResolver(rv);
  return IPC_OK();
}

void SerialPortParent::StopReadPump() {
  if (mReadCopierCtx) {
    MOZ_LOG(gWebSerialLog, LogLevel::Debug,
            ("SerialPortParent[%p]::StopReadPump cancelling copy for port '%s'",
             this, NS_ConvertUTF16toUTF8(mPortId).get()));
    NS_CancelAsyncCopy(mReadCopierCtx, NS_BASE_STREAM_CLOSED);
    mReadCopierCtx = nullptr;
  }
  // mPlatformInputStream is kept alive: on Windows, IOCP associates the kernel
  // file object permanently, so re-registering a new duplicate handle fails.
  // The reader is only destroyed in DestroyPlatformReader (port close).
}

void SerialPortParent::DestroyPlatformReader() {
  if (!mPlatformInputStream) {
    return;
  }
  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("SerialPortParent[%p]::DestroyPlatformReader calling "
           "CloseWithStatus for port '%s'",
           this, NS_ConvertUTF16toUTF8(mPortId).get()));
  // PlatformPipeReader cancels pending I/O and dispatches the handle close
  // to a background thread with NS_DISPATCH_EVENT_MAY_BLOCK, so USB-serial
  // driver cleanup cannot block the IPC I/O thread.
  mPlatformInputStream->CloseWithStatus(NS_ERROR_ABORT);
  mPlatformInputStream = nullptr;
}

void SerialPortParent::StopWritePump() {
  if (mWritePump) {
    mWritePump->Stop();
    mWritePump = nullptr;
  }
  if (mWritePipeReceiver) {
    mWritePipeReceiver->CloseWithStatus(NS_BASE_STREAM_CLOSED);
    mWritePipeReceiver = nullptr;
  }
}

void SerialPortParent::StartReadPump(
    already_AddRefed<mozilla::ipc::DataPipeSender> aReadPipeSender) {
  mReadPipeSender = aReadPipeSender;

  RefPtr<SerialPlatformService> service = SerialPlatformService::GetInstance();
  if (!service) {
    return;
  }

  if (!mPlatformInputStream) {
    uint32_t bufferSize = std::max(mPipeCapacity, kMinSerialPortPumpSize);
    nsresult rv = service->GetReadStream(mPortId, bufferSize,
                                         getter_AddRefs(mPlatformInputStream));
    if (NS_FAILED(rv) || !mPlatformInputStream) {
      MOZ_LOG(gWebSerialLog, LogLevel::Error,
              ("SerialPortParent[%p]::StartReadPump GetReadStream failed for "
               "port '%s': 0x%08x",
               this, NS_ConvertUTF16toUTF8(mPortId).get(),
               static_cast<uint32_t>(rv)));
      mReadPipeSender->CloseWithStatus(NS_FAILED(rv) ? rv : NS_ERROR_FAILURE);
      mReadPipeSender = nullptr;
      return;
    }
  }

  // aCloseSource=false: PlatformPipeReader must outlive the copy because its
  // IOCP registration is permanent per file object on Windows, so we reuse
  // it across flush/reattach cycles.
  nsresult rv =
      NS_AsyncCopy(mPlatformInputStream, mReadPipeSender, service->IOThread(),
                   NS_ASYNCCOPY_VIA_READSEGMENTS, 4096, nullptr, nullptr,
                   /* aCloseSource = */ false, /* aCloseSink = */ true,
                   getter_AddRefs(mReadCopierCtx));
  if (NS_FAILED(rv)) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("SerialPortParent[%p]::StartReadPump NS_AsyncCopy failed for "
             "port '%s': 0x%08x",
             this, NS_ConvertUTF16toUTF8(mPortId).get(),
             static_cast<uint32_t>(rv)));
    mReadPipeSender->CloseWithStatus(rv);
    mReadPipeSender = nullptr;
    return;
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("SerialPortParent[%p]::StartReadPump started async read for "
           "port '%s' (pipeCapacity=%u)",
           this, NS_ConvertUTF16toUTF8(mPortId).get(), mPipeCapacity));
}

void SerialPortParent::StartWritePump(
    already_AddRefed<mozilla::ipc::DataPipeReceiver> aWritePipeReceiver) {
  mWritePipeReceiver = aWritePipeReceiver;

  mWritePump =
      MakeRefPtr<webserial::SerialPortWritePump>(mPortId, mWritePipeReceiver);
  mWritePump->Start();
}

mozilla::ipc::IPCResult SerialPortParent::RecvAttachReadPipe(
    const RefPtr<mozilla::ipc::DataPipeSender>& aReadPipeSender) {
  if (!mIsOpen || !aReadPipeSender) {
    MOZ_LOG(gWebSerialLog, LogLevel::Warning,
            ("SerialPortParent[%p]::RecvAttachReadPipe: port '%s' not open",
             this, NS_ConvertUTF16toUTF8(mPortId).get()));
    if (aReadPipeSender) {
      aReadPipeSender->CloseWithStatus(NS_ERROR_NOT_AVAILABLE);
    }
    return IPC_OK();
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("SerialPortParent[%p]::RecvAttachReadPipe for port '%s'", this,
           NS_ConvertUTF16toUTF8(mPortId).get()));

  // Clean up any existing read pipe/pump from a previous readable stream.
  StopReadPump();

  RefPtr<mozilla::ipc::DataPipeSender> sender = aReadPipeSender;
  StartReadPump(sender.forget());

  return IPC_OK();
}

mozilla::ipc::IPCResult SerialPortParent::RecvAttachWritePipe(
    const RefPtr<mozilla::ipc::DataPipeReceiver>& aWritePipeReceiver) {
  if (!mIsOpen || !aWritePipeReceiver) {
    MOZ_LOG(gWebSerialLog, LogLevel::Warning,
            ("SerialPortParent[%p]::RecvAttachWritePipe: port '%s' not open",
             this, NS_ConvertUTF16toUTF8(mPortId).get()));
    if (aWritePipeReceiver) {
      aWritePipeReceiver->CloseWithStatus(NS_ERROR_NOT_AVAILABLE);
    }
    return IPC_OK();
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("SerialPortParent[%p]::RecvAttachWritePipe for port '%s'", this,
           NS_ConvertUTF16toUTF8(mPortId).get()));

  // Clean up any existing write pipe/pump from a previous writable stream.
  StopWritePump();

  RefPtr<mozilla::ipc::DataPipeReceiver> receiver = aWritePipeReceiver;
  StartWritePump(receiver.forget());

  return IPC_OK();
}

mozilla::ipc::IPCResult SerialPortParent::RecvSetSignals(
    const IPCSerialOutputSignals& aSignals, SetSignalsResolver&& aResolver) {
  if (!mIsOpen) {
    aResolver(NS_ERROR_NOT_AVAILABLE);
    return IPC_OK();
  }

  RefPtr<SerialPlatformService> service = SerialPlatformService::GetInstance();
  nsresult rv = NS_ERROR_FAILURE;
  if (service) {
    service->AssertIsOnIOThread();
    rv = service->SetSignals(mPortId, aSignals);
  }

  aResolver(rv);
  return IPC_OK();
}

mozilla::ipc::IPCResult SerialPortParent::RecvGetSignals(
    GetSignalsResolver&& aResolver) {
  IPCSerialInputSignals signals(
      /* dataCarrierDetect */ false, /* clearToSend */ false,
      /* ringIndicator */ false, /* dataSetReady */ false);

  if (!mIsOpen) {
    aResolver(std::tuple(NS_ERROR_NOT_AVAILABLE, signals));
    return IPC_OK();
  }

  RefPtr<SerialPlatformService> service = SerialPlatformService::GetInstance();
  nsresult rv = NS_ERROR_FAILURE;
  if (service) {
    service->AssertIsOnIOThread();
    rv = service->GetSignals(mPortId, signals);
  }

  aResolver(std::tuple(rv, signals));
  return IPC_OK();
}

mozilla::ipc::IPCResult SerialPortParent::RecvDrain(DrainResolver&& aResolver) {
  if (!mIsOpen) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("SerialPortParent[%p]::RecvDrain failed: port '%s' not open", this,
             NS_ConvertUTF16toUTF8(mPortId).get()));
    aResolver(NS_ERROR_NOT_AVAILABLE);
    return IPC_OK();
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("SerialPortParent[%p]::RecvDrain draining buffers for port '%s'",
           this, NS_ConvertUTF16toUTF8(mPortId).get()));

  // There is no ordering guarantee between DataPipe notifications and IPC
  // messages. The child closes its DataPipeSender before sending Drain, but
  // the DataPipe closure notification may not have reached the parent yet,
  // meaning the write pump may still have unconsumed data. We must wait for
  // the write pipe to be fully closed (all data written to the device)
  // before draining OS transmit buffers.
  auto completeDrain = [portId = mPortId, aResolver]() {
    RefPtr<SerialPlatformService> service =
        SerialPlatformService::GetInstance();
    nsresult rv = NS_ERROR_FAILURE;
    if (service) {
      service->AssertIsOnIOThread();
      rv = service->Drain(portId);
    }
    aResolver(rv);
  };

  if (mWritePump) {
    mWritePump->OnPipeClosed(
        NS_NewRunnableFunction("DrainAfterPipeClosed", completeDrain));
    return IPC_OK();
  }
  completeDrain();
  return IPC_OK();
}

mozilla::ipc::IPCResult SerialPortParent::RecvFlush(bool aReceive,
                                                    FlushResolver&& aResolver) {
  if (!mIsOpen) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("SerialPortParent[%p]::RecvFlush failed: port '%s' not open", this,
             NS_ConvertUTF16toUTF8(mPortId).get()));
    aResolver(NS_ERROR_NOT_AVAILABLE);
    return IPC_OK();
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("SerialPortParent[%p]::RecvFlush flushing %s buffers for port '%s'",
           this, aReceive ? "receive" : "transmit",
           NS_ConvertUTF16toUTF8(mPortId).get()));

  if (aReceive) {
    StopReadPump();
  } else {
    StopWritePump();
  }

  RefPtr<SerialPlatformService> service = SerialPlatformService::GetInstance();
  nsresult rv = NS_ERROR_FAILURE;
  if (service) {
    service->AssertIsOnIOThread();
    rv = service->Flush(mPortId, aReceive);
  }

  aResolver(rv);
  return IPC_OK();
}

void SerialPortParent::NotifySharingStateChanged(bool aConnected) {
  mSharingConnected = aConnected;

  NS_DispatchToMainThread(NS_NewRunnableFunction(
      "SerialPortParent::NotifySharingStateChanged",
      [browserId = mBrowserId, aConnected]() {
        nsCOMPtr<nsIObserverService> obs =
            mozilla::services::GetObserverService();
        if (!obs) {
          return;
        }

        auto props = MakeRefPtr<nsHashPropertyBag>();
        props->SetPropertyAsUint64(u"browserId"_ns, browserId);
        props->SetPropertyAsBool(u"connected"_ns, aConnected);

        obs->NotifyObservers(static_cast<nsIPropertyBag2*>(props),
                             "serial-device-state-changed", nullptr);
      }));
}

mozilla::ipc::IPCResult SerialPortParent::RecvUpdateSharingState(
    bool aConnected) {
  if (aConnected != mSharingConnected) {
    NotifySharingStateChanged(aConnected);
  } else {
    MOZ_LOG(gWebSerialLog, LogLevel::Warning,
            ("SerialPortParent[%p]::RecvUpdateSharingState got same state of "
             "%d for port '%s'",
             this, aConnected ? 1 : 0, NS_ConvertUTF16toUTF8(mPortId).get()));
  }
  return IPC_OK();
}

mozilla::ipc::IPCResult SerialPortParent::RecvClone(
    mozilla::ipc::Endpoint<PSerialPortParent>&& aEndpoint) {
  if (!aEndpoint.IsValid()) {
    return IPC_FAIL(this, "Invalid endpoint in RecvClone");
  }

  auto actor = MakeRefPtr<SerialPortParent>(mPortId, mBrowserId);
  if (!aEndpoint.Bind(actor)) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("SerialPortParent[%p]::RecvClone failed to bind for port '%s'",
             this, NS_ConvertUTF16toUTF8(mPortId).get()));
    return IPC_OK();
  }

  mClones.AppendElement(actor);
  return IPC_OK();
}

void SerialPortParent::NotifyConnected() {
  if (CanSend()) {
    (void)SendConnected();
  }
  for (const auto& clone : mClones) {
    clone->NotifyConnected();
  }
}

void SerialPortParent::NotifyDisconnected() {
  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("SerialPortParent[%p]::NotifyDisconnected for port '%s'", this,
           NS_ConvertUTF16toUTF8(mPortId).get()));

  if (mIsOpen) {
    StopPumpsBeforeClose();
    mIsOpen = false;
    RefPtr<SerialPlatformService> service =
        SerialPlatformService::GetInstance();
    if (service) {
      service->Close(mPortId);
    }
  }

  if (CanSend()) {
    (void)SendDisconnected();
  }
  for (const auto& clone : mClones) {
    clone->NotifyDisconnected();
  }
}

void SerialPortParent::StopPumpsBeforeClose() {
  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("SerialPortParent[%p]::StopPumpsBeforeClose for port '%s' "
           "(hasReader=%d, "
           "hasCopier=%d, hasWritePump=%d)",
           this, NS_ConvertUTF16toUTF8(mPortId).get(), !!mPlatformInputStream,
           !!mReadCopierCtx, !!mWritePump));
  StopReadPump();
  DestroyPlatformReader();
  StopWritePump();
}

void SerialPortParent::ActorDestroy(ActorDestroyReason aWhy) {
  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("SerialPortParent[%p]::ActorDestroy for port '%s' (reason: %d)",
           this, NS_ConvertUTF16toUTF8(mPortId).get(), (int)aWhy));

  if (mDeviceChangeProxy) {
    mDeviceChangeProxy->RemovePortActor(this);
    mDeviceChangeProxy = nullptr;
  }

  StopPumpsBeforeClose();

  if (mIsOpen) {
    RefPtr<SerialPlatformService> service =
        SerialPlatformService::GetInstance();
    if (service) {
      service->Close(mPortId);
    }
    mIsOpen = false;
  }

  // If the child sent connected=true but never sent connected=false (e.g.
  // content process crash or iframe navigation), send the disconnect
  // notification so the browser sharing indicator count stays in sync.
  if (mSharingConnected) {
    NotifySharingStateChanged(false);
  }

  nsTArray<RefPtr<SerialPortParent>> clones = std::move(mClones);
  for (const auto& clone : clones) {
    if (clone->CanSend()) {
      clone->Close();
    }
  }
}

}  // namespace mozilla::dom

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

¤ Dauer der Verarbeitung: 0.1 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.