summaryrefslogtreecommitdiffstats
path: root/dom/serviceworkers/ServiceWorkerContainer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/serviceworkers/ServiceWorkerContainer.cpp')
-rw-r--r--dom/serviceworkers/ServiceWorkerContainer.cpp888
1 files changed, 888 insertions, 0 deletions
diff --git a/dom/serviceworkers/ServiceWorkerContainer.cpp b/dom/serviceworkers/ServiceWorkerContainer.cpp
new file mode 100644
index 0000000000..1a7b5bbed6
--- /dev/null
+++ b/dom/serviceworkers/ServiceWorkerContainer.cpp
@@ -0,0 +1,888 @@
+/* -*- 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 "ServiceWorkerContainer.h"
+
+#include "nsContentPolicyUtils.h"
+#include "nsContentSecurityManager.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/Document.h"
+#include "nsIServiceWorkerManager.h"
+#include "nsIScriptError.h"
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/Components.h"
+#include "mozilla/StaticPrefs_dom.h"
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/StaticPrefs_extensions.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "mozilla/StorageAccess.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "mozilla/dom/ClientIPCTypes.h"
+#include "mozilla/dom/DOMMozPromiseRequestHolder.h"
+#include "mozilla/dom/MessageEvent.h"
+#include "mozilla/dom/MessageEventBinding.h"
+#include "mozilla/dom/Navigator.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/ServiceWorker.h"
+#include "mozilla/dom/ServiceWorkerContainerBinding.h"
+#include "mozilla/dom/ServiceWorkerContainerChild.h"
+#include "mozilla/dom/ServiceWorkerManager.h"
+#include "mozilla/dom/ipc/StructuredCloneData.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+
+#include "ServiceWorker.h"
+#include "ServiceWorkerRegistration.h"
+#include "ServiceWorkerUtils.h"
+
+// This is defined to something else on Windows
+#ifdef DispatchMessage
+# undef DispatchMessage
+#endif
+
+namespace mozilla::dom {
+
+using mozilla::ipc::BackgroundChild;
+using mozilla::ipc::PBackgroundChild;
+using mozilla::ipc::ResponseRejectReason;
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerContainer)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(ServiceWorkerContainer, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(ServiceWorkerContainer, DOMEventTargetHelper)
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(ServiceWorkerContainer, DOMEventTargetHelper,
+ mControllerWorker, mReadyPromise)
+
+// static
+already_AddRefed<ServiceWorkerContainer> ServiceWorkerContainer::Create(
+ nsIGlobalObject* aGlobal) {
+ RefPtr<ServiceWorkerContainer> ref = new ServiceWorkerContainer(aGlobal);
+ return ref.forget();
+}
+
+ServiceWorkerContainer::ServiceWorkerContainer(nsIGlobalObject* aGlobal)
+ : DOMEventTargetHelper(aGlobal), mShutdown(false) {
+ PBackgroundChild* parentActor =
+ BackgroundChild::GetOrCreateForCurrentThread();
+ if (NS_WARN_IF(!parentActor)) {
+ Shutdown();
+ return;
+ }
+
+ RefPtr<ServiceWorkerContainerChild> actor =
+ ServiceWorkerContainerChild::Create();
+ if (NS_WARN_IF(!actor)) {
+ Shutdown();
+ return;
+ }
+
+ PServiceWorkerContainerChild* sentActor =
+ parentActor->SendPServiceWorkerContainerConstructor(actor);
+ if (NS_WARN_IF(!sentActor)) {
+ Shutdown();
+ return;
+ }
+ MOZ_DIAGNOSTIC_ASSERT(sentActor == actor);
+
+ mActor = std::move(actor);
+ mActor->SetOwner(this);
+
+ Maybe<ServiceWorkerDescriptor> controller = aGlobal->GetController();
+ if (controller.isSome()) {
+ mControllerWorker = aGlobal->GetOrCreateServiceWorker(controller.ref());
+ }
+}
+
+ServiceWorkerContainer::~ServiceWorkerContainer() { Shutdown(); }
+
+void ServiceWorkerContainer::DisconnectFromOwner() {
+ mControllerWorker = nullptr;
+ mReadyPromise = nullptr;
+ DOMEventTargetHelper::DisconnectFromOwner();
+}
+
+void ServiceWorkerContainer::ControllerChanged(ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> go = GetParentObject();
+ if (!go) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+ mControllerWorker = go->GetOrCreateServiceWorker(go->GetController().ref());
+ aRv = DispatchTrustedEvent(u"controllerchange"_ns);
+}
+
+using mozilla::dom::ipc::StructuredCloneData;
+
+// A ReceivedMessage represents a message sent via
+// Client.postMessage(). It is used as used both for queuing of
+// incoming messages and as an interface to DispatchMessage().
+struct MOZ_HEAP_CLASS ServiceWorkerContainer::ReceivedMessage {
+ explicit ReceivedMessage(const ClientPostMessageArgs& aArgs)
+ : mServiceWorker(aArgs.serviceWorker()) {
+ mClonedData.CopyFromClonedMessageData(aArgs.clonedData());
+ }
+
+ ServiceWorkerDescriptor mServiceWorker;
+ StructuredCloneData mClonedData;
+
+ NS_INLINE_DECL_REFCOUNTING(ReceivedMessage)
+
+ private:
+ ~ReceivedMessage() = default;
+};
+
+void ServiceWorkerContainer::ReceiveMessage(
+ const ClientPostMessageArgs& aArgs) {
+ RefPtr<ReceivedMessage> message = new ReceivedMessage(aArgs);
+ if (mMessagesStarted) {
+ EnqueueReceivedMessageDispatch(std::move(message));
+ } else {
+ mPendingMessages.AppendElement(message.forget());
+ }
+}
+
+void ServiceWorkerContainer::RevokeActor(ServiceWorkerContainerChild* aActor) {
+ MOZ_DIAGNOSTIC_ASSERT(mActor);
+ MOZ_DIAGNOSTIC_ASSERT(mActor == aActor);
+ mActor->RevokeOwner(this);
+ mActor = nullptr;
+
+ mShutdown = true;
+}
+
+JSObject* ServiceWorkerContainer::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return ServiceWorkerContainer_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+namespace {
+
+already_AddRefed<nsIURI> GetBaseURIFromGlobal(nsIGlobalObject* aGlobal,
+ ErrorResult& aRv) {
+ // It would be nice not to require a window here, but right
+ // now we don't have a great way to get the base URL just
+ // from the nsIGlobalObject.
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal);
+ if (!window) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ Document* doc = window->GetExtantDoc();
+ if (!doc) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> baseURI = doc->GetDocBaseURI();
+ if (!baseURI) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ return baseURI.forget();
+}
+
+} // anonymous namespace
+
+already_AddRefed<Promise> ServiceWorkerContainer::Register(
+ const nsAString& aScriptURL, const RegistrationOptions& aOptions,
+ const CallerType aCallerType, ErrorResult& aRv) {
+ // Note, we can't use GetGlobalIfValid() from the start here. If we
+ // hit a storage failure we want to log a message with the final
+ // scope string we put together below.
+ nsIGlobalObject* global = GetParentObject();
+ if (!global) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ Maybe<ClientInfo> clientInfo = global->GetClientInfo();
+ if (clientInfo.isNothing()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> baseURI = GetBaseURIFromGlobal(global, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Don't use NS_ConvertUTF16toUTF8 because that doesn't let us handle OOM.
+ nsAutoCString scriptURL;
+ if (!AppendUTF16toUTF8(aScriptURL, scriptURL, fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> scriptURI;
+ nsresult rv =
+ NS_NewURI(getter_AddRefs(scriptURI), scriptURL, nullptr, baseURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.ThrowTypeError<MSG_INVALID_URL>(scriptURL);
+ return nullptr;
+ }
+
+ // Never allow script URL with moz-extension scheme if support is fully
+ // disabled by the 'extensions.background_service_worker.enabled' pref.
+ if (scriptURI->SchemeIs("moz-extension") &&
+ !StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup()) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ // In ServiceWorkerContainer.register() the scope argument is parsed against
+ // different base URLs depending on whether it was passed or not.
+ nsCOMPtr<nsIURI> scopeURI;
+
+ // Step 4. If none passed, parse against script's URL
+ if (!aOptions.mScope.WasPassed()) {
+ constexpr auto defaultScope = "./"_ns;
+ rv = NS_NewURI(getter_AddRefs(scopeURI), defaultScope, nullptr, scriptURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ nsAutoCString spec;
+ scriptURI->GetSpec(spec);
+ aRv.ThrowTypeError<MSG_INVALID_SCOPE>(defaultScope, spec);
+ return nullptr;
+ }
+ } else {
+ // Step 5. Parse against entry settings object's base URL.
+ rv = NS_NewURI(getter_AddRefs(scopeURI), aOptions.mScope.Value(), nullptr,
+ baseURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ nsIURI* uri = baseURI ? baseURI : scriptURI;
+ nsAutoCString spec;
+ uri->GetSpec(spec);
+ aRv.ThrowTypeError<MSG_INVALID_SCOPE>(
+ NS_ConvertUTF16toUTF8(aOptions.mScope.Value()), spec);
+ return nullptr;
+ }
+ }
+
+ // Strip the any ref from both the script and scope URLs.
+ nsCOMPtr<nsIURI> cloneWithoutRef;
+ aRv = NS_GetURIWithoutRef(scriptURI, getter_AddRefs(cloneWithoutRef));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ scriptURI = std::move(cloneWithoutRef);
+
+ aRv = NS_GetURIWithoutRef(scopeURI, getter_AddRefs(cloneWithoutRef));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ scopeURI = std::move(cloneWithoutRef);
+
+ ServiceWorkerScopeAndScriptAreValid(clientInfo.ref(), scopeURI, scriptURI,
+ aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global);
+ if (!window) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ Document* doc = window->GetExtantDoc();
+ if (!doc) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ // The next section of code executes an NS_CheckContentLoadPolicy()
+ // check. This is necessary to enforce the CSP of the calling client.
+ // Currently this requires an Document. Once bug 965637 lands we
+ // should try to move this into ServiceWorkerScopeAndScriptAreValid()
+ // using the ClientInfo instead of doing a window-specific check here.
+ // See bug 1455077 for further investigation.
+ nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new mozilla::net::LoadInfo(
+ doc->NodePrincipal(), // loading principal
+ doc->NodePrincipal(), // triggering principal
+ doc, // loading node
+ nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
+ nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER);
+
+ // Check content policy.
+ int16_t decision = nsIContentPolicy::ACCEPT;
+ rv = NS_CheckContentLoadPolicy(scriptURI, secCheckLoadInfo, &decision);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+ if (NS_WARN_IF(decision != nsIContentPolicy::ACCEPT)) {
+ aRv.Throw(NS_ERROR_CONTENT_BLOCKED);
+ return nullptr;
+ }
+
+ // Get the string representation for both the script and scope since
+ // we sanitized them above.
+ nsCString cleanedScopeURL;
+ aRv = scopeURI->GetSpec(cleanedScopeURL);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ nsCString cleanedScriptURL;
+ aRv = scriptURI->GetSpec(cleanedScriptURL);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Verify that the global is valid and has permission to store
+ // data. We perform this late so that we can report the final
+ // scope URL in any error message.
+ Unused << GetGlobalIfValid(aRv, [&](Document* aDoc) {
+ AutoTArray<nsString, 1> param;
+ CopyUTF8toUTF16(cleanedScopeURL, *param.AppendElement());
+ nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+ "Service Workers"_ns, aDoc,
+ nsContentUtils::eDOM_PROPERTIES,
+ "ServiceWorkerRegisterStorageError", param);
+ });
+
+ window->NoteCalledRegisterForServiceWorkerScope(cleanedScopeURL);
+
+ RefPtr<Promise> outer =
+ Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ RefPtr<ServiceWorkerContainer> self = this;
+
+ if (!mActor) {
+ aRv.ThrowInvalidStateError("Can't register service worker");
+ return nullptr;
+ }
+
+ mActor->SendRegister(
+ clientInfo.ref().ToIPC(), nsCString(cleanedScopeURL),
+ nsCString(cleanedScriptURL), aOptions.mUpdateViaCache,
+ [self,
+ outer](const IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult&
+ aResult) {
+ if (aResult.type() ==
+ IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult::
+ TCopyableErrorResult) {
+ // application layer error
+ CopyableErrorResult rv = aResult.get_CopyableErrorResult();
+ MOZ_DIAGNOSTIC_ASSERT(rv.Failed());
+ outer->MaybeReject(std::move(rv));
+ return;
+ }
+ // success
+ const auto& ipcDesc =
+ aResult.get_IPCServiceWorkerRegistrationDescriptor();
+ ErrorResult rv;
+ nsIGlobalObject* global = self->GetGlobalIfValid(rv);
+ if (rv.Failed()) {
+ outer->MaybeReject(std::move(rv));
+ return;
+ }
+ RefPtr<ServiceWorkerRegistration> reg =
+ global->GetOrCreateServiceWorkerRegistration(
+ ServiceWorkerRegistrationDescriptor(ipcDesc));
+ outer->MaybeResolve(reg);
+ },
+ [outer](ResponseRejectReason&& aReason) {
+ // IPC layer error
+ CopyableErrorResult rv;
+ rv.ThrowInvalidStateError("Failed to register service worker");
+ outer->MaybeReject(std::move(rv));
+ });
+
+ return outer.forget();
+}
+
+already_AddRefed<ServiceWorker> ServiceWorkerContainer::GetController() {
+ RefPtr<ServiceWorker> ref = mControllerWorker;
+ return ref.forget();
+}
+
+already_AddRefed<Promise> ServiceWorkerContainer::GetRegistrations(
+ ErrorResult& aRv) {
+ nsIGlobalObject* global = GetGlobalIfValid(aRv, [](Document* aDoc) {
+ nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+ "Service Workers"_ns, aDoc,
+ nsContentUtils::eDOM_PROPERTIES,
+ "ServiceWorkerGetRegistrationStorageError");
+ });
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ Maybe<ClientInfo> clientInfo = global->GetClientInfo();
+ if (clientInfo.isNothing()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ RefPtr<Promise> outer =
+ Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ RefPtr<ServiceWorkerContainer> self = this;
+
+ if (!mActor) {
+ outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return outer.forget();
+ }
+
+ mActor->SendGetRegistrations(
+ clientInfo.ref().ToIPC(),
+ [self, outer](
+ const IPCServiceWorkerRegistrationDescriptorListOrCopyableErrorResult&
+ aResult) {
+ if (aResult.type() ==
+ IPCServiceWorkerRegistrationDescriptorListOrCopyableErrorResult::
+ TCopyableErrorResult) {
+ // application layer error
+ const auto& rv = aResult.get_CopyableErrorResult();
+ MOZ_DIAGNOSTIC_ASSERT(rv.Failed());
+ outer->MaybeReject(CopyableErrorResult(rv));
+ return;
+ }
+ // success
+ const auto& ipcList =
+ aResult.get_IPCServiceWorkerRegistrationDescriptorList();
+ nsTArray<ServiceWorkerRegistrationDescriptor> list(
+ ipcList.values().Length());
+ for (const auto& ipcDesc : ipcList.values()) {
+ list.AppendElement(ServiceWorkerRegistrationDescriptor(ipcDesc));
+ }
+
+ ErrorResult rv;
+ nsIGlobalObject* global = self->GetGlobalIfValid(rv);
+ if (rv.Failed()) {
+ outer->MaybeReject(std::move(rv));
+ return;
+ }
+ nsTArray<RefPtr<ServiceWorkerRegistration>> regList;
+ for (auto& desc : list) {
+ RefPtr<ServiceWorkerRegistration> reg =
+ global->GetOrCreateServiceWorkerRegistration(desc);
+ if (reg) {
+ regList.AppendElement(std::move(reg));
+ }
+ }
+ outer->MaybeResolve(regList);
+ },
+ [outer](ResponseRejectReason&& aReason) {
+ // IPC layer error
+ outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+ });
+
+ return outer.forget();
+}
+
+void ServiceWorkerContainer::StartMessages() {
+ while (!mPendingMessages.IsEmpty()) {
+ EnqueueReceivedMessageDispatch(mPendingMessages.ElementAt(0));
+ mPendingMessages.RemoveElementAt(0);
+ }
+ mMessagesStarted = true;
+}
+
+already_AddRefed<Promise> ServiceWorkerContainer::GetRegistration(
+ const nsAString& aURL, ErrorResult& aRv) {
+ nsIGlobalObject* global = GetGlobalIfValid(aRv, [](Document* aDoc) {
+ nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+ "Service Workers"_ns, aDoc,
+ nsContentUtils::eDOM_PROPERTIES,
+ "ServiceWorkerGetRegistrationStorageError");
+ });
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ Maybe<ClientInfo> clientInfo = global->GetClientInfo();
+ if (clientInfo.isNothing()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> baseURI = GetBaseURIFromGlobal(global, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ aRv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr, baseURI);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ nsCString spec;
+ aRv = uri->GetSpec(spec);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ RefPtr<Promise> outer =
+ Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ RefPtr<ServiceWorkerContainer> self = this;
+
+ if (!mActor) {
+ outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return outer.forget();
+ }
+
+ mActor->SendGetRegistration(
+ clientInfo.ref().ToIPC(), spec,
+ [self,
+ outer](const IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult&
+ aResult) {
+ if (aResult.type() ==
+ IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult::
+ TCopyableErrorResult) {
+ CopyableErrorResult ipcRv(aResult.get_CopyableErrorResult());
+ ErrorResult rv(std::move(ipcRv));
+ if (!rv.Failed()) {
+ // ErrorResult rv;
+ // If rv is a failure then this is an application layer error.
+ // Note, though, we also reject with NS_OK to indicate that we just
+ // didn't find a registration.
+ Unused << self->GetGlobalIfValid(rv);
+ if (!rv.Failed()) {
+ outer->MaybeResolveWithUndefined();
+ return;
+ }
+ }
+ outer->MaybeReject(std::move(rv));
+ return;
+ }
+ // success
+ const auto& ipcDesc =
+ aResult.get_IPCServiceWorkerRegistrationDescriptor();
+ ErrorResult rv;
+ nsIGlobalObject* global = self->GetGlobalIfValid(rv);
+ if (rv.Failed()) {
+ outer->MaybeReject(std::move(rv));
+ return;
+ }
+ RefPtr<ServiceWorkerRegistration> reg =
+ global->GetOrCreateServiceWorkerRegistration(
+ ServiceWorkerRegistrationDescriptor(ipcDesc));
+ outer->MaybeResolve(reg);
+ },
+ [self, outer](ResponseRejectReason&& aReason) {
+ // IPC layer error
+ outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+ });
+ return outer.forget();
+}
+
+Promise* ServiceWorkerContainer::GetReady(ErrorResult& aRv) {
+ if (mReadyPromise) {
+ return mReadyPromise;
+ }
+
+ nsIGlobalObject* global = GetGlobalIfValid(aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ MOZ_DIAGNOSTIC_ASSERT(global);
+
+ Maybe<ClientInfo> clientInfo(global->GetClientInfo());
+ if (clientInfo.isNothing()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ mReadyPromise =
+ Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ RefPtr<ServiceWorkerContainer> self = this;
+ RefPtr<Promise> outer = mReadyPromise;
+
+ if (!mActor) {
+ mReadyPromise->MaybeReject(
+ CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR));
+ return mReadyPromise;
+ }
+
+ mActor->SendGetReady(
+ clientInfo.ref().ToIPC(),
+ [self,
+ outer](const IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult&
+ aResult) {
+ if (aResult.type() ==
+ IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult::
+ TCopyableErrorResult) {
+ // application layer error
+ CopyableErrorResult rv(aResult.get_CopyableErrorResult());
+ MOZ_DIAGNOSTIC_ASSERT(rv.Failed());
+ outer->MaybeReject(std::move(rv));
+ return;
+ }
+ // success
+ const auto& ipcDesc =
+ aResult.get_IPCServiceWorkerRegistrationDescriptor();
+ ErrorResult rv;
+ nsIGlobalObject* global = self->GetGlobalIfValid(rv);
+ if (rv.Failed()) {
+ outer->MaybeReject(std::move(rv));
+ return;
+ }
+ RefPtr<ServiceWorkerRegistration> reg =
+ global->GetOrCreateServiceWorkerRegistration(
+ ServiceWorkerRegistrationDescriptor(ipcDesc));
+ NS_ENSURE_TRUE_VOID(reg);
+
+ // Don't resolve the ready promise until the registration has
+ // reached the right version. This ensures that the active
+ // worker property is set correctly on the registration.
+ reg->WhenVersionReached(ipcDesc.version(), [outer, reg](bool aResult) {
+ outer->MaybeResolve(reg);
+ });
+ },
+ [outer](ResponseRejectReason&& aReason) {
+ // IPC layer error
+ outer->MaybeReject(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR));
+ });
+
+ return mReadyPromise;
+}
+
+// Testing only.
+void ServiceWorkerContainer::GetScopeForUrl(const nsAString& aUrl,
+ nsString& aScope,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsIServiceWorkerManager> swm =
+ mozilla::components::ServiceWorkerManager::Service();
+ if (!swm) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
+ if (NS_WARN_IF(!window)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = StoragePrincipalHelper::GetPrincipal(
+ window,
+ StaticPrefs::privacy_partition_serviceWorkers()
+ ? StoragePrincipalHelper::eForeignPartitionedPrincipal
+ : StoragePrincipalHelper::eRegularPrincipal,
+ getter_AddRefs(principal));
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ aRv = swm->GetScopeForUrl(principal, aUrl, aScope);
+}
+
+nsIGlobalObject* ServiceWorkerContainer::GetGlobalIfValid(
+ ErrorResult& aRv,
+ const std::function<void(Document*)>&& aStorageFailureCB) const {
+ // For now we require a window since ServiceWorkerContainer is
+ // not exposed on worker globals yet. The main thing we need
+ // to fix here to support that is the storage access check via
+ // the nsIGlobalObject.
+ nsPIDOMWindowInner* window = GetOwner();
+ if (NS_WARN_IF(!window)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ nsCOMPtr<Document> doc = window->GetExtantDoc();
+ if (NS_WARN_IF(!doc)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ // Don't allow a service worker to access service worker registrations
+ // from a window with storage disabled. If these windows can access
+ // the registration it increases the chance they can bypass the storage
+ // block via postMessage(), etc.
+ auto storageAllowed = StorageAllowedForWindow(window);
+ if (NS_WARN_IF(storageAllowed != StorageAccess::eAllow &&
+ (!StaticPrefs::privacy_partition_serviceWorkers() ||
+ !StoragePartitioningEnabled(storageAllowed,
+ doc->CookieJarSettings())))) {
+ if (aStorageFailureCB) {
+ aStorageFailureCB(doc);
+ }
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ // Don't allow service workers when the document is chrome.
+ if (NS_WARN_IF(doc->NodePrincipal()->IsSystemPrincipal())) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ return window->AsGlobal();
+}
+
+void ServiceWorkerContainer::EnqueueReceivedMessageDispatch(
+ RefPtr<ReceivedMessage> aMessage) {
+ NS_DispatchToMainThread(NewRunnableMethod<RefPtr<ReceivedMessage>>(
+ "ServiceWorkerContainer::DispatchMessage", this,
+ &ServiceWorkerContainer::DispatchMessage, std::move(aMessage)));
+}
+
+template <typename F>
+void ServiceWorkerContainer::RunWithJSContext(F&& aCallable) {
+ nsCOMPtr<nsIGlobalObject> globalObject;
+ if (nsPIDOMWindowInner* const window = GetOwner()) {
+ globalObject = do_QueryInterface(window);
+ }
+
+ // If AutoJSAPI::Init() fails then either global is nullptr or not
+ // in a usable state.
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(globalObject)) {
+ return;
+ }
+
+ aCallable(jsapi.cx(), globalObject);
+}
+
+void ServiceWorkerContainer::DispatchMessage(RefPtr<ReceivedMessage> aMessage) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // When dispatching a message, either DOMContentLoaded has already
+ // been fired, or someone called startMessages() or set onmessage.
+ // Either way, a global object is supposed to be present. If it's
+ // not, we'd fail to initialize the JS API and exit.
+ RunWithJSContext([this, message = std::move(aMessage)](
+ JSContext* const aCx, nsIGlobalObject* const aGlobal) {
+ ErrorResult result;
+ bool deserializationFailed = false;
+ RootedDictionary<MessageEventInit> init(aCx);
+ auto res = FillInMessageEventInit(aCx, aGlobal, *message, init, result);
+ if (res.isErr()) {
+ deserializationFailed = res.unwrapErr();
+ MOZ_ASSERT_IF(deserializationFailed, init.mData.isNull());
+ MOZ_ASSERT_IF(deserializationFailed, init.mPorts.IsEmpty());
+ MOZ_ASSERT_IF(deserializationFailed, !init.mOrigin.IsEmpty());
+ MOZ_ASSERT_IF(deserializationFailed, !init.mSource.IsNull());
+ result.SuppressException();
+
+ if (!deserializationFailed && result.MaybeSetPendingException(aCx)) {
+ return;
+ }
+ }
+
+ RefPtr<MessageEvent> event = MessageEvent::Constructor(
+ this, deserializationFailed ? u"messageerror"_ns : u"message"_ns, init);
+ event->SetTrusted(true);
+
+ result = NS_OK;
+ DispatchEvent(*event, result);
+ if (result.Failed()) {
+ result.SuppressException();
+ }
+ });
+}
+
+namespace {
+
+nsresult FillInOriginNoSuffix(const ServiceWorkerDescriptor& aServiceWorker,
+ nsString& aOrigin) {
+ using mozilla::ipc::PrincipalInfoToPrincipal;
+
+ nsresult rv;
+
+ auto principalOrErr =
+ PrincipalInfoToPrincipal(aServiceWorker.PrincipalInfo());
+ if (NS_WARN_IF(principalOrErr.isErr())) {
+ return principalOrErr.unwrapErr();
+ }
+
+ nsAutoCString originUTF8;
+ rv = principalOrErr.unwrap()->GetOriginNoSuffix(originUTF8);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ CopyUTF8toUTF16(originUTF8, aOrigin);
+ return NS_OK;
+}
+
+} // namespace
+
+Result<Ok, bool> ServiceWorkerContainer::FillInMessageEventInit(
+ JSContext* const aCx, nsIGlobalObject* const aGlobal,
+ ReceivedMessage& aMessage, MessageEventInit& aInit, ErrorResult& aRv) {
+ // Determining the source and origin should preceed attempting deserialization
+ // because on a "messageerror" event (i.e. when deserialization fails), the
+ // dispatched message needs to contain such an origin and source, per spec:
+ //
+ // "If this throws an exception, catch it, fire an event named messageerror
+ // at destination, using MessageEvent, with the origin attribute initialized
+ // to origin and the source attribute initialized to source, and then abort
+ // these steps." - 6.4 of postMessage
+ // See: https://w3c.github.io/ServiceWorker/#service-worker-postmessage
+ const RefPtr<ServiceWorker> serviceWorkerInstance =
+ aGlobal->GetOrCreateServiceWorker(aMessage.mServiceWorker);
+ if (serviceWorkerInstance) {
+ aInit.mSource.SetValue().SetAsServiceWorker() = serviceWorkerInstance;
+ }
+
+ const nsresult rv =
+ FillInOriginNoSuffix(aMessage.mServiceWorker, aInit.mOrigin);
+ if (NS_FAILED(rv)) {
+ return Err(false);
+ }
+
+ JS::Rooted<JS::Value> messageData(aCx);
+ aMessage.mClonedData.Read(aCx, &messageData, aRv);
+ if (aRv.Failed()) {
+ return Err(true);
+ }
+
+ aInit.mData = messageData;
+
+ if (!aMessage.mClonedData.TakeTransferredPortsAsSequence(aInit.mPorts)) {
+ xpc::Throw(aCx, NS_ERROR_OUT_OF_MEMORY);
+ return Err(false);
+ }
+
+ return Ok();
+}
+
+void ServiceWorkerContainer::Shutdown() {
+ if (mShutdown) {
+ return;
+ }
+ mShutdown = true;
+
+ if (mActor) {
+ mActor->RevokeOwner(this);
+ mActor->MaybeStartTeardown();
+ mActor = nullptr;
+ }
+}
+
+} // namespace mozilla::dom