Note that this relies on the new PlatformPipeReader class added for bug 2031881. Using this and NS_AsyncCopy() means that we no longer have to poll for new data from serial devices, which means we're faster to receive that data and also yields better power usage. Differential Revision: https://phabricator.services.mozilla.com/D294690
543 lines
18 KiB
C++
543 lines
18 KiB
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
|