We are able to remove ServiceWorkerVisible and instead use ServiceWorkersEnabled in its place since we are no longer limiting where ServiceWorker instances are exposed. The correctness of ExtendableMessageEvent.source is addressed later in the stack. Although we enable skip-waiting-installed.https.html here, we also have to make it expected it will sometimes fail due to an inherent IPC race; bug 1926641 describes the situation. Differential Revision: https://phabricator.services.mozilla.com/D180914
313 lines
10 KiB
C++
313 lines
10 KiB
C++
/* -*- 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
|
|
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_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() { 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_CONCRETE(ServiceWorker)
|
|
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
|
|
|
|
JSObject* ServiceWorker::WrapObject(JSContext* aCx,
|
|
JS::Handle<JSObject*> aGivenProto) {
|
|
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;
|
|
}
|
|
|
|
nsIGlobalObject* global = GetOwnerGlobal();
|
|
if (NS_WARN_IF(!global)) {
|
|
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
return;
|
|
}
|
|
|
|
Maybe<ClientInfo> clientInfo = global->GetClientInfo();
|
|
Maybe<ClientState> clientState = global->GetClientState();
|
|
if (NS_WARN_IF(clientInfo.isNothing() || clientState.isNothing())) {
|
|
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
return;
|
|
}
|
|
|
|
auto storageAllowed = clientState.ref().GetStorageAccess();
|
|
// This check should be removed as part of bug 1776271 when we should be able
|
|
// to have a stronger invariant about how content should not be able to see
|
|
// a ServiceWorker instance if there is no access to storage. For now we
|
|
// retain this check as a defense-in-depth mechanism at runtime and a
|
|
// non-diagnostic assert for test purposes.
|
|
MOZ_ASSERT(storageAllowed != StorageAccess::eDeny);
|
|
if (storageAllowed == StorageAccess::eDeny) {
|
|
ServiceWorkerManager::LocalizeAndReportToAllClients(
|
|
mDescriptor.Scope(), "ServiceWorkerPostMessageStorageError",
|
|
nsTArray<nsString>{NS_ConvertUTF8toUTF16(mDescriptor.Scope())});
|
|
aRv.Throw(NS_ERROR_DOM_SECURITY_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 (global->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 contains a shared memory object, then the scope will be restricted
|
|
// and thus return SameProcess. If not, it will return DifferentProcess.
|
|
//
|
|
// We know that an attempt to postMessage a shared memory object will result
|
|
// in an error if the source and target aren't in the same agent cluster and
|
|
// ServiceWorkers are inherently in a different agent cluster from windows,
|
|
// so convert to an error if this binding is attached to a window (which must
|
|
// be the case if this is the main thread).
|
|
if (data->CloneScope() ==
|
|
StructuredCloneHolder::StructuredCloneScope::SameProcess &&
|
|
NS_IsMainThread()) {
|
|
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
|