/* -*- 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::Constructor( const GlobalObject& aGlobal, const nsAString& aScriptURL, const StringOrWorkerOptions& aOptions, ErrorResult& aRv) { AssertIsOnMainThread(); nsCOMPtr 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 principal = aGlobal.GetSubjectPrincipal(); StorageAccess storageAllowed; if (principal && principal->IsSystemPrincipal()) { storageAllowed = StorageAccess::eAllow; } else { storageAllowed = StorageAllowedForWindow(window); } if (storageAllowed == StorageAccess::eDeny) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return nullptr; } if (ShouldPartitionStorage(storageAllowed) && !StoragePartitioningEnabled( storageAllowed, window->GetExtantDoc()->CookieJarSettings())) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 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 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 sop = do_QueryInterface(window); if (!sop) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } nsIPrincipal* windowPrincipal = sop->GetPrincipal(); if (!windowPrincipal) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } nsIPrincipal* windowPartitionedPrincipal = sop->PartitionedPrincipal(); if (!windowPartitionedPrincipal) { aRv.Throw(NS_ERROR_UNEXPECTED); 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 global = do_QueryInterface(window); RefPtr 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. PBackgroundChild* actorChild = BackgroundChild::GetOrCreateForCurrentThread(); bool isSecureContext = JS::GetIsSecureContext(js::GetContextRealm(cx)); Maybe ipcClientInfo; Maybe 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; } RemoteWorkerData remoteWorkerData( nsString(aScriptURL), baseURL, resolvedScriptURL, name, workerType, credentials, loadingPrincipalInfo, principalInfo, partitionedPrincipalInfo, loadInfo.mUseRegularPrincipal, loadInfo.mHasStorageAccessPermissionGranted, cjsData, loadInfo.mDomain, isSecureContext, ipcClientInfo, loadInfo.mReferrerInfo, storageAllowed, AntiTrackingUtils::IsThirdPartyWindow(window, nullptr), loadInfo.mShouldResistFingerprinting, OriginTrials::FromWindow(nsGlobalWindowInner::Cast(window)), void_t() /* OptionalServiceWorkerData */, agentClusterId, remoteType.unwrap()); PSharedWorkerChild* pActor = actorChild->SendPSharedWorkerConstructor( remoteWorkerData, loadInfo.mWindowID, portIdentifier.release()); RefPtr actor = static_cast(pActor); MOZ_ASSERT(actor); RefPtr 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> events = std::move(mFrozenEvents); for (uint32_t index = 0; index < events.Length(); index++) { RefPtr& event = events[index]; MOZ_ASSERT(event); RefPtr 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 aMessage, const Sequence& 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 aGivenProto) { AssertIsOnMainThread(); return SharedWorker_Binding::Wrap(aCx, this, aGivenProto); } void SharedWorker::GetEventTargetParent(EventChainPreVisitor& aVisitor) { AssertIsOnMainThread(); if (IsFrozen()) { RefPtr 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::ErrorPropagation(nsresult aError) { AssertIsOnMainThread(); MOZ_ASSERT(mActor); MOZ_ASSERT(NS_FAILED(aError)); RefPtr errorEvent = new AsyncEventDispatcher(this, u"error"_ns, CanBubble::eNo); errorEvent->PostDOMEvent(); Close(); }