/* -*- 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 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 mBackgroundEventTarget; RefPtr mService; RefPtr mActor; RemoteWorkerData mData; uint64_t mWindowID; UniqueMessagePortId mPortIdentifier; }; class WorkerManagerCreatedRunnable final : public Runnable { public: WorkerManagerCreatedRunnable( already_AddRefed 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 mManagerWrapper; RefPtr 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 mActor; nsresult mError; }; } // namespace /* static */ already_AddRefed SharedWorkerService::GetOrCreate() { AssertIsOnBackgroundThread(); StaticMutexAutoLock lock(sSharedWorkerMutex); if (!sSharedWorkerService) { sSharedWorkerService = new SharedWorkerService(); // ClearOnShutdown can only be called on main thread nsresult rv = SchedulerGroup::Dispatch( TaskCategory::Other, NS_NewRunnableFunction("RegisterSharedWorkerServiceClearOnShutdown", []() { StaticMutexAutoLock lock(sSharedWorkerMutex); MOZ_ASSERT(sSharedWorkerService); ClearOnShutdown(&sSharedWorkerService); })); Unused << NS_WARN_IF(NS_FAILED(rv)); } RefPtr 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 r = new GetOrCreateWorkerManagerRunnable(this, aActor, aData, aWindowID, aPortIdentifier); nsresult rv = SchedulerGroup::Dispatch(TaskCategory::Other, 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 managerHolder; nsCOMPtr loadingPrincipal = loadingPrincipalOrErr.unwrap(); nsCOMPtr partitionedPrincipal = partitionedPrincipalOrErr.unwrap(); nsCOMPtr effectiveStoragePrincipal = partitionedPrincipal; if (aData.useRegularPrincipal()) { effectiveStoragePrincipal = loadingPrincipal; } // Let's see if there is already a SharedWorker to share. nsCOMPtr 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 wrapper = new SharedWorkerManagerWrapper(managerHolder.forget()); RefPtr 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 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