summaryrefslogtreecommitdiffstats
path: root/dom/workers/sharedworkers/SharedWorker.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/workers/sharedworkers/SharedWorker.cpp')
-rw-r--r--dom/workers/sharedworkers/SharedWorker.cpp445
1 files changed, 445 insertions, 0 deletions
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();
+}