diff options
Diffstat (limited to 'dom/serviceworkers/ServiceWorker.cpp')
-rw-r--r-- | dom/serviceworkers/ServiceWorker.cpp | 379 |
1 files changed, 379 insertions, 0 deletions
diff --git a/dom/serviceworkers/ServiceWorker.cpp b/dom/serviceworkers/ServiceWorker.cpp new file mode 100644 index 0000000000..74aded5250 --- /dev/null +++ b/dom/serviceworkers/ServiceWorker.cpp @@ -0,0 +1,379 @@ +/* -*- 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 "ServiceWorker.h" + +#include "mozilla/dom/Document.h" +#include "nsGlobalWindowInner.h" +#include "nsPIDOMWindow.h" +#include "ServiceWorkerChild.h" +#include "ServiceWorkerCloneData.h" +#include "ServiceWorkerManager.h" +#include "ServiceWorkerPrivate.h" +#include "ServiceWorkerRegistration.h" +#include "ServiceWorkerUtils.h" + +#include "mozilla/dom/ClientIPCTypes.h" +#include "mozilla/dom/ClientState.h" +#include "mozilla/dom/MessagePortBinding.h" +#include "mozilla/dom/Navigator.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/PBackgroundChild.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StorageAccess.h" + +#ifdef XP_WIN +# undef PostMessage +#endif + +using mozilla::ipc::BackgroundChild; +using mozilla::ipc::PBackgroundChild; + +namespace mozilla::dom { + +static bool IsServiceWorkersTestingEnabledInWindow(JSObject* const aGlobal) { + if (const nsCOMPtr<nsPIDOMWindowInner> innerWindow = + Navigator::GetWindowFromGlobal(aGlobal)) { + if (auto* bc = innerWindow->GetBrowsingContext()) { + return bc->Top()->ServiceWorkersTestingEnabled(); + } + } + return false; +} + +static bool IsInPrivateBrowsing(JSContext* const aCx) { + if (const nsCOMPtr<nsIGlobalObject> global = xpc::CurrentNativeGlobal(aCx)) { + if (const nsCOMPtr<nsIPrincipal> principal = global->PrincipalOrNull()) { + return principal->GetPrivateBrowsingId() > 0; + } + } + return false; +} + +bool ServiceWorkersEnabled(JSContext* aCx, JSObject* aGlobal) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!StaticPrefs::dom_serviceWorkers_enabled()) { + return false; + } + + // xpc::CurrentNativeGlobal below requires rooting + JS::Rooted<JSObject*> global(aCx, aGlobal); + + if (IsInPrivateBrowsing(aCx)) { + return false; + } + + // Allow a webextension principal to register a service worker script with + // a moz-extension url only if 'extensions.service_worker_register.allowed' + // is true. + if (!StaticPrefs::extensions_serviceWorkerRegister_allowed()) { + nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(aCx); + if (principal && BasePrincipal::Cast(principal)->AddonPolicy()) { + return false; + } + } + + if (IsSecureContextOrObjectIsFromSecureContext(aCx, global)) { + return true; + } + + return StaticPrefs::dom_serviceWorkers_testing_enabled() || + IsServiceWorkersTestingEnabledInWindow(global); +} + +bool ServiceWorkerVisible(JSContext* aCx, JSObject* aGlobal) { + if (NS_IsMainThread()) { + // We want to expose ServiceWorker interface only when + // navigator.serviceWorker is available. Currently it may not be available + // with some reasons: + // 1. navigator.serviceWorker is not supported in workers. (bug 1131324) + return ServiceWorkersEnabled(aCx, aGlobal); + } + + // We are already in ServiceWorker and interfaces need to be exposed for e.g. + // globalThis.registration.serviceWorker. Note that navigator.serviceWorker + // is still not supported. (bug 1131324) + return IS_INSTANCE_OF(ServiceWorkerGlobalScope, aGlobal); +} + +// static +already_AddRefed<ServiceWorker> ServiceWorker::Create( + nsIGlobalObject* aOwner, const ServiceWorkerDescriptor& aDescriptor) { + RefPtr<ServiceWorker> ref = new ServiceWorker(aOwner, aDescriptor); + return ref.forget(); +} + +ServiceWorker::ServiceWorker(nsIGlobalObject* aGlobal, + const ServiceWorkerDescriptor& aDescriptor) + : DOMEventTargetHelper(aGlobal), + mDescriptor(aDescriptor), + mShutdown(false), + mLastNotifiedState(ServiceWorkerState::Installing) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_DIAGNOSTIC_ASSERT(aGlobal); + + PBackgroundChild* parentActor = + BackgroundChild::GetOrCreateForCurrentThread(); + if (NS_WARN_IF(!parentActor)) { + Shutdown(); + return; + } + + RefPtr<ServiceWorkerChild> actor = ServiceWorkerChild::Create(); + if (NS_WARN_IF(!actor)) { + Shutdown(); + return; + } + + PServiceWorkerChild* sentActor = + parentActor->SendPServiceWorkerConstructor(actor, aDescriptor.ToIPC()); + if (NS_WARN_IF(!sentActor)) { + Shutdown(); + return; + } + MOZ_DIAGNOSTIC_ASSERT(sentActor == actor); + + mActor = std::move(actor); + mActor->SetOwner(this); + + KeepAliveIfHasListenersFor(nsGkAtoms::onstatechange); + + // The error event handler is required by the spec currently, but is not used + // anywhere. Don't keep the object alive in that case. + + // Attempt to get an existing binding object for the registration + // associated with this ServiceWorker. + RefPtr<ServiceWorkerRegistration> reg = + aGlobal->GetServiceWorkerRegistration(ServiceWorkerRegistrationDescriptor( + mDescriptor.RegistrationId(), mDescriptor.RegistrationVersion(), + mDescriptor.PrincipalInfo(), mDescriptor.Scope(), + ServiceWorkerUpdateViaCache::Imports)); + + if (reg) { + MaybeAttachToRegistration(reg); + // Following codes are commented since GetRegistration has no + // implementation. If we can not get an existing binding object, probably + // need to create one to associate to it. + // https://bugzilla.mozilla.org/show_bug.cgi?id=1769652 + /* + } else { + + RefPtr<ServiceWorker> self = this; + GetRegistration( + [self = std::move(self)]( + const ServiceWorkerRegistrationDescriptor& aDescriptor) { + nsIGlobalObject* global = self->GetParentObject(); + NS_ENSURE_TRUE_VOID(global); + RefPtr<ServiceWorkerRegistration> reg = + global->GetOrCreateServiceWorkerRegistration(aDescriptor); + self->MaybeAttachToRegistration(reg); + }, + [](ErrorResult&& aRv) { + // do nothing + aRv.SuppressException(); + }); + */ + } +} + +ServiceWorker::~ServiceWorker() { + MOZ_ASSERT(NS_IsMainThread()); + Shutdown(); +} + +NS_IMPL_CYCLE_COLLECTION_INHERITED(ServiceWorker, DOMEventTargetHelper, + mRegistration); + +NS_IMPL_ADDREF_INHERITED(ServiceWorker, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(ServiceWorker, DOMEventTargetHelper) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorker) + NS_INTERFACE_MAP_ENTRY(ServiceWorker) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +JSObject* ServiceWorker::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + MOZ_ASSERT(NS_IsMainThread()); + + return ServiceWorker_Binding::Wrap(aCx, this, aGivenProto); +} + +ServiceWorkerState ServiceWorker::State() const { return mDescriptor.State(); } + +void ServiceWorker::SetState(ServiceWorkerState aState) { + NS_ENSURE_TRUE_VOID(aState >= mDescriptor.State()); + mDescriptor.SetState(aState); +} + +void ServiceWorker::MaybeDispatchStateChangeEvent() { + if (mDescriptor.State() <= mLastNotifiedState || !GetParentObject()) { + return; + } + mLastNotifiedState = mDescriptor.State(); + + DOMEventTargetHelper::DispatchTrustedEvent(u"statechange"_ns); + + // Once we have transitioned to the redundant state then no + // more statechange events will occur. We can allow the DOM + // object to GC if script is not holding it alive. + if (mLastNotifiedState == ServiceWorkerState::Redundant) { + IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onstatechange); + } +} + +void ServiceWorker::GetScriptURL(nsString& aURL) const { + CopyUTF8toUTF16(mDescriptor.ScriptURL(), aURL); +} + +void ServiceWorker::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage, + const Sequence<JSObject*>& aTransferable, + ErrorResult& aRv) { + // Step 6.1 of + // https://w3c.github.io/ServiceWorker/#service-worker-postmessage-options + // invokes + // https://w3c.github.io/ServiceWorker/#run-service-worker + // which returns failure in step 3 if the ServiceWorker state is redundant. + // This will result in the "in parallel" step 6.1 of postMessage itself early + // returning without starting the ServiceWorker and without throwing an error. + if (State() == ServiceWorkerState::Redundant) { + return; + } + + nsPIDOMWindowInner* window = GetOwner(); + if (NS_WARN_IF(!window || !window->GetExtantDoc())) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + auto storageAllowed = StorageAllowedForWindow(window); + if (storageAllowed != StorageAccess::eAllow && + (!StaticPrefs::privacy_partition_serviceWorkers() || + !StoragePartitioningEnabled( + storageAllowed, window->GetExtantDoc()->CookieJarSettings()))) { + ServiceWorkerManager::LocalizeAndReportToAllClients( + mDescriptor.Scope(), "ServiceWorkerPostMessageStorageError", + nsTArray<nsString>{NS_ConvertUTF8toUTF16(mDescriptor.Scope())}); + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return; + } + + Maybe<ClientInfo> clientInfo = window->GetClientInfo(); + Maybe<ClientState> clientState = window->GetClientState(); + if (NS_WARN_IF(clientInfo.isNothing() || clientState.isNothing())) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedValue()); + aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransferable, + &transferable); + if (aRv.Failed()) { + return; + } + + // Window-to-SW messages do not allow memory sharing since they are not in the + // same agent cluster group, but we do not want to throw an error during the + // serialization. Because of this, ServiceWorkerCloneData will propagate an + // error message data if the SameProcess serialization is required. So that + // the receiver (service worker) knows that it needs to throw while + // deserialization and sharing memory objects are not propagated to the other + // process. + JS::CloneDataPolicy clonePolicy; + if (nsGlobalWindowInner::Cast(window)->IsSharedMemoryAllowed()) { + clonePolicy.allowSharedMemoryObjects(); + } + + RefPtr<ServiceWorkerCloneData> data = new ServiceWorkerCloneData(); + data->Write(aCx, aMessage, transferable, clonePolicy, aRv); + if (aRv.Failed()) { + return; + } + + // The value of CloneScope() is set while StructuredCloneData::Write(). If the + // aValue contiains a shared memory object, then the scope will be restricted + // and thus return SameProcess. If not, it will return DifferentProcess. + // + // When we postMessage a shared memory object from a window to a service + // worker, the object must be sent from a cross-origin isolated process to + // another one. So, we mark mark this data as an error message data if the + // scope is limited to same process. + if (data->CloneScope() == + StructuredCloneHolder::StructuredCloneScope::SameProcess) { + data->SetAsErrorMessageData(); + } + + if (!mActor) { + return; + } + + ClonedOrErrorMessageData clonedData; + if (!data->BuildClonedMessageData(clonedData)) { + return; + } + + mActor->SendPostMessage( + clonedData, + ClientInfoAndState(clientInfo.ref().ToIPC(), clientState.ref().ToIPC())); +} + +void ServiceWorker::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage, + const StructuredSerializeOptions& aOptions, + ErrorResult& aRv) { + PostMessage(aCx, aMessage, aOptions.mTransfer, aRv); +} + +const ServiceWorkerDescriptor& ServiceWorker::Descriptor() const { + return mDescriptor; +} + +void ServiceWorker::DisconnectFromOwner() { + DOMEventTargetHelper::DisconnectFromOwner(); +} + +void ServiceWorker::RevokeActor(ServiceWorkerChild* aActor) { + MOZ_DIAGNOSTIC_ASSERT(mActor); + MOZ_DIAGNOSTIC_ASSERT(mActor == aActor); + mActor->RevokeOwner(this); + mActor = nullptr; + + mShutdown = true; +} + +void ServiceWorker::MaybeAttachToRegistration( + ServiceWorkerRegistration* aRegistration) { + MOZ_DIAGNOSTIC_ASSERT(aRegistration); + MOZ_DIAGNOSTIC_ASSERT(!mRegistration); + + // If the registration no longer actually references this ServiceWorker + // then we must be in the redundant state. + if (!aRegistration->Descriptor().HasWorker(mDescriptor)) { + SetState(ServiceWorkerState::Redundant); + MaybeDispatchStateChangeEvent(); + return; + } + + mRegistration = aRegistration; +} + +void ServiceWorker::Shutdown() { + if (mShutdown) { + return; + } + mShutdown = true; + + if (mActor) { + mActor->RevokeOwner(this); + mActor->MaybeStartTeardown(); + mActor = nullptr; + } +} + +} // namespace mozilla::dom |