diff options
Diffstat (limited to 'dom/workers/sharedworkers')
-rw-r--r-- | dom/workers/sharedworkers/PSharedWorker.ipdl | 40 | ||||
-rw-r--r-- | dom/workers/sharedworkers/SharedWorker.cpp | 445 | ||||
-rw-r--r-- | dom/workers/sharedworkers/SharedWorker.h | 96 | ||||
-rw-r--r-- | dom/workers/sharedworkers/SharedWorkerChild.cpp | 181 | ||||
-rw-r--r-- | dom/workers/sharedworkers/SharedWorkerChild.h | 61 | ||||
-rw-r--r-- | dom/workers/sharedworkers/SharedWorkerManager.cpp | 348 | ||||
-rw-r--r-- | dom/workers/sharedworkers/SharedWorkerManager.h | 164 | ||||
-rw-r--r-- | dom/workers/sharedworkers/SharedWorkerParent.cpp | 165 | ||||
-rw-r--r-- | dom/workers/sharedworkers/SharedWorkerParent.h | 81 | ||||
-rw-r--r-- | dom/workers/sharedworkers/SharedWorkerService.cpp | 261 | ||||
-rw-r--r-- | dom/workers/sharedworkers/SharedWorkerService.h | 74 | ||||
-rw-r--r-- | dom/workers/sharedworkers/moz.build | 28 |
12 files changed, 1944 insertions, 0 deletions
diff --git a/dom/workers/sharedworkers/PSharedWorker.ipdl b/dom/workers/sharedworkers/PSharedWorker.ipdl new file mode 100644 index 0000000000..2406a731da --- /dev/null +++ b/dom/workers/sharedworkers/PSharedWorker.ipdl @@ -0,0 +1,40 @@ +/* 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 protocol PBackground; + +include RemoteWorkerTypes; + +namespace mozilla { +namespace dom { + +/** + * Protocol for SharedWorker bindings to communicate with per-worker + * SharedWorkerManager instances in the parent via SharedWorkerChild / + * SharedWorkerParent and SharedWorkerService getting/creating the + * SharedWorkerManager if it doesn't already exist. Main-thread to PBackground. + */ +[ManualDealloc] +protocol PSharedWorker +{ + manager PBackground; + +parent: + async Close(); + async Suspend(); + async Resume(); + async Freeze(); + async Thaw(); + +child: + async Error(ErrorValue value); + async NotifyLock(bool aCreated); + async NotifyWebTransport(bool aCreated); + async Terminate(); + + async __delete__(); +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/workers/sharedworkers/SharedWorker.cpp b/dom/workers/sharedworkers/SharedWorker.cpp new file mode 100644 index 0000000000..d3bb3bfcaa --- /dev/null +++ b/dom/workers/sharedworkers/SharedWorker.cpp @@ -0,0 +1,445 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SharedWorker.h" + +#include "mozilla/AntiTrackingUtils.h" +#include "mozilla/AsyncEventDispatcher.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/dom/ClientInfo.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/MessageChannel.h" +#include "mozilla/dom/MessagePort.h" +#include "mozilla/dom/PMessagePort.h" +#include "mozilla/dom/RemoteWorkerManager.h" // RemoteWorkerManager::GetRemoteType +#include "mozilla/dom/RemoteWorkerTypes.h" +#include "mozilla/dom/SharedWorkerBinding.h" +#include "mozilla/dom/SharedWorkerChild.h" +#include "mozilla/dom/WorkerBinding.h" +#include "mozilla/dom/WorkerLoadInfo.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/ipc/PBackgroundChild.h" +#include "mozilla/ipc/URIUtils.h" +#include "mozilla/net/CookieJarSettings.h" +#include "mozilla/StorageAccess.h" +#include "nsGlobalWindowInner.h" +#include "nsPIDOMWindow.h" + +#ifdef XP_WIN +# undef PostMessage +#endif + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::ipc; + +SharedWorker::SharedWorker(nsPIDOMWindowInner* aWindow, + SharedWorkerChild* aActor, MessagePort* aMessagePort) + : DOMEventTargetHelper(aWindow), + mWindow(aWindow), + mActor(aActor), + mMessagePort(aMessagePort), + mFrozen(false) { + AssertIsOnMainThread(); + MOZ_ASSERT(aActor); + MOZ_ASSERT(aMessagePort); +} + +SharedWorker::~SharedWorker() { + AssertIsOnMainThread(); + Close(); +} + +// static +already_AddRefed<SharedWorker> SharedWorker::Constructor( + const GlobalObject& aGlobal, const nsAString& aScriptURL, + const StringOrWorkerOptions& aOptions, ErrorResult& aRv) { + AssertIsOnMainThread(); + + nsCOMPtr<nsPIDOMWindowInner> window = + do_QueryInterface(aGlobal.GetAsSupports()); + MOZ_ASSERT(window); + + // Our current idiom is that storage-related APIs specialize for the system + // principal themselves, which is consistent with StorageAllowedForwindow not + // specializing for the system principal. Without this specialization we + // would end up with ePrivateBrowsing for system principaled private browsing + // windows which is explicitly not what we want. System Principal code always + // should have access to storage. It may make sense to enhance + // StorageAllowedForWindow in the future to handle this after comprehensive + // auditing. + nsCOMPtr<nsIPrincipal> principal = aGlobal.GetSubjectPrincipal(); + StorageAccess storageAllowed; + if (principal && principal->IsSystemPrincipal()) { + storageAllowed = StorageAccess::eAllow; + } else { + storageAllowed = StorageAllowedForWindow(window); + } + + if (storageAllowed == StorageAccess::eDeny) { + aRv.ThrowSecurityError("StorageAccess denied."); + return nullptr; + } + + if (ShouldPartitionStorage(storageAllowed) && + !StoragePartitioningEnabled( + storageAllowed, window->GetExtantDoc()->CookieJarSettings())) { + aRv.ThrowSecurityError("StoragePartitioning not enabled."); + return nullptr; + } + + // Assert that the principal private browsing state matches the + // StorageAccess value. +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + if (storageAllowed == StorageAccess::ePrivateBrowsing) { + uint32_t privateBrowsingId = 0; + if (principal) { + MOZ_ALWAYS_SUCCEEDS(principal->GetPrivateBrowsingId(&privateBrowsingId)); + } + MOZ_DIAGNOSTIC_ASSERT(privateBrowsingId != 0); + } +#endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED + + PBackgroundChild* actorChild = BackgroundChild::GetOrCreateForCurrentThread(); + if (!actorChild || !actorChild->CanSend()) { + aRv.ThrowSecurityError("PBackground not available."); + return nullptr; + } + + nsAutoString name; + WorkerType workerType = WorkerType::Classic; + RequestCredentials credentials = RequestCredentials::Omit; + if (aOptions.IsString()) { + name = aOptions.GetAsString(); + } else { + MOZ_ASSERT(aOptions.IsWorkerOptions()); + name = aOptions.GetAsWorkerOptions().mName; + workerType = aOptions.GetAsWorkerOptions().mType; + credentials = aOptions.GetAsWorkerOptions().mCredentials; + } + + JSContext* cx = aGlobal.Context(); + + WorkerLoadInfo loadInfo; + aRv = WorkerPrivate::GetLoadInfo( + cx, window, nullptr, aScriptURL, workerType, credentials, false, + WorkerPrivate::OverrideLoadGroup, WorkerKindShared, &loadInfo); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + PrincipalInfo principalInfo; + aRv = PrincipalToPrincipalInfo(loadInfo.mPrincipal, &principalInfo); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + PrincipalInfo loadingPrincipalInfo; + aRv = PrincipalToPrincipalInfo(loadInfo.mLoadingPrincipal, + &loadingPrincipalInfo); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + // Here, the PartitionedPrincipal is always equal to the SharedWorker's + // principal because the channel is not opened yet, and, because of this, it's + // not classified. We need to force the correct originAttributes. + // + // The sharedWorker's principal could be a null principal, e.g. loading a + // data url. In this case, we don't need to force the OAs for the partitioned + // principal because creating storage from a null principal will fail anyway. + // We should only do this for content principals. + // + // You can find more details in StoragePrincipalHelper.h + if (ShouldPartitionStorage(storageAllowed) && + BasePrincipal::Cast(loadInfo.mPrincipal)->IsContentPrincipal()) { + nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(window); + if (!sop) { + aRv.ThrowSecurityError("ScriptObjectPrincipal not available."); + return nullptr; + } + + nsIPrincipal* windowPrincipal = sop->GetPrincipal(); + if (!windowPrincipal) { + aRv.ThrowSecurityError("WindowPrincipal not available."); + return nullptr; + } + + nsIPrincipal* windowPartitionedPrincipal = sop->PartitionedPrincipal(); + if (!windowPartitionedPrincipal) { + aRv.ThrowSecurityError("WindowPartitionedPrincipal not available."); + return nullptr; + } + + if (!windowPrincipal->Equals(windowPartitionedPrincipal)) { + loadInfo.mPartitionedPrincipal = + BasePrincipal::Cast(loadInfo.mPrincipal) + ->CloneForcingOriginAttributes( + BasePrincipal::Cast(windowPartitionedPrincipal) + ->OriginAttributesRef()); + } + } + + PrincipalInfo partitionedPrincipalInfo; + if (loadInfo.mPrincipal->Equals(loadInfo.mPartitionedPrincipal)) { + partitionedPrincipalInfo = principalInfo; + } else { + aRv = PrincipalToPrincipalInfo(loadInfo.mPartitionedPrincipal, + &partitionedPrincipalInfo); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + } + + // We don't actually care about this MessageChannel, but we use it to 'steal' + // its 2 connected ports. + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(window); + RefPtr<MessageChannel> channel = MessageChannel::Constructor(global, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + UniqueMessagePortId portIdentifier; + channel->Port1()->CloneAndDisentangle(portIdentifier); + + URIParams resolvedScriptURL; + SerializeURI(loadInfo.mResolvedScriptURI, resolvedScriptURL); + + URIParams baseURL; + SerializeURI(loadInfo.mBaseURI, baseURL); + + // Register this component to PBackground. + bool isSecureContext = JS::GetIsSecureContext(js::GetContextRealm(cx)); + + Maybe<IPCClientInfo> ipcClientInfo; + Maybe<ClientInfo> clientInfo = window->GetClientInfo(); + if (clientInfo.isSome()) { + ipcClientInfo.emplace(clientInfo.value().ToIPC()); + } + + nsID agentClusterId = nsID::GenerateUUID(); + + net::CookieJarSettingsArgs cjsData; + MOZ_ASSERT(loadInfo.mCookieJarSettings); + net::CookieJarSettings::Cast(loadInfo.mCookieJarSettings)->Serialize(cjsData); + + auto remoteType = RemoteWorkerManager::GetRemoteType( + loadInfo.mPrincipal, WorkerKind::WorkerKindShared); + if (NS_WARN_IF(remoteType.isErr())) { + aRv.Throw(remoteType.unwrapErr()); + return nullptr; + } + + Maybe<uint64_t> overriddenFingerprintingSettingsArg; + if (loadInfo.mOverriddenFingerprintingSettings.isSome()) { + overriddenFingerprintingSettingsArg.emplace( + uint64_t(loadInfo.mOverriddenFingerprintingSettings.ref())); + } + + RemoteWorkerData remoteWorkerData( + nsString(aScriptURL), baseURL, resolvedScriptURL, name, workerType, + credentials, loadingPrincipalInfo, principalInfo, + partitionedPrincipalInfo, loadInfo.mUseRegularPrincipal, + loadInfo.mUsingStorageAccess, cjsData, loadInfo.mDomain, isSecureContext, + ipcClientInfo, loadInfo.mReferrerInfo, storageAllowed, + AntiTrackingUtils::IsThirdPartyWindow(window, nullptr), + loadInfo.mShouldResistFingerprinting, overriddenFingerprintingSettingsArg, + OriginTrials::FromWindow(nsGlobalWindowInner::Cast(window)), + void_t() /* OptionalServiceWorkerData */, agentClusterId, + remoteType.unwrap()); + + PSharedWorkerChild* pActor = actorChild->SendPSharedWorkerConstructor( + remoteWorkerData, loadInfo.mWindowID, portIdentifier.release()); + if (!pActor) { + MOZ_ASSERT_UNREACHABLE("We already checked PBackground above."); + aRv.ThrowSecurityError("PBackground not available."); + return nullptr; + } + + RefPtr<SharedWorkerChild> actor = static_cast<SharedWorkerChild*>(pActor); + + RefPtr<SharedWorker> sharedWorker = + new SharedWorker(window, actor, channel->Port2()); + + // Let's inform the window about this SharedWorker. + nsGlobalWindowInner::Cast(window)->StoreSharedWorker(sharedWorker); + actor->SetParent(sharedWorker); + + if (nsGlobalWindowInner::Cast(window)->IsSuspended()) { + sharedWorker->Suspend(); + } + + return sharedWorker.forget(); +} + +MessagePort* SharedWorker::Port() { + AssertIsOnMainThread(); + return mMessagePort; +} + +void SharedWorker::Freeze() { + AssertIsOnMainThread(); + MOZ_ASSERT(!IsFrozen()); + + if (mFrozen) { + return; + } + + mFrozen = true; + + if (mActor) { + mActor->SendFreeze(); + } +} + +void SharedWorker::Thaw() { + AssertIsOnMainThread(); + MOZ_ASSERT(IsFrozen()); + + if (!mFrozen) { + return; + } + + mFrozen = false; + + if (mActor) { + mActor->SendThaw(); + } + + if (!mFrozenEvents.IsEmpty()) { + nsTArray<RefPtr<Event>> events = std::move(mFrozenEvents); + + for (uint32_t index = 0; index < events.Length(); index++) { + RefPtr<Event>& event = events[index]; + MOZ_ASSERT(event); + + RefPtr<EventTarget> target = event->GetTarget(); + ErrorResult rv; + target->DispatchEvent(*event, rv); + if (rv.Failed()) { + NS_WARNING("Failed to dispatch event!"); + } + } + } +} + +void SharedWorker::QueueEvent(Event* aEvent) { + AssertIsOnMainThread(); + MOZ_ASSERT(aEvent); + MOZ_ASSERT(IsFrozen()); + + mFrozenEvents.AppendElement(aEvent); +} + +void SharedWorker::Close() { + AssertIsOnMainThread(); + + if (mWindow) { + nsGlobalWindowInner::Cast(mWindow)->ForgetSharedWorker(this); + mWindow = nullptr; + } + + if (mActor) { + mActor->SendClose(); + mActor->SetParent(nullptr); + mActor = nullptr; + } + + if (mMessagePort) { + mMessagePort->Close(); + } +} + +void SharedWorker::Suspend() { + if (mActor) { + mActor->SendSuspend(); + } +} + +void SharedWorker::Resume() { + if (mActor) { + mActor->SendResume(); + } +} + +void SharedWorker::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage, + const Sequence<JSObject*>& aTransferable, + ErrorResult& aRv) { + AssertIsOnMainThread(); + MOZ_ASSERT(mMessagePort); + + mMessagePort->PostMessage(aCx, aMessage, aTransferable, aRv); +} + +NS_IMPL_ADDREF_INHERITED(SharedWorker, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(SharedWorker, DOMEventTargetHelper) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SharedWorker) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +NS_IMPL_CYCLE_COLLECTION_CLASS(SharedWorker) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SharedWorker, + DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessagePort) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrozenEvents) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SharedWorker, + DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessagePort) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrozenEvents) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +JSObject* SharedWorker::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + AssertIsOnMainThread(); + + return SharedWorker_Binding::Wrap(aCx, this, aGivenProto); +} + +void SharedWorker::GetEventTargetParent(EventChainPreVisitor& aVisitor) { + AssertIsOnMainThread(); + + if (IsFrozen()) { + RefPtr<Event> event = aVisitor.mDOMEvent; + if (!event) { + event = EventDispatcher::CreateEvent(aVisitor.mEvent->mOriginalTarget, + aVisitor.mPresContext, + aVisitor.mEvent, u""_ns); + } + + QueueEvent(event); + + aVisitor.mCanHandle = false; + aVisitor.SetParentTarget(nullptr, false); + return; + } + + DOMEventTargetHelper::GetEventTargetParent(aVisitor); +} + +void SharedWorker::DisconnectFromOwner() { + Close(); + DOMEventTargetHelper::DisconnectFromOwner(); +} + +void SharedWorker::ErrorPropagation(nsresult aError) { + AssertIsOnMainThread(); + MOZ_ASSERT(mActor); + MOZ_ASSERT(NS_FAILED(aError)); + + RefPtr<AsyncEventDispatcher> errorEvent = + new AsyncEventDispatcher(this, u"error"_ns, CanBubble::eNo); + errorEvent->PostDOMEvent(); + + Close(); +} diff --git a/dom/workers/sharedworkers/SharedWorker.h b/dom/workers/sharedworkers/SharedWorker.h new file mode 100644 index 0000000000..be61de8889 --- /dev/null +++ b/dom/workers/sharedworkers/SharedWorker.h @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_dom_workers_sharedworker_h__ +#define mozilla_dom_workers_sharedworker_h__ + +#include "mozilla/dom/WorkerCommon.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/DOMEventTargetHelper.h" + +#ifdef XP_WIN +# undef PostMessage +#endif + +class nsPIDOMWindowInner; + +namespace mozilla { +class EventChainPreVisitor; + +namespace dom { +class MessagePort; +class StringOrWorkerOptions; +class Event; + +class SharedWorkerChild; + +/** + * DOM binding. Holds a SharedWorkerChild. Must exist on the main thread because + * we only allow top-level windows to create SharedWorkers. + */ +class SharedWorker final : public DOMEventTargetHelper { + using ErrorResult = mozilla::ErrorResult; + using GlobalObject = mozilla::dom::GlobalObject; + + RefPtr<nsPIDOMWindowInner> mWindow; + RefPtr<SharedWorkerChild> mActor; + RefPtr<MessagePort> mMessagePort; + nsTArray<RefPtr<Event>> mFrozenEvents; + bool mFrozen; + + public: + static already_AddRefed<SharedWorker> Constructor( + const GlobalObject& aGlobal, const nsAString& aScriptURL, + const StringOrWorkerOptions& aOptions, ErrorResult& aRv); + + MessagePort* Port(); + + bool IsFrozen() const { return mFrozen; } + + void QueueEvent(Event* aEvent); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SharedWorker, DOMEventTargetHelper) + + IMPL_EVENT_HANDLER(error) + + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + void GetEventTargetParent(EventChainPreVisitor& aVisitor) override; + + void DisconnectFromOwner() override; + + void ErrorPropagation(nsresult aError); + + // Methods called from the window. + + void Close(); + + void Suspend(); + + void Resume(); + + void Freeze(); + + void Thaw(); + + private: + SharedWorker(nsPIDOMWindowInner* aWindow, SharedWorkerChild* aActor, + MessagePort* aMessagePort); + + // This class is reference-counted and will be destroyed from Release(). + ~SharedWorker(); + + // Only called by MessagePort. + void PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage, + const Sequence<JSObject*>& aTransferable, ErrorResult& aRv); +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_workers_sharedworker_h__ diff --git a/dom/workers/sharedworkers/SharedWorkerChild.cpp b/dom/workers/sharedworkers/SharedWorkerChild.cpp new file mode 100644 index 0000000000..21ae74681b --- /dev/null +++ b/dom/workers/sharedworkers/SharedWorkerChild.cpp @@ -0,0 +1,181 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SharedWorkerChild.h" +#include "mozilla/dom/ErrorEvent.h" +#include "mozilla/dom/ErrorEventBinding.h" +#include "mozilla/dom/Exceptions.h" +#include "mozilla/dom/RootedDictionary.h" +#include "mozilla/dom/SecurityPolicyViolationEvent.h" +#include "mozilla/dom/SecurityPolicyViolationEventBinding.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/SharedWorker.h" +#include "mozilla/dom/WebTransport.h" +#include "mozilla/dom/WindowGlobalChild.h" +#include "mozilla/dom/WorkerError.h" +#include "mozilla/dom/locks/LockManagerChild.h" + +namespace mozilla { + +using namespace ipc; + +namespace dom { + +SharedWorkerChild::SharedWorkerChild() : mParent(nullptr), mActive(true) {} + +SharedWorkerChild::~SharedWorkerChild() = default; + +void SharedWorkerChild::ActorDestroy(ActorDestroyReason aWhy) { + mActive = false; +} + +void SharedWorkerChild::SendClose() { + if (mActive) { + // This is the last message. + mActive = false; + PSharedWorkerChild::SendClose(); + } +} + +void SharedWorkerChild::SendSuspend() { + if (mActive) { + PSharedWorkerChild::SendSuspend(); + } +} + +void SharedWorkerChild::SendResume() { + if (mActive) { + PSharedWorkerChild::SendResume(); + } +} + +void SharedWorkerChild::SendFreeze() { + if (mActive) { + PSharedWorkerChild::SendFreeze(); + } +} + +void SharedWorkerChild::SendThaw() { + if (mActive) { + PSharedWorkerChild::SendThaw(); + } +} + +IPCResult SharedWorkerChild::RecvError(const ErrorValue& aValue) { + if (!mParent) { + return IPC_OK(); + } + + if (aValue.type() == ErrorValue::Tnsresult) { + mParent->ErrorPropagation(aValue.get_nsresult()); + return IPC_OK(); + } + + nsPIDOMWindowInner* window = mParent->GetOwner(); + uint64_t innerWindowId = window ? window->WindowID() : 0; + + if (aValue.type() == ErrorValue::TCSPViolation) { + SecurityPolicyViolationEventInit violationEventInit; + if (NS_WARN_IF( + !violationEventInit.Init(aValue.get_CSPViolation().json()))) { + return IPC_OK(); + } + + if (NS_WARN_IF(!window)) { + return IPC_OK(); + } + + RefPtr<EventTarget> eventTarget = window->GetExtantDoc(); + if (NS_WARN_IF(!eventTarget)) { + return IPC_OK(); + } + + RefPtr<Event> event = SecurityPolicyViolationEvent::Constructor( + eventTarget, u"securitypolicyviolation"_ns, violationEventInit); + event->SetTrusted(true); + + eventTarget->DispatchEvent(*event); + return IPC_OK(); + } + + if (aValue.type() == ErrorValue::TErrorData && + aValue.get_ErrorData().isWarning()) { + // Don't fire any events for warnings. Just log to console. + WorkerErrorReport::LogErrorToConsole(aValue.get_ErrorData(), innerWindowId); + return IPC_OK(); + } + + AutoJSAPI jsapi; + jsapi.Init(); + + RefPtr<Event> event; + if (aValue.type() == ErrorValue::TErrorData) { + const ErrorData& errorData = aValue.get_ErrorData(); + RootedDictionary<ErrorEventInit> errorInit(jsapi.cx()); + errorInit.mBubbles = false; + errorInit.mCancelable = true; + errorInit.mMessage = errorData.message(); + errorInit.mFilename = errorData.filename(); + errorInit.mLineno = errorData.lineNumber(); + errorInit.mColno = errorData.columnNumber(); + + event = ErrorEvent::Constructor(mParent, u"error"_ns, errorInit); + } else { + event = Event::Constructor(mParent, u"error"_ns, EventInit()); + } + event->SetTrusted(true); + + ErrorResult res; + bool defaultActionEnabled = + mParent->DispatchEvent(*event, CallerType::System, res); + if (res.Failed()) { + ThrowAndReport(window, res.StealNSResult()); + return IPC_OK(); + } + + if (aValue.type() != ErrorValue::TErrorData) { + MOZ_ASSERT(aValue.type() == ErrorValue::Tvoid_t); + return IPC_OK(); + } + + if (defaultActionEnabled) { + WorkerErrorReport::LogErrorToConsole(aValue.get_ErrorData(), innerWindowId); + } + + return IPC_OK(); +} + +IPCResult SharedWorkerChild::RecvNotifyLock(bool aCreated) { + if (!mParent) { + return IPC_OK(); + } + + locks::LockManagerChild::NotifyBFCacheOnMainThread(mParent->GetOwner(), + aCreated); + + return IPC_OK(); +} + +IPCResult SharedWorkerChild::RecvNotifyWebTransport(bool aCreated) { + if (!mParent) { + return IPC_OK(); + } + + WebTransport::NotifyBFCacheOnMainThread(mParent->GetOwner(), aCreated); + + return IPC_OK(); +} + +IPCResult SharedWorkerChild::RecvTerminate() { + if (mParent) { + mParent->Close(); + } + + return IPC_OK(); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/workers/sharedworkers/SharedWorkerChild.h b/dom/workers/sharedworkers/SharedWorkerChild.h new file mode 100644 index 0000000000..c899589320 --- /dev/null +++ b/dom/workers/sharedworkers/SharedWorkerChild.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_dom_dom_SharedWorkerChild_h +#define mozilla_dom_dom_SharedWorkerChild_h + +#include "mozilla/dom/PSharedWorkerChild.h" +#include "nsISupportsImpl.h" + +namespace mozilla::dom { + +class SharedWorker; + +/** + * Held by SharedWorker bindings to remotely control sharedworker lifecycle and + * receive error and termination reports. + */ +class SharedWorkerChild final : public mozilla::dom::PSharedWorkerChild { + friend class PSharedWorkerChild; + + public: + NS_INLINE_DECL_REFCOUNTING(SharedWorkerChild) + + SharedWorkerChild(); + + void SetParent(SharedWorker* aSharedWorker) { mParent = aSharedWorker; } + + void SendClose(); + + void SendSuspend(); + + void SendResume(); + + void SendFreeze(); + + void SendThaw(); + + private: + ~SharedWorkerChild(); + + mozilla::ipc::IPCResult RecvError(const ErrorValue& aValue); + + mozilla::ipc::IPCResult RecvNotifyLock(bool aCreated); + + mozilla::ipc::IPCResult RecvNotifyWebTransport(bool aCreated); + + mozilla::ipc::IPCResult RecvTerminate(); + + void ActorDestroy(ActorDestroyReason aWhy) override; + + // Raw pointer because mParent is set to null when released. + SharedWorker* MOZ_NON_OWNING_REF mParent; + bool mActive; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_dom_SharedWorkerChild_h diff --git a/dom/workers/sharedworkers/SharedWorkerManager.cpp b/dom/workers/sharedworkers/SharedWorkerManager.cpp new file mode 100644 index 0000000000..37dbc700d9 --- /dev/null +++ b/dom/workers/sharedworkers/SharedWorkerManager.cpp @@ -0,0 +1,348 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SharedWorkerManager.h" +#include "SharedWorkerParent.h" +#include "SharedWorkerService.h" +#include "mozilla/dom/MessagePort.h" +#include "mozilla/dom/PSharedWorker.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/ipc/URIUtils.h" +#include "mozilla/dom/RemoteWorkerController.h" +#include "nsIConsoleReportCollector.h" +#include "nsIPrincipal.h" +#include "nsProxyRelease.h" + +namespace mozilla::dom { + +// static +already_AddRefed<SharedWorkerManagerHolder> SharedWorkerManager::Create( + SharedWorkerService* aService, nsIEventTarget* aPBackgroundEventTarget, + const RemoteWorkerData& aData, nsIPrincipal* aLoadingPrincipal, + const OriginAttributes& aEffectiveStoragePrincipalAttrs) { + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr<SharedWorkerManager> manager = + new SharedWorkerManager(aPBackgroundEventTarget, aData, aLoadingPrincipal, + aEffectiveStoragePrincipalAttrs); + + RefPtr<SharedWorkerManagerHolder> holder = + new SharedWorkerManagerHolder(manager, aService); + return holder.forget(); +} + +SharedWorkerManager::SharedWorkerManager( + nsIEventTarget* aPBackgroundEventTarget, const RemoteWorkerData& aData, + nsIPrincipal* aLoadingPrincipal, + const OriginAttributes& aEffectiveStoragePrincipalAttrs) + : mPBackgroundEventTarget(aPBackgroundEventTarget), + mLoadingPrincipal(aLoadingPrincipal), + mDomain(aData.domain()), + mEffectiveStoragePrincipalAttrs(aEffectiveStoragePrincipalAttrs), + mResolvedScriptURL(DeserializeURI(aData.resolvedScriptURL())), + mName(aData.name()), + mIsSecureContext(aData.isSecureContext()), + mSuspended(false), + mFrozen(false) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aLoadingPrincipal); +} + +SharedWorkerManager::~SharedWorkerManager() { + NS_ReleaseOnMainThread("SharedWorkerManager::mLoadingPrincipal", + mLoadingPrincipal.forget()); + NS_ProxyRelease("SharedWorkerManager::mRemoteWorkerController", + mPBackgroundEventTarget, mRemoteWorkerController.forget()); +} + +bool SharedWorkerManager::MaybeCreateRemoteWorker( + const RemoteWorkerData& aData, uint64_t aWindowID, + UniqueMessagePortId& aPortIdentifier, base::ProcessId aProcessId) { + ::mozilla::ipc::AssertIsOnBackgroundThread(); + + // Creating remote workers may result in creating new processes, but during + // parent shutdown that would add just noise, so better bail out. + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + return false; + } + + if (!mRemoteWorkerController) { + mRemoteWorkerController = + RemoteWorkerController::Create(aData, this, aProcessId); + if (NS_WARN_IF(!mRemoteWorkerController)) { + return false; + } + } + + if (aWindowID) { + mRemoteWorkerController->AddWindowID(aWindowID); + } + + mRemoteWorkerController->AddPortIdentifier(aPortIdentifier.release()); + return true; +} + +already_AddRefed<SharedWorkerManagerHolder> +SharedWorkerManager::MatchOnMainThread( + SharedWorkerService* aService, const nsACString& aDomain, + nsIURI* aScriptURL, const nsAString& aName, nsIPrincipal* aLoadingPrincipal, + const OriginAttributes& aEffectiveStoragePrincipalAttrs) { + MOZ_ASSERT(NS_IsMainThread()); + + bool urlEquals; + if (NS_FAILED(aScriptURL->Equals(mResolvedScriptURL, &urlEquals))) { + return nullptr; + } + + bool match = + aDomain == mDomain && urlEquals && aName == mName && + // We want to be sure that the window's principal subsumes the + // SharedWorker's loading principal and vice versa. + mLoadingPrincipal->Subsumes(aLoadingPrincipal) && + aLoadingPrincipal->Subsumes(mLoadingPrincipal) && + mEffectiveStoragePrincipalAttrs == aEffectiveStoragePrincipalAttrs; + if (!match) { + return nullptr; + } + + RefPtr<SharedWorkerManagerHolder> holder = + new SharedWorkerManagerHolder(this, aService); + return holder.forget(); +} + +void SharedWorkerManager::AddActor(SharedWorkerParent* aParent) { + ::mozilla::ipc::AssertIsOnBackgroundThread(); + MOZ_ASSERT(aParent); + MOZ_ASSERT(!mActors.Contains(aParent)); + + mActors.AppendElement(aParent); + + if (mLockCount) { + Unused << aParent->SendNotifyLock(true); + } + + if (mWebTransportCount) { + Unused << aParent->SendNotifyWebTransport(true); + } + + // NB: We don't update our Suspended/Frozen state here, yet. The aParent is + // responsible for doing so from SharedWorkerParent::ManagerCreated. + // XXX But we could avoid iterating all of our actors because if aParent is + // not frozen and we are, we would just need to thaw ourselves. +} + +void SharedWorkerManager::RemoveActor(SharedWorkerParent* aParent) { + ::mozilla::ipc::AssertIsOnBackgroundThread(); + MOZ_ASSERT(aParent); + MOZ_ASSERT(mActors.Contains(aParent)); + + uint64_t windowID = aParent->WindowID(); + if (windowID) { + mRemoteWorkerController->RemoveWindowID(windowID); + } + + mActors.RemoveElement(aParent); + + if (!mActors.IsEmpty()) { + // Our remaining actors could be all suspended or frozen. + UpdateSuspend(); + UpdateFrozen(); + return; + } +} + +void SharedWorkerManager::Terminate() { + ::mozilla::ipc::AssertIsOnBackgroundThread(); + MOZ_ASSERT(mActors.IsEmpty()); + MOZ_ASSERT(mHolders.IsEmpty()); + + // mRemoteWorkerController creation can fail. If the creation fails + // mRemoteWorkerController is nullptr and we should stop termination here. + if (!mRemoteWorkerController) { + return; + } + + mRemoteWorkerController->Terminate(); + mRemoteWorkerController = nullptr; +} + +void SharedWorkerManager::UpdateSuspend() { + ::mozilla::ipc::AssertIsOnBackgroundThread(); + MOZ_ASSERT(mRemoteWorkerController); + + uint32_t suspended = 0; + + for (SharedWorkerParent* actor : mActors) { + if (actor->IsSuspended()) { + ++suspended; + } + } + + // Call Suspend only when all of our actors' windows are suspended and call + // Resume only when one of them resumes. + if ((mSuspended && suspended == mActors.Length()) || + (!mSuspended && suspended != mActors.Length())) { + return; + } + + if (!mSuspended) { + mSuspended = true; + mRemoteWorkerController->Suspend(); + } else { + mSuspended = false; + mRemoteWorkerController->Resume(); + } +} + +void SharedWorkerManager::UpdateFrozen() { + ::mozilla::ipc::AssertIsOnBackgroundThread(); + MOZ_ASSERT(mRemoteWorkerController); + + uint32_t frozen = 0; + + for (SharedWorkerParent* actor : mActors) { + if (actor->IsFrozen()) { + ++frozen; + } + } + + // Similar to UpdateSuspend, above, we only want to be frozen when all of our + // actors are frozen. + if ((mFrozen && frozen == mActors.Length()) || + (!mFrozen && frozen != mActors.Length())) { + return; + } + + if (!mFrozen) { + mFrozen = true; + mRemoteWorkerController->Freeze(); + } else { + mFrozen = false; + mRemoteWorkerController->Thaw(); + } +} + +bool SharedWorkerManager::IsSecureContext() const { return mIsSecureContext; } + +void SharedWorkerManager::CreationFailed() { + ::mozilla::ipc::AssertIsOnBackgroundThread(); + + for (SharedWorkerParent* actor : mActors) { + Unused << actor->SendError(NS_ERROR_FAILURE); + } +} + +void SharedWorkerManager::CreationSucceeded() { + ::mozilla::ipc::AssertIsOnBackgroundThread(); + // Nothing to do here. +} + +void SharedWorkerManager::ErrorReceived(const ErrorValue& aValue) { + ::mozilla::ipc::AssertIsOnBackgroundThread(); + + for (SharedWorkerParent* actor : mActors) { + Unused << actor->SendError(aValue); + } +} + +void SharedWorkerManager::LockNotified(bool aCreated) { + ::mozilla::ipc::AssertIsOnBackgroundThread(); + MOZ_ASSERT_IF(!aCreated, mLockCount > 0); + + mLockCount += aCreated ? 1 : -1; + + // Notify only when we either: + // 1. Got a new lock when nothing were there + // 2. Lost all locks + if ((aCreated && mLockCount == 1) || !mLockCount) { + for (SharedWorkerParent* actor : mActors) { + Unused << actor->SendNotifyLock(aCreated); + } + } +}; + +void SharedWorkerManager::WebTransportNotified(bool aCreated) { + ::mozilla::ipc::AssertIsOnBackgroundThread(); + MOZ_ASSERT_IF(!aCreated, mWebTransportCount > 0); + + mWebTransportCount += aCreated ? 1 : -1; + + // Notify only when we either: + // 1. Got a first WebTransport + // 2. The last WebTransport goes away + if ((aCreated && mWebTransportCount == 1) || mWebTransportCount == 0) { + for (SharedWorkerParent* actor : mActors) { + Unused << actor->SendNotifyWebTransport(aCreated); + } + } +}; + +void SharedWorkerManager::Terminated() { + ::mozilla::ipc::AssertIsOnBackgroundThread(); + + for (SharedWorkerParent* actor : mActors) { + Unused << actor->SendTerminate(); + } +} + +void SharedWorkerManager::RegisterHolder(SharedWorkerManagerHolder* aHolder) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aHolder); + MOZ_ASSERT(!mHolders.Contains(aHolder)); + + mHolders.AppendElement(aHolder); +} + +void SharedWorkerManager::UnregisterHolder(SharedWorkerManagerHolder* aHolder) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aHolder); + MOZ_ASSERT(mHolders.Contains(aHolder)); + + mHolders.RemoveElement(aHolder); + + if (!mHolders.IsEmpty()) { + return; + } + + // Time to go. + + aHolder->Service()->RemoveWorkerManagerOnMainThread(this); + + RefPtr<SharedWorkerManager> self = this; + mPBackgroundEventTarget->Dispatch( + NS_NewRunnableFunction( + "SharedWorkerService::RemoveWorkerManagerOnMainThread", + [self]() { self->Terminate(); }), + NS_DISPATCH_NORMAL); +} + +SharedWorkerManagerHolder::SharedWorkerManagerHolder( + SharedWorkerManager* aManager, SharedWorkerService* aService) + : mManager(aManager), mService(aService) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aManager); + MOZ_ASSERT(aService); + + aManager->RegisterHolder(this); +} + +SharedWorkerManagerHolder::~SharedWorkerManagerHolder() { + MOZ_ASSERT(NS_IsMainThread()); + mManager->UnregisterHolder(this); +} + +SharedWorkerManagerWrapper::SharedWorkerManagerWrapper( + already_AddRefed<SharedWorkerManagerHolder> aHolder) + : mHolder(aHolder) { + MOZ_ASSERT(NS_IsMainThread()); +} + +SharedWorkerManagerWrapper::~SharedWorkerManagerWrapper() { + NS_ReleaseOnMainThread("SharedWorkerManagerWrapper::mHolder", + mHolder.forget()); +} + +} // namespace mozilla::dom diff --git a/dom/workers/sharedworkers/SharedWorkerManager.h b/dom/workers/sharedworkers/SharedWorkerManager.h new file mode 100644 index 0000000000..fceabca4d4 --- /dev/null +++ b/dom/workers/sharedworkers/SharedWorkerManager.h @@ -0,0 +1,164 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_dom_SharedWorkerManager_h +#define mozilla_dom_SharedWorkerManager_h + +#include "SharedWorkerParent.h" +#include "mozilla/dom/RemoteWorkerController.h" +#include "mozilla/dom/quota/CheckedUnsafePtr.h" +#include "nsISupportsImpl.h" +#include "nsTArray.h" + +class nsIPrincipal; + +namespace mozilla::dom { + +class UniqueMessagePortId; +class RemoteWorkerData; +class SharedWorkerManager; +class SharedWorkerService; + +// Main-thread only object that keeps a manager and the service alive. +// When the last SharedWorkerManagerHolder is released, the corresponding +// manager unregisters itself from the service and terminates the worker. +class SharedWorkerManagerHolder final + : public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> { + public: + NS_INLINE_DECL_REFCOUNTING(SharedWorkerManagerHolder); + + SharedWorkerManagerHolder(SharedWorkerManager* aManager, + SharedWorkerService* aService); + + SharedWorkerManager* Manager() const { return mManager; } + + SharedWorkerService* Service() const { return mService; } + + private: + ~SharedWorkerManagerHolder(); + + const RefPtr<SharedWorkerManager> mManager; + const RefPtr<SharedWorkerService> mService; +}; + +// Thread-safe wrapper for SharedWorkerManagerHolder. +class SharedWorkerManagerWrapper final { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedWorkerManagerWrapper); + + explicit SharedWorkerManagerWrapper( + already_AddRefed<SharedWorkerManagerHolder> aHolder); + + SharedWorkerManager* Manager() const { return mHolder->Manager(); } + + private: + ~SharedWorkerManagerWrapper(); + + RefPtr<SharedWorkerManagerHolder> mHolder; +}; + +/** + * PBackground instance that corresponds to a single logical Shared Worker that + * exists somewhere in the process tree. Referenced/owned by multiple + * SharedWorkerParent instances on the PBackground thread. Holds/owns a single + * RemoteWorkerController to interact with the actual shared worker thread, + * wherever it is located. Creates the RemoteWorkerController via + * RemoteWorkerController::Create which uses RemoteWorkerManager::Launch under + * the hood. + */ +class SharedWorkerManager final : public RemoteWorkerObserver { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedWorkerManager, override); + + // Called on main-thread thread methods + + static already_AddRefed<SharedWorkerManagerHolder> Create( + SharedWorkerService* aService, nsIEventTarget* aPBackgroundEventTarget, + const RemoteWorkerData& aData, nsIPrincipal* aLoadingPrincipal, + const OriginAttributes& aEffectiveStoragePrincipalAttrs); + + // Returns a holder if this manager matches. The holder blocks the shutdown of + // the manager. + already_AddRefed<SharedWorkerManagerHolder> MatchOnMainThread( + SharedWorkerService* aService, const nsACString& aDomain, + nsIURI* aScriptURL, const nsAString& aName, + nsIPrincipal* aLoadingPrincipal, + const OriginAttributes& aEffectiveStoragePrincipalAttrs); + + // RemoteWorkerObserver + + void CreationFailed() override; + + void CreationSucceeded() override; + + void ErrorReceived(const ErrorValue& aValue) override; + + void LockNotified(bool aCreated) final; + + void WebTransportNotified(bool aCreated) final; + + void Terminated() override; + + // Called on PBackground thread methods + + bool MaybeCreateRemoteWorker(const RemoteWorkerData& aData, + uint64_t aWindowID, + UniqueMessagePortId& aPortIdentifier, + base::ProcessId aProcessId); + + void AddActor(SharedWorkerParent* aParent); + + void RemoveActor(SharedWorkerParent* aParent); + + void UpdateSuspend(); + + void UpdateFrozen(); + + bool IsSecureContext() const; + + void Terminate(); + + // Called on main-thread only. + + void RegisterHolder(SharedWorkerManagerHolder* aHolder); + + void UnregisterHolder(SharedWorkerManagerHolder* aHolder); + + private: + SharedWorkerManager(nsIEventTarget* aPBackgroundEventTarget, + const RemoteWorkerData& aData, + nsIPrincipal* aLoadingPrincipal, + const OriginAttributes& aEffectiveStoragePrincipalAttrs); + + ~SharedWorkerManager(); + + nsCOMPtr<nsIEventTarget> mPBackgroundEventTarget; + + nsCOMPtr<nsIPrincipal> mLoadingPrincipal; + const nsCString mDomain; + const OriginAttributes mEffectiveStoragePrincipalAttrs; + const nsCOMPtr<nsIURI> mResolvedScriptURL; + const nsString mName; + const bool mIsSecureContext; + bool mSuspended; + bool mFrozen; + uint32_t mLockCount = 0; + uint32_t mWebTransportCount = 0; + + // Raw pointers because SharedWorkerParent unregisters itself in + // ActorDestroy(). + nsTArray<CheckedUnsafePtr<SharedWorkerParent>> mActors; + + RefPtr<RemoteWorkerController> mRemoteWorkerController; + + // Main-thread only. Raw Pointers because holders keep the manager alive and + // they unregister themselves in their DTOR. + nsTArray<CheckedUnsafePtr<SharedWorkerManagerHolder>> mHolders; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_SharedWorkerManager_h diff --git a/dom/workers/sharedworkers/SharedWorkerParent.cpp b/dom/workers/sharedworkers/SharedWorkerParent.cpp new file mode 100644 index 0000000000..38e56f5100 --- /dev/null +++ b/dom/workers/sharedworkers/SharedWorkerParent.cpp @@ -0,0 +1,165 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SharedWorkerParent.h" +#include "SharedWorkerManager.h" +#include "SharedWorkerService.h" +#include "mozilla/dom/RemoteWorkerTypes.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/Unused.h" + +namespace mozilla { + +using namespace ipc; + +namespace dom { + +SharedWorkerParent::SharedWorkerParent() + : mBackgroundEventTarget(GetCurrentSerialEventTarget()), + mStatus(eInit), + mSuspended(false), + mFrozen(false) { + AssertIsOnBackgroundThread(); +} + +SharedWorkerParent::~SharedWorkerParent() = default; + +void SharedWorkerParent::ActorDestroy(IProtocol::ActorDestroyReason aReason) { + AssertIsOnBackgroundThread(); + + if (mWorkerManagerWrapper) { + mWorkerManagerWrapper->Manager()->RemoveActor(this); + mWorkerManagerWrapper = nullptr; + } +} + +void SharedWorkerParent::Initialize( + const RemoteWorkerData& aData, uint64_t aWindowID, + const MessagePortIdentifier& aPortIdentifier) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mStatus == eInit); + + mWindowID = aWindowID; + + mStatus = ePending; + + RefPtr<SharedWorkerService> service = SharedWorkerService::GetOrCreate(); + MOZ_ASSERT(service); + service->GetOrCreateWorkerManager(this, aData, aWindowID, aPortIdentifier); +} + +IPCResult SharedWorkerParent::RecvClose() { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mStatus == ePending || mStatus == eActive); + + mStatus = eClosed; + + if (mWorkerManagerWrapper) { + mWorkerManagerWrapper->Manager()->RemoveActor(this); + mWorkerManagerWrapper = nullptr; + } + + Unused << Send__delete__(this); + return IPC_OK(); +} + +IPCResult SharedWorkerParent::RecvSuspend() { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!mSuspended); + MOZ_ASSERT(mStatus == ePending || mStatus == eActive); + + mSuspended = true; + + if (mStatus == eActive) { + MOZ_ASSERT(mWorkerManagerWrapper); + mWorkerManagerWrapper->Manager()->UpdateSuspend(); + } + + return IPC_OK(); +} + +IPCResult SharedWorkerParent::RecvResume() { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mSuspended); + MOZ_ASSERT(mStatus == ePending || mStatus == eActive); + + mSuspended = false; + + if (mStatus == eActive) { + MOZ_ASSERT(mWorkerManagerWrapper); + mWorkerManagerWrapper->Manager()->UpdateSuspend(); + } + + return IPC_OK(); +} + +IPCResult SharedWorkerParent::RecvFreeze() { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!mFrozen); + MOZ_ASSERT(mStatus == ePending || mStatus == eActive); + + mFrozen = true; + + if (mStatus == eActive) { + MOZ_ASSERT(mWorkerManagerWrapper); + mWorkerManagerWrapper->Manager()->UpdateFrozen(); + } + + return IPC_OK(); +} + +IPCResult SharedWorkerParent::RecvThaw() { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mFrozen); + MOZ_ASSERT(mStatus == ePending || mStatus == eActive); + + mFrozen = false; + + if (mStatus == eActive) { + MOZ_ASSERT(mWorkerManagerWrapper); + mWorkerManagerWrapper->Manager()->UpdateFrozen(); + } + + return IPC_OK(); +} + +void SharedWorkerParent::ManagerCreated( + already_AddRefed<SharedWorkerManagerWrapper> aWorkerManagerWrapper) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!mWorkerManagerWrapper); + MOZ_ASSERT(mStatus == ePending || mStatus == eClosed); + + RefPtr<SharedWorkerManagerWrapper> wrapper = aWorkerManagerWrapper; + + // Already gone. + if (mStatus == eClosed) { + wrapper->Manager()->RemoveActor(this); + return; + } + + mStatus = eActive; + mWorkerManagerWrapper = wrapper; + + mWorkerManagerWrapper->Manager()->UpdateFrozen(); + mWorkerManagerWrapper->Manager()->UpdateSuspend(); +} + +void SharedWorkerParent::ErrorPropagation(nsresult aError) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(NS_FAILED(aError)); + MOZ_ASSERT(mStatus == ePending || mStatus == eClosed); + + // Already gone. + if (mStatus == eClosed) { + return; + } + + Unused << SendError(aError); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/workers/sharedworkers/SharedWorkerParent.h b/dom/workers/sharedworkers/SharedWorkerParent.h new file mode 100644 index 0000000000..c91e66cc2e --- /dev/null +++ b/dom/workers/sharedworkers/SharedWorkerParent.h @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_dom_dom_SharedWorkerParent_h +#define mozilla_dom_dom_SharedWorkerParent_h + +#include "mozilla/dom/PSharedWorkerParent.h" +#include "mozilla/dom/quota/CheckedUnsafePtr.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "nsISupportsImpl.h" + +namespace mozilla::dom { + +class MessagePortIdentifier; +class RemoteWorkerData; +class SharedWorkerManagerWrapper; + +/** + * PBackground actor that relays life-cycle events (freeze/thaw, suspend/resume, + * close) to the PBackground SharedWorkerManager and relays error/termination + * back to the child. + */ +class SharedWorkerParent final + : public mozilla::dom::PSharedWorkerParent, + public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedWorkerParent) + + SharedWorkerParent(); + + void Initialize(const RemoteWorkerData& aData, uint64_t aWindowID, + const MessagePortIdentifier& aPortIdentifier); + + void ManagerCreated( + already_AddRefed<SharedWorkerManagerWrapper> aWorkerManagerWrapper); + + void ErrorPropagation(nsresult aError); + + mozilla::ipc::IPCResult RecvClose(); + + mozilla::ipc::IPCResult RecvSuspend(); + + mozilla::ipc::IPCResult RecvResume(); + + mozilla::ipc::IPCResult RecvFreeze(); + + mozilla::ipc::IPCResult RecvThaw(); + + bool IsSuspended() const { return mSuspended; } + + bool IsFrozen() const { return mFrozen; } + + uint64_t WindowID() const { return mWindowID; } + + private: + ~SharedWorkerParent(); + + void ActorDestroy(IProtocol::ActorDestroyReason aReason) override; + + nsCOMPtr<nsIEventTarget> mBackgroundEventTarget; + RefPtr<SharedWorkerManagerWrapper> mWorkerManagerWrapper; + + enum { + eInit, + ePending, + eActive, + eClosed, + } mStatus; + + uint64_t mWindowID; + + bool mSuspended; + bool mFrozen; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_dom_SharedWorkerParent_h diff --git a/dom/workers/sharedworkers/SharedWorkerService.cpp b/dom/workers/sharedworkers/SharedWorkerService.cpp new file mode 100644 index 0000000000..c7e3ca1d4c --- /dev/null +++ b/dom/workers/sharedworkers/SharedWorkerService.cpp @@ -0,0 +1,261 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SharedWorkerService.h" +#include "mozilla/dom/MessagePort.h" +#include "mozilla/dom/RemoteWorkerTypes.h" +#include "mozilla/dom/SharedWorkerManager.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/ipc/URIUtils.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/StaticMutex.h" +#include "nsIPrincipal.h" +#include "nsProxyRelease.h" + +namespace mozilla { + +using namespace ipc; + +namespace dom { + +namespace { + +StaticMutex sSharedWorkerMutex; + +StaticRefPtr<SharedWorkerService> sSharedWorkerService; + +class GetOrCreateWorkerManagerRunnable final : public Runnable { + public: + GetOrCreateWorkerManagerRunnable(SharedWorkerService* aService, + SharedWorkerParent* aActor, + const RemoteWorkerData& aData, + uint64_t aWindowID, + const MessagePortIdentifier& aPortIdentifier) + : Runnable("GetOrCreateWorkerManagerRunnable"), + mBackgroundEventTarget(GetCurrentSerialEventTarget()), + mService(aService), + mActor(aActor), + mData(aData), + mWindowID(aWindowID), + mPortIdentifier(aPortIdentifier) {} + + NS_IMETHOD + Run() { + mService->GetOrCreateWorkerManagerOnMainThread( + mBackgroundEventTarget, mActor, mData, mWindowID, mPortIdentifier); + + return NS_OK; + } + + private: + nsCOMPtr<nsIEventTarget> mBackgroundEventTarget; + RefPtr<SharedWorkerService> mService; + RefPtr<SharedWorkerParent> mActor; + RemoteWorkerData mData; + uint64_t mWindowID; + UniqueMessagePortId mPortIdentifier; +}; + +class WorkerManagerCreatedRunnable final : public Runnable { + public: + WorkerManagerCreatedRunnable( + already_AddRefed<SharedWorkerManagerWrapper> aManagerWrapper, + SharedWorkerParent* aActor, const RemoteWorkerData& aData, + uint64_t aWindowID, UniqueMessagePortId& aPortIdentifier) + : Runnable("WorkerManagerCreatedRunnable"), + mManagerWrapper(aManagerWrapper), + mActor(aActor), + mData(aData), + mWindowID(aWindowID), + mPortIdentifier(std::move(aPortIdentifier)) {} + + NS_IMETHOD + Run() { + AssertIsOnBackgroundThread(); + + if (NS_WARN_IF( + !mActor->CanSend() || + !mManagerWrapper->Manager()->MaybeCreateRemoteWorker( + mData, mWindowID, mPortIdentifier, mActor->OtherPid()))) { + // If we cannot send, the error won't arrive, but we may log something. + mActor->ErrorPropagation(NS_ERROR_FAILURE); + return NS_OK; + } + + mManagerWrapper->Manager()->AddActor(mActor); + mActor->ManagerCreated(mManagerWrapper.forget()); + return NS_OK; + } + + private: + RefPtr<SharedWorkerManagerWrapper> mManagerWrapper; + RefPtr<SharedWorkerParent> mActor; + RemoteWorkerData mData; + uint64_t mWindowID; + UniqueMessagePortId mPortIdentifier; +}; + +class ErrorPropagationRunnable final : public Runnable { + public: + ErrorPropagationRunnable(SharedWorkerParent* aActor, nsresult aError) + : Runnable("ErrorPropagationRunnable"), mActor(aActor), mError(aError) {} + + NS_IMETHOD + Run() { + AssertIsOnBackgroundThread(); + mActor->ErrorPropagation(mError); + return NS_OK; + } + + private: + RefPtr<SharedWorkerParent> mActor; + nsresult mError; +}; + +} // namespace + +/* static */ +already_AddRefed<SharedWorkerService> SharedWorkerService::GetOrCreate() { + AssertIsOnBackgroundThread(); + + StaticMutexAutoLock lock(sSharedWorkerMutex); + + if (!sSharedWorkerService) { + sSharedWorkerService = new SharedWorkerService(); + // ClearOnShutdown can only be called on main thread + nsresult rv = SchedulerGroup::Dispatch(NS_NewRunnableFunction( + "RegisterSharedWorkerServiceClearOnShutdown", []() { + StaticMutexAutoLock lock(sSharedWorkerMutex); + MOZ_ASSERT(sSharedWorkerService); + ClearOnShutdown(&sSharedWorkerService); + })); + Unused << NS_WARN_IF(NS_FAILED(rv)); + } + + RefPtr<SharedWorkerService> instance = sSharedWorkerService; + return instance.forget(); +} + +/* static */ +SharedWorkerService* SharedWorkerService::Get() { + StaticMutexAutoLock lock(sSharedWorkerMutex); + + MOZ_ASSERT(sSharedWorkerService); + return sSharedWorkerService; +} + +void SharedWorkerService::GetOrCreateWorkerManager( + SharedWorkerParent* aActor, const RemoteWorkerData& aData, + uint64_t aWindowID, const MessagePortIdentifier& aPortIdentifier) { + AssertIsOnBackgroundThread(); + + // The real check happens on main-thread. + RefPtr<GetOrCreateWorkerManagerRunnable> r = + new GetOrCreateWorkerManagerRunnable(this, aActor, aData, aWindowID, + aPortIdentifier); + + nsresult rv = SchedulerGroup::Dispatch(r.forget()); + Unused << NS_WARN_IF(NS_FAILED(rv)); +} + +void SharedWorkerService::GetOrCreateWorkerManagerOnMainThread( + nsIEventTarget* aBackgroundEventTarget, SharedWorkerParent* aActor, + const RemoteWorkerData& aData, uint64_t aWindowID, + UniqueMessagePortId& aPortIdentifier) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aBackgroundEventTarget); + MOZ_ASSERT(aActor); + + auto partitionedPrincipalOrErr = + PrincipalInfoToPrincipal(aData.partitionedPrincipalInfo()); + if (NS_WARN_IF(partitionedPrincipalOrErr.isErr())) { + ErrorPropagationOnMainThread(aBackgroundEventTarget, aActor, + partitionedPrincipalOrErr.unwrapErr()); + return; + } + + auto loadingPrincipalOrErr = + PrincipalInfoToPrincipal(aData.loadingPrincipalInfo()); + if (NS_WARN_IF(loadingPrincipalOrErr.isErr())) { + ErrorPropagationOnMainThread(aBackgroundEventTarget, aActor, + loadingPrincipalOrErr.unwrapErr()); + return; + } + + RefPtr<SharedWorkerManagerHolder> managerHolder; + + nsCOMPtr<nsIPrincipal> loadingPrincipal = loadingPrincipalOrErr.unwrap(); + nsCOMPtr<nsIPrincipal> partitionedPrincipal = + partitionedPrincipalOrErr.unwrap(); + + nsCOMPtr<nsIPrincipal> effectiveStoragePrincipal = partitionedPrincipal; + if (aData.useRegularPrincipal()) { + effectiveStoragePrincipal = loadingPrincipal; + } + + // Let's see if there is already a SharedWorker to share. + nsCOMPtr<nsIURI> resolvedScriptURL = + DeserializeURI(aData.resolvedScriptURL()); + for (SharedWorkerManager* workerManager : mWorkerManagers) { + managerHolder = workerManager->MatchOnMainThread( + this, aData.domain(), resolvedScriptURL, aData.name(), loadingPrincipal, + BasePrincipal::Cast(effectiveStoragePrincipal)->OriginAttributesRef()); + if (managerHolder) { + break; + } + } + + // Let's create a new one. + if (!managerHolder) { + managerHolder = SharedWorkerManager::Create( + this, aBackgroundEventTarget, aData, loadingPrincipal, + BasePrincipal::Cast(effectiveStoragePrincipal)->OriginAttributesRef()); + + mWorkerManagers.AppendElement(managerHolder->Manager()); + } else { + // We are attaching the actor to an existing one. + if (managerHolder->Manager()->IsSecureContext() != + aData.isSecureContext()) { + ErrorPropagationOnMainThread(aBackgroundEventTarget, aActor, + NS_ERROR_DOM_SECURITY_ERR); + return; + } + } + + RefPtr<SharedWorkerManagerWrapper> wrapper = + new SharedWorkerManagerWrapper(managerHolder.forget()); + + RefPtr<WorkerManagerCreatedRunnable> r = new WorkerManagerCreatedRunnable( + wrapper.forget(), aActor, aData, aWindowID, aPortIdentifier); + aBackgroundEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL); +} + +void SharedWorkerService::ErrorPropagationOnMainThread( + nsIEventTarget* aBackgroundEventTarget, SharedWorkerParent* aActor, + nsresult aError) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aBackgroundEventTarget); + MOZ_ASSERT(aActor); + MOZ_ASSERT(NS_FAILED(aError)); + + RefPtr<ErrorPropagationRunnable> r = + new ErrorPropagationRunnable(aActor, aError); + aBackgroundEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL); +} + +void SharedWorkerService::RemoveWorkerManagerOnMainThread( + SharedWorkerManager* aManager) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aManager); + MOZ_ASSERT(mWorkerManagers.Contains(aManager)); + + mWorkerManagers.RemoveElement(aManager); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/workers/sharedworkers/SharedWorkerService.h b/dom/workers/sharedworkers/SharedWorkerService.h new file mode 100644 index 0000000000..c4671163bd --- /dev/null +++ b/dom/workers/sharedworkers/SharedWorkerService.h @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_dom_SharedWorkerService_h +#define mozilla_dom_SharedWorkerService_h + +#include "mozilla/dom/quota/CheckedUnsafePtr.h" +#include "nsISupportsImpl.h" +#include "nsTArray.h" + +class nsIEventTarget; + +namespace mozilla { + +namespace ipc { +class PrincipalInfo; +} + +namespace dom { + +class MessagePortIdentifier; +class RemoteWorkerData; +class SharedWorkerManager; +class SharedWorkerParent; +class UniqueMessagePortId; + +/** + * PBackground service that creates and tracks the per-worker + * SharedWorkerManager instances, allowing rendezvous between SharedWorkerParent + * instances and the SharedWorkerManagers they want to talk to (1:1). + */ +class SharedWorkerService final { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedWorkerService); + + // This can be called on PBackground thread only. + static already_AddRefed<SharedWorkerService> GetOrCreate(); + + // The service, if already created, is available on any thread using this + // method. + static SharedWorkerService* Get(); + + // PBackground method only. + void GetOrCreateWorkerManager(SharedWorkerParent* aActor, + const RemoteWorkerData& aData, + uint64_t aWindowID, + const MessagePortIdentifier& aPortIdentifier); + + void GetOrCreateWorkerManagerOnMainThread( + nsIEventTarget* aBackgroundEventTarget, SharedWorkerParent* aActor, + const RemoteWorkerData& aData, uint64_t aWindowID, + UniqueMessagePortId& aPortIdentifier); + + void RemoveWorkerManagerOnMainThread(SharedWorkerManager* aManager); + + private: + SharedWorkerService() = default; + ~SharedWorkerService() = default; + + void ErrorPropagationOnMainThread(nsIEventTarget* aBackgroundEventTarget, + SharedWorkerParent* aActor, + nsresult aError); + + // Touched on main-thread only. + nsTArray<RefPtr<SharedWorkerManager>> mWorkerManagers; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_SharedWorkerService_h diff --git a/dom/workers/sharedworkers/moz.build b/dom/workers/sharedworkers/moz.build new file mode 100644 index 0000000000..2b83bc9525 --- /dev/null +++ b/dom/workers/sharedworkers/moz.build @@ -0,0 +1,28 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +EXPORTS.mozilla.dom += [ + "SharedWorker.h", + "SharedWorkerChild.h", + "SharedWorkerManager.h", + "SharedWorkerParent.h", +] + +UNIFIED_SOURCES += [ + "SharedWorker.cpp", + "SharedWorkerChild.cpp", + "SharedWorkerManager.cpp", + "SharedWorkerParent.cpp", + "SharedWorkerService.cpp", +] + +IPDL_SOURCES += [ + "PSharedWorker.ipdl", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" |