gecko-dev/dom/serviceworkers/ServiceWorkerContainer.cpp
Nicholas Nethercote 18fae65f38 Bug 1563139 - Remove StaticPrefs.h. r=glandium
This requires replacing inclusions of it with inclusions of more specific prefs
files.

The exception is that StaticPrefsAll.h, which is equivalent to StaticPrefs.h,
and is used in `Codegen.py` because doing something smarter is tricky and
suitable for a follow-up. As a result, any change to StaticPrefList.yaml will
still trigger recompilation of all the generated DOM bindings files, but that's
still a big improvement over trigger recompilation of every file that uses
static prefs.

Most of the changes in this commit are very boring. The only changes that are
not boring are modules/libpref/*, Codegen.py, and ServoBindings.toml.

Differential Revision: https://phabricator.services.mozilla.com/D39138

--HG--
extra : moz-landing-system : lando
2019-07-26 01:10:23 +00:00

812 lines
26 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 "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 "nsIURL.h"
#include "nsNetUtil.h"
#include "nsPIDOMWindow.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPrefs_dom.h"
#include "nsCycleCollectionParticipant.h"
#include "nsServiceManagerUtils.h"
#include "mozilla/LoadInfo.h"
#include "mozilla/StorageAccess.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/ServiceWorker.h"
#include "mozilla/dom/ServiceWorkerContainerBinding.h"
#include "mozilla/dom/ServiceWorkerManager.h"
#include "mozilla/dom/ipc/StructuredCloneData.h"
#include "RemoteServiceWorkerContainerImpl.h"
#include "ServiceWorker.h"
#include "ServiceWorkerContainerImpl.h"
#include "ServiceWorkerRegistration.h"
#include "ServiceWorkerUtils.h"
// This is defined to something else on Windows
#ifdef DispatchMessage
# undef DispatchMessage
#endif
namespace mozilla {
namespace dom {
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)
namespace {
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 IsServiceWorkersTestingEnabledInWindow(JSObject* const aGlobal) {
if (const nsCOMPtr<nsPIDOMWindowInner> innerWindow =
Navigator::GetWindowFromGlobal(aGlobal)) {
if (const nsCOMPtr<nsPIDOMWindowOuter> outerWindow =
innerWindow->GetOuterWindow()) {
return outerWindow->GetServiceWorkersTestingEnabled();
}
}
return false;
}
} // namespace
/* static */
bool ServiceWorkerContainer::IsEnabled(JSContext* aCx, JSObject* aGlobal) {
MOZ_ASSERT(NS_IsMainThread());
JS::Rooted<JSObject*> global(aCx, aGlobal);
if (!StaticPrefs::dom_serviceWorkers_enabled()) {
return false;
}
if (IsInPrivateBrowsing(aCx)) {
return false;
}
if (IsSecureContextOrObjectIsFromSecureContext(aCx, global)) {
return true;
}
const bool isTestingEnabledInWindow =
IsServiceWorkersTestingEnabledInWindow(global);
const bool isTestingEnabledByPref =
StaticPrefs::dom_serviceWorkers_testing_enabled();
const bool isTestingEnabled =
isTestingEnabledByPref || isTestingEnabledInWindow;
return isTestingEnabled;
}
// static
already_AddRefed<ServiceWorkerContainer> ServiceWorkerContainer::Create(
nsIGlobalObject* aGlobal) {
RefPtr<Inner> inner;
if (ServiceWorkerParentInterceptEnabled()) {
inner = new RemoteServiceWorkerContainerImpl();
} else {
inner = new ServiceWorkerContainerImpl();
}
NS_ENSURE_TRUE(inner, nullptr);
RefPtr<ServiceWorkerContainer> ref =
new ServiceWorkerContainer(aGlobal, inner.forget());
return ref.forget();
}
ServiceWorkerContainer::ServiceWorkerContainer(
nsIGlobalObject* aGlobal,
already_AddRefed<ServiceWorkerContainer::Inner> aInner)
: DOMEventTargetHelper(aGlobal), mInner(aInner) {
mInner->AddContainer(this);
Maybe<ServiceWorkerDescriptor> controller = aGlobal->GetController();
if (controller.isSome()) {
mControllerWorker = aGlobal->GetOrCreateServiceWorker(controller.ref());
}
}
ServiceWorkerContainer::~ServiceWorkerContainer() {
mInner->RemoveContainer(this);
}
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(NS_LITERAL_STRING("controllerchange"));
}
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.CopyFromClonedMessageDataForBackgroundChild(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(message.forget());
} else {
mPendingMessages.AppendElement(message.forget());
}
}
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,
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;
}
nsresult rv;
nsCOMPtr<nsIURI> scriptURI;
rv = NS_NewURI(getter_AddRefs(scriptURI), aScriptURL, nullptr, baseURI);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.ThrowTypeError<MSG_INVALID_URL>(aScriptURL);
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()) {
NS_NAMED_LITERAL_STRING(defaultScope, "./");
rv = NS_NewURI(getter_AddRefs(scopeURI), defaultScope, nullptr, scriptURI);
if (NS_WARN_IF(NS_FAILED(rv))) {
nsAutoCString spec;
scriptURI->GetSpec(spec);
NS_ConvertUTF8toUTF16 wSpec(spec);
aRv.ThrowTypeError<MSG_INVALID_SCOPE>(defaultScope, wSpec);
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);
NS_ConvertUTF8toUTF16 wSpec(spec);
aRv.ThrowTypeError<MSG_INVALID_SCOPE>(aOptions.mScope.Value(), wSpec);
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 = cloneWithoutRef.forget();
aRv = NS_GetURIWithoutRef(scopeURI, getter_AddRefs(cloneWithoutRef));
if (aRv.Failed()) {
return nullptr;
}
scopeURI = cloneWithoutRef.forget();
aRv = ServiceWorkerScopeAndScriptAreValid(clientInfo.ref(), scopeURI,
scriptURI);
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,
NS_LITERAL_CSTRING("application/javascript"),
&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,
NS_LITERAL_CSTRING("Service Workers"), 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;
mInner->Register(
clientInfo.ref(), cleanedScopeURL, cleanedScriptURL,
aOptions.mUpdateViaCache,
[self, outer](const ServiceWorkerRegistrationDescriptor& aDesc) {
ErrorResult rv;
nsIGlobalObject* global = self->GetGlobalIfValid(rv);
if (rv.Failed()) {
outer->MaybeReject(rv);
return;
}
RefPtr<ServiceWorkerRegistration> reg =
global->GetOrCreateServiceWorkerRegistration(aDesc);
outer->MaybeResolve(reg);
},
[outer](ErrorResult& aRv) { outer->MaybeReject(aRv); });
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,
NS_LITERAL_CSTRING("Service Workers"), 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;
mInner->GetRegistrations(
clientInfo.ref(),
[self,
outer](const nsTArray<ServiceWorkerRegistrationDescriptor>& aDescList) {
ErrorResult rv;
nsIGlobalObject* global = self->GetGlobalIfValid(rv);
if (rv.Failed()) {
outer->MaybeReject(rv);
return;
}
nsTArray<RefPtr<ServiceWorkerRegistration>> regList;
for (auto& desc : aDescList) {
RefPtr<ServiceWorkerRegistration> reg =
global->GetOrCreateServiceWorkerRegistration(desc);
if (reg) {
regList.AppendElement(std::move(reg));
}
}
outer->MaybeResolve(regList);
},
[self, outer](ErrorResult& aRv) { outer->MaybeReject(aRv); });
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,
NS_LITERAL_CSTRING("Service Workers"), 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;
mInner->GetRegistration(
clientInfo.ref(), spec,
[self, outer](const ServiceWorkerRegistrationDescriptor& aDescriptor) {
ErrorResult rv;
nsIGlobalObject* global = self->GetGlobalIfValid(rv);
if (rv.Failed()) {
outer->MaybeReject(rv);
return;
}
RefPtr<ServiceWorkerRegistration> reg =
global->GetOrCreateServiceWorkerRegistration(aDescriptor);
outer->MaybeResolve(reg);
},
[self, outer](ErrorResult& aRv) {
if (!aRv.Failed()) {
Unused << self->GetGlobalIfValid(aRv);
if (!aRv.Failed()) {
outer->MaybeResolveWithUndefined();
return;
}
}
outer->MaybeReject(aRv);
});
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;
mInner->GetReady(
clientInfo.ref(),
[self, outer](const ServiceWorkerRegistrationDescriptor& aDescriptor) {
ErrorResult rv;
nsIGlobalObject* global = self->GetGlobalIfValid(rv);
if (rv.Failed()) {
outer->MaybeReject(rv);
return;
}
RefPtr<ServiceWorkerRegistration> reg =
global->GetOrCreateServiceWorkerRegistration(aDescriptor);
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(
aDescriptor.Version(),
[outer, reg](bool aResult) { outer->MaybeResolve(reg); });
},
[self, outer](ErrorResult& aRv) { outer->MaybeReject(aRv); });
return mReadyPromise;
}
// Testing only.
void ServiceWorkerContainer::GetScopeForUrl(const nsAString& aUrl,
nsString& aScope,
ErrorResult& aRv) {
nsCOMPtr<nsIServiceWorkerManager> swm =
mozilla::services::GetServiceWorkerManager();
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<Document> doc = window->GetExtantDoc();
if (NS_WARN_IF(!doc)) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
aRv = swm->GetScopeForUrl(doc->NodePrincipal(), 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)) {
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(nsContentUtils::IsSystemPrincipal(doc->NodePrincipal()))) {
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr;
}
return window->AsGlobal();
}
void ServiceWorkerContainer::EnqueueReceivedMessageDispatch(
RefPtr<ReceivedMessage> aMessage) {
if (nsPIDOMWindowInner* const window = GetOwner()) {
if (auto* const target = window->EventTargetFor(TaskCategory::Other)) {
target->Dispatch(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);
if (!FillInMessageEventInit(aCx, aGlobal, *message, init, result)) {
deserializationFailed = result.ErrorCodeIs(NS_ERROR_DOM_DATA_CLONE_ERR);
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());
if (!deserializationFailed && result.MaybeSetPendingException(aCx)) {
return;
}
}
RefPtr<MessageEvent> event = MessageEvent::Constructor(
this,
deserializationFailed ? NS_LITERAL_STRING("messageerror")
: NS_LITERAL_STRING("message"),
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;
nsCOMPtr<nsIPrincipal> principal =
PrincipalInfoToPrincipal(aServiceWorker.PrincipalInfo(), &rv);
if (NS_FAILED(rv) || !principal) {
return rv;
}
nsAutoCString originUTF8;
rv = principal->GetOriginNoSuffix(originUTF8);
if (NS_FAILED(rv)) {
return rv;
}
CopyUTF8toUTF16(originUTF8, aOrigin);
return NS_OK;
}
already_AddRefed<ServiceWorker> GetOrCreateServiceWorkerWithoutWarnings(
nsIGlobalObject* const aGlobal,
const ServiceWorkerDescriptor& aDescriptor) {
// In child-intercept mode we have to verify that the registration
// exists in the current process. This exact check is also performed
// (indirectly) in nsIGlobalObject::GetOrCreateServiceWorker, but it
// also emits a warning when the registration is not present. To
// to avoid having too many warnings, we do a precheck here.
if (!ServiceWorkerParentInterceptEnabled()) {
const RefPtr<ServiceWorkerManager> serviceWorkerManager =
ServiceWorkerManager::GetInstance();
if (!serviceWorkerManager) {
return nullptr;
}
const RefPtr<ServiceWorkerRegistrationInfo> registration =
serviceWorkerManager->GetRegistration(aDescriptor.PrincipalInfo(),
aDescriptor.Scope());
if (!registration) {
return nullptr;
}
}
return aGlobal->GetOrCreateServiceWorker(aDescriptor).forget();
}
} // namespace
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 =
GetOrCreateServiceWorkerWithoutWarnings(aGlobal, aMessage.mServiceWorker);
if (serviceWorkerInstance) {
aInit.mSource.SetValue().SetAsServiceWorker() = serviceWorkerInstance;
}
const nsresult rv =
FillInOriginNoSuffix(aMessage.mServiceWorker, aInit.mOrigin);
if (NS_FAILED(rv)) {
return false;
}
JS::Rooted<JS::Value> messageData(aCx);
aMessage.mClonedData.Read(aCx, &messageData, aRv);
if (aRv.Failed()) {
return false;
}
aInit.mData = messageData;
if (!aMessage.mClonedData.TakeTransferredPortsAsSequence(aInit.mPorts)) {
xpc::Throw(aCx, NS_ERROR_OUT_OF_MEMORY);
return false;
}
return true;
}
} // namespace dom
} // namespace mozilla