gecko-dev/dom/serviceworkers/ServiceWorkerPrivateImpl.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

861 строка
25 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 "ServiceWorkerPrivateImpl.h"
#include <utility>
#include "MainThreadUtils.h"
#include "js/ErrorReport.h"
#include "nsContentUtils.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsICacheInfoChannel.h"
#include "nsIChannel.h"
#include "nsIHttpChannel.h"
#include "nsIInputStream.h"
#include "nsILoadInfo.h"
#include "nsINetworkInterceptController.h"
#include "nsIObserverService.h"
#include "nsIURI.h"
#include "nsThreadUtils.h"
#include "ServiceWorkerManager.h"
#include "ServiceWorkerRegistrationInfo.h"
#include "mozilla/Assertions.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/Maybe.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/SystemGroup.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/ClientIPCTypes.h"
#include "mozilla/dom/DOMTypes.h"
#include "mozilla/dom/FetchEventOpChild.h"
#include "mozilla/dom/InternalHeaders.h"
#include "mozilla/dom/ReferrerInfo.h"
#include "mozilla/dom/RemoteWorkerControllerChild.h"
#include "mozilla/dom/ServiceWorkerBinding.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/IPCStreamUtils.h"
namespace mozilla {
using namespace ipc;
namespace dom {
ServiceWorkerPrivateImpl::RAIIActorPtrHolder::RAIIActorPtrHolder(
already_AddRefed<RemoteWorkerControllerChild> aActor)
: mActor(aActor) {
AssertIsOnMainThread();
MOZ_ASSERT(mActor);
MOZ_ASSERT(mActor->Manager());
}
ServiceWorkerPrivateImpl::RAIIActorPtrHolder::~RAIIActorPtrHolder() {
AssertIsOnMainThread();
mDestructorPromiseHolder.ResolveIfExists(true, __func__);
mActor->MaybeSendDelete();
}
RemoteWorkerControllerChild*
ServiceWorkerPrivateImpl::RAIIActorPtrHolder::operator->() const {
AssertIsOnMainThread();
return get();
}
RemoteWorkerControllerChild* ServiceWorkerPrivateImpl::RAIIActorPtrHolder::get()
const {
AssertIsOnMainThread();
return mActor.get();
}
RefPtr<GenericPromise>
ServiceWorkerPrivateImpl::RAIIActorPtrHolder::OnDestructor() {
AssertIsOnMainThread();
return mDestructorPromiseHolder.Ensure(__func__);
}
ServiceWorkerPrivateImpl::ServiceWorkerPrivateImpl(
RefPtr<ServiceWorkerPrivate> aOuter)
: mOuter(std::move(aOuter)) {
AssertIsOnMainThread();
MOZ_ASSERT(mOuter);
MOZ_ASSERT(WorkerIsDead());
}
nsresult ServiceWorkerPrivateImpl::Initialize() {
AssertIsOnMainThread();
MOZ_ASSERT(mOuter);
MOZ_ASSERT(mOuter->mInfo);
nsCOMPtr<nsIPrincipal> principal = mOuter->mInfo->Principal();
nsCOMPtr<nsIURI> uri;
nsresult rv = principal->GetURI(getter_AddRefs(uri));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (NS_WARN_IF(!uri)) {
return NS_ERROR_FAILURE;
}
URIParams baseScriptURL;
SerializeURI(uri, baseScriptURL);
nsString id;
rv = mOuter->mInfo->GetId(id);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
PrincipalInfo principalInfo;
rv = PrincipalToPrincipalInfo(principal, &principalInfo);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
if (NS_WARN_IF(!swm)) {
return NS_ERROR_DOM_ABORT_ERR;
}
RefPtr<ServiceWorkerRegistrationInfo> regInfo =
swm->GetRegistration(principal, mOuter->mInfo->Scope());
if (NS_WARN_IF(!regInfo)) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
nsCOMPtr<nsICookieSettings> cookieSettings =
mozilla::net::CookieSettings::Create();
MOZ_ASSERT(cookieSettings);
StorageAccess storageAccess =
StorageAllowedForServiceWorker(principal, cookieSettings);
ServiceWorkerData serviceWorkerData;
serviceWorkerData.cacheName() = mOuter->mInfo->CacheName();
serviceWorkerData.loadFlags() =
static_cast<uint32_t>(mOuter->mInfo->GetImportsLoadFlags() |
nsIChannel::LOAD_BYPASS_SERVICE_WORKER);
serviceWorkerData.id() = std::move(id);
mRemoteWorkerData.originalScriptURL() =
NS_ConvertUTF8toUTF16(mOuter->mInfo->ScriptSpec());
mRemoteWorkerData.baseScriptURL() = baseScriptURL;
mRemoteWorkerData.resolvedScriptURL() = baseScriptURL;
mRemoteWorkerData.name() = VoidString();
mRemoteWorkerData.loadingPrincipalInfo() = principalInfo;
mRemoteWorkerData.principalInfo() = principalInfo;
// storagePrincipalInfo for ServiceWorkers is equal to principalInfo because,
// at the moment, ServiceWorkers are not exposed in partitioned contexts.
mRemoteWorkerData.storagePrincipalInfo() = principalInfo;
rv = uri->GetHost(mRemoteWorkerData.domain());
NS_ENSURE_SUCCESS(rv, rv);
mRemoteWorkerData.isSecureContext() = true;
mRemoteWorkerData.referrerInfo() = MakeAndAddRef<ReferrerInfo>();
mRemoteWorkerData.storageAccess() = storageAccess;
mRemoteWorkerData.serviceWorkerData() = std::move(serviceWorkerData);
mRemoteWorkerData.agentClusterId() = regInfo->AgentClusterId();
// This fills in the rest of mRemoteWorkerData.serviceWorkerData().
Bug 1575185 - Subscribe content processes spawning Service Workers to permission updates r=asuth Previously, Service Workers could spawn in a process that isn't subscribed to permission updates, which could happen if that process hadn't loaded any same-origin documents. To address this, parent-process logic for spawning Service Workers would snapshot the permissions state to be sent to a content process. Unfortunately, this approach could lead to outdated, unsynchronized permissions. Note that nsIPermissionManager::SetPermissionsWithKey is only used to initialize permissions for a given key and is a no-op if already called with the same key in a given process. As a result, the following sequence of events could happen: Assume a content process CP that isn't subscribed to permission changes for an origin A: 1) Parent process decides to spawn an origin A Service Worker in CP, snapshotting a value V for permission P. 2) The Service Worker is spawned in CP, setting CP's permission manager's permission P to value V (for origin A). 3) Parent process updates its permission P to a value A', which is not broadcasted to CP (because it's not subscribed). 4) By now, the initial Service Worker has been terminated, and the parent process decides once again to spawn an origin A Service Worker in CP. 5) The Service Worker is spawned in CP, but the call to SetPermissionsWithKey is a no-op, leaving CP1 with a mismatched value for permission P. An additional scenario is if the parent process updates a permission during a remote Service Worker's lifetime. This patch, which would subscribe CP1 to permission updates when the parent process knows a Service Worker would be spawned in CP1, prevents these problems. Differential Revision: https://phabricator.services.mozilla.com/D48620 --HG-- extra : moz-landing-system : lando
2019-10-09 05:23:41 +03:00
RefreshRemoteWorkerData(regInfo);
return NS_OK;
}
RefPtr<GenericPromise> ServiceWorkerPrivateImpl::SetSkipWaitingFlag() {
AssertIsOnMainThread();
MOZ_ASSERT(mOuter);
MOZ_ASSERT(mOuter->mInfo);
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
if (!swm) {
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
}
RefPtr<ServiceWorkerRegistrationInfo> regInfo =
swm->GetRegistration(mOuter->mInfo->Principal(), mOuter->mInfo->Scope());
if (!regInfo) {
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
}
mOuter->mInfo->SetSkipWaitingFlag();
RefPtr<GenericPromise::Private> promise =
new GenericPromise::Private(__func__);
regInfo->TryToActivateAsync([promise] { promise->Resolve(true, __func__); });
return promise;
}
Bug 1575185 - Subscribe content processes spawning Service Workers to permission updates r=asuth Previously, Service Workers could spawn in a process that isn't subscribed to permission updates, which could happen if that process hadn't loaded any same-origin documents. To address this, parent-process logic for spawning Service Workers would snapshot the permissions state to be sent to a content process. Unfortunately, this approach could lead to outdated, unsynchronized permissions. Note that nsIPermissionManager::SetPermissionsWithKey is only used to initialize permissions for a given key and is a no-op if already called with the same key in a given process. As a result, the following sequence of events could happen: Assume a content process CP that isn't subscribed to permission changes for an origin A: 1) Parent process decides to spawn an origin A Service Worker in CP, snapshotting a value V for permission P. 2) The Service Worker is spawned in CP, setting CP's permission manager's permission P to value V (for origin A). 3) Parent process updates its permission P to a value A', which is not broadcasted to CP (because it's not subscribed). 4) By now, the initial Service Worker has been terminated, and the parent process decides once again to spawn an origin A Service Worker in CP. 5) The Service Worker is spawned in CP, but the call to SetPermissionsWithKey is a no-op, leaving CP1 with a mismatched value for permission P. An additional scenario is if the parent process updates a permission during a remote Service Worker's lifetime. This patch, which would subscribe CP1 to permission updates when the parent process knows a Service Worker would be spawned in CP1, prevents these problems. Differential Revision: https://phabricator.services.mozilla.com/D48620 --HG-- extra : moz-landing-system : lando
2019-10-09 05:23:41 +03:00
void ServiceWorkerPrivateImpl::RefreshRemoteWorkerData(
const RefPtr<ServiceWorkerRegistrationInfo>& aRegistration) {
AssertIsOnMainThread();
MOZ_ASSERT(mOuter);
MOZ_ASSERT(mOuter->mInfo);
ServiceWorkerData& serviceWorkerData =
mRemoteWorkerData.serviceWorkerData().get_ServiceWorkerData();
serviceWorkerData.descriptor() = mOuter->mInfo->Descriptor().ToIPC();
serviceWorkerData.registrationDescriptor() =
aRegistration->Descriptor().ToIPC();
}
nsresult ServiceWorkerPrivateImpl::SpawnWorkerIfNeeded() {
AssertIsOnMainThread();
MOZ_ASSERT(mOuter);
MOZ_ASSERT(mOuter->mInfo);
if (mControllerChild) {
return NS_OK;
}
PBackgroundChild* bgChild = BackgroundChild::GetForCurrentThread();
if (NS_WARN_IF(!bgChild)) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
if (NS_WARN_IF(!swm)) {
return NS_ERROR_DOM_ABORT_ERR;
}
RefPtr<ServiceWorkerRegistrationInfo> regInfo =
swm->GetRegistration(mOuter->mInfo->Principal(), mOuter->mInfo->Scope());
if (NS_WARN_IF(!regInfo)) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
Bug 1575185 - Subscribe content processes spawning Service Workers to permission updates r=asuth Previously, Service Workers could spawn in a process that isn't subscribed to permission updates, which could happen if that process hadn't loaded any same-origin documents. To address this, parent-process logic for spawning Service Workers would snapshot the permissions state to be sent to a content process. Unfortunately, this approach could lead to outdated, unsynchronized permissions. Note that nsIPermissionManager::SetPermissionsWithKey is only used to initialize permissions for a given key and is a no-op if already called with the same key in a given process. As a result, the following sequence of events could happen: Assume a content process CP that isn't subscribed to permission changes for an origin A: 1) Parent process decides to spawn an origin A Service Worker in CP, snapshotting a value V for permission P. 2) The Service Worker is spawned in CP, setting CP's permission manager's permission P to value V (for origin A). 3) Parent process updates its permission P to a value A', which is not broadcasted to CP (because it's not subscribed). 4) By now, the initial Service Worker has been terminated, and the parent process decides once again to spawn an origin A Service Worker in CP. 5) The Service Worker is spawned in CP, but the call to SetPermissionsWithKey is a no-op, leaving CP1 with a mismatched value for permission P. An additional scenario is if the parent process updates a permission during a remote Service Worker's lifetime. This patch, which would subscribe CP1 to permission updates when the parent process knows a Service Worker would be spawned in CP1, prevents these problems. Differential Revision: https://phabricator.services.mozilla.com/D48620 --HG-- extra : moz-landing-system : lando
2019-10-09 05:23:41 +03:00
RefreshRemoteWorkerData(regInfo);
RefPtr<RemoteWorkerControllerChild> controllerChild =
new RemoteWorkerControllerChild(this);
if (NS_WARN_IF(!bgChild->SendPRemoteWorkerControllerConstructor(
controllerChild, mRemoteWorkerData))) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
/**
* Manutally `AddRef()` because `DeallocPRemoteWorkerControllerChild()`
* calls `Release()` and the `AllocPRemoteWorkerControllerChild()` function
* is not called.
*/
// NOLINTNEXTLINE(readability-redundant-smartptr-get)
controllerChild.get()->AddRef();
mControllerChild = new RAIIActorPtrHolder(controllerChild.forget());
return NS_OK;
}
ServiceWorkerPrivateImpl::~ServiceWorkerPrivateImpl() {
AssertIsOnMainThread();
MOZ_ASSERT(!mOuter);
MOZ_ASSERT(WorkerIsDead());
}
nsresult ServiceWorkerPrivateImpl::SendMessageEvent(
RefPtr<ServiceWorkerCloneData>&& aData,
const ClientInfoAndState& aClientInfoAndState) {
AssertIsOnMainThread();
MOZ_ASSERT(mOuter);
MOZ_ASSERT(aData);
auto scopeExit = MakeScopeExit([&] { Shutdown(); });
PBackgroundChild* bgChild = BackgroundChild::GetForCurrentThread();
if (NS_WARN_IF(!bgChild)) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
ServiceWorkerMessageEventOpArgs args;
args.clientInfoAndState() = aClientInfoAndState;
if (!aData->BuildClonedMessageDataForBackgroundChild(bgChild,
args.clonedData())) {
return NS_ERROR_DOM_DATA_CLONE_ERR;
}
scopeExit.release();
return ExecServiceWorkerOp(
std::move(args), [](ServiceWorkerOpResult&& aResult) {
MOZ_ASSERT(aResult.type() == ServiceWorkerOpResult::Tnsresult);
});
}
nsresult ServiceWorkerPrivateImpl::CheckScriptEvaluation(
RefPtr<LifeCycleEventCallback> aCallback) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mOuter);
MOZ_ASSERT(aCallback);
RefPtr<ServiceWorkerPrivateImpl> self = this;
/**
* We need to capture the actor associated with the current Service Worker so
* we can terminate it if script evaluation failed.
*/
nsresult rv = SpawnWorkerIfNeeded();
if (NS_WARN_IF(NS_FAILED(rv))) {
aCallback->SetResult(false);
aCallback->Run();
return rv;
}
MOZ_ASSERT(mControllerChild);
RefPtr<RAIIActorPtrHolder> holder = mControllerChild;
return ExecServiceWorkerOp(
ServiceWorkerCheckScriptEvaluationOpArgs(),
[self = std::move(self), holder = std::move(holder),
callback = aCallback](ServiceWorkerOpResult&& aResult) mutable {
if (aResult.type() == ServiceWorkerOpResult::
TServiceWorkerCheckScriptEvaluationOpResult) {
auto& result =
aResult.get_ServiceWorkerCheckScriptEvaluationOpResult();
if (result.workerScriptExecutedSuccessfully()) {
if (self->mOuter) {
self->mOuter->SetHandlesFetch(result.fetchHandlerWasAdded());
}
Unused << NS_WARN_IF(!self->mOuter);
callback->SetResult(result.workerScriptExecutedSuccessfully());
callback->Run();
return;
}
}
/**
* If script evaluation failed, first terminate the Service Worker
* before invoking the callback.
*/
MOZ_ASSERT_IF(aResult.type() == ServiceWorkerOpResult::Tnsresult,
NS_FAILED(aResult.get_nsresult()));
// If a termination operation was already issued using `holder`...
if (self->mControllerChild != holder) {
holder->OnDestructor()->Then(
GetCurrentThreadSerialEventTarget(), __func__,
[callback = std::move(callback)](
const GenericPromise::ResolveOrRejectValue&) {
callback->SetResult(false);
callback->Run();
});
return;
}
RefPtr<GenericNonExclusivePromise> promise = self->ShutdownInternal();
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
MOZ_ASSERT(swm);
swm->BlockShutdownOn(promise);
promise->Then(
GetCurrentThreadSerialEventTarget(), __func__,
[callback = std::move(callback)](
const GenericNonExclusivePromise::ResolveOrRejectValue&) {
callback->SetResult(false);
callback->Run();
});
},
[callback = aCallback] {
callback->SetResult(false);
callback->Run();
});
}
nsresult ServiceWorkerPrivateImpl::SendLifeCycleEvent(
const nsAString& aEventName, RefPtr<LifeCycleEventCallback> aCallback) {
AssertIsOnMainThread();
MOZ_ASSERT(mOuter);
MOZ_ASSERT(aCallback);
return ExecServiceWorkerOp(
ServiceWorkerLifeCycleEventOpArgs(nsString(aEventName)),
[callback = aCallback](ServiceWorkerOpResult&& aResult) {
MOZ_ASSERT(aResult.type() == ServiceWorkerOpResult::Tnsresult);
callback->SetResult(NS_SUCCEEDED(aResult.get_nsresult()));
callback->Run();
},
[callback = aCallback] {
callback->SetResult(false);
callback->Run();
});
}
nsresult ServiceWorkerPrivateImpl::SendPushEvent(
RefPtr<ServiceWorkerRegistrationInfo> aRegistration,
const nsAString& aMessageId, const Maybe<nsTArray<uint8_t>>& aData) {
AssertIsOnMainThread();
MOZ_ASSERT(mOuter);
MOZ_ASSERT(aRegistration);
ServiceWorkerPushEventOpArgs args;
args.messageId() = nsString(aMessageId);
if (aData) {
args.data() = aData.ref();
} else {
args.data() = void_t();
}
if (mOuter->mInfo->State() == ServiceWorkerState::Activating) {
UniquePtr<PendingFunctionalEvent> pendingEvent =
MakeUnique<PendingPushEvent>(this, std::move(aRegistration),
std::move(args));
mPendingFunctionalEvents.AppendElement(std::move(pendingEvent));
return NS_OK;
}
MOZ_ASSERT(mOuter->mInfo->State() == ServiceWorkerState::Activated);
return SendPushEventInternal(std::move(aRegistration), std::move(args));
}
nsresult ServiceWorkerPrivateImpl::SendPushEventInternal(
RefPtr<ServiceWorkerRegistrationInfo>&& aRegistration,
ServiceWorkerPushEventOpArgs&& aArgs) {
AssertIsOnMainThread();
MOZ_ASSERT(mOuter);
MOZ_ASSERT(aRegistration);
return ExecServiceWorkerOp(
std::move(aArgs),
[registration = aRegistration](ServiceWorkerOpResult&& aResult) {
MOZ_ASSERT(aResult.type() == ServiceWorkerOpResult::Tnsresult);
registration->MaybeScheduleTimeCheckAndUpdate();
},
[registration = aRegistration]() {
registration->MaybeScheduleTimeCheckAndUpdate();
});
}
nsresult ServiceWorkerPrivateImpl::SendPushSubscriptionChangeEvent() {
AssertIsOnMainThread();
MOZ_ASSERT(mOuter);
return ExecServiceWorkerOp(
ServiceWorkerPushSubscriptionChangeEventOpArgs(),
[](ServiceWorkerOpResult&& aResult) {
MOZ_ASSERT(aResult.type() == ServiceWorkerOpResult::Tnsresult);
});
}
nsresult ServiceWorkerPrivateImpl::SendNotificationEvent(
const nsAString& aEventName, const nsAString& aID, const nsAString& aTitle,
const nsAString& aDir, const nsAString& aLang, const nsAString& aBody,
const nsAString& aTag, const nsAString& aIcon, const nsAString& aData,
const nsAString& aBehavior, const nsAString& aScope,
uint32_t aDisableOpenClickDelay) {
AssertIsOnMainThread();
MOZ_ASSERT(mOuter);
ServiceWorkerNotificationEventOpArgs args;
args.eventName() = nsString(aEventName);
args.id() = nsString(aID);
args.title() = nsString(aTitle);
args.dir() = nsString(aDir);
args.lang() = nsString(aLang);
args.body() = nsString(aBody);
args.tag() = nsString(aTag);
args.icon() = nsString(aIcon);
args.data() = nsString(aData);
args.behavior() = nsString(aBehavior);
args.scope() = nsString(aScope);
args.disableOpenClickDelay() = aDisableOpenClickDelay;
return ExecServiceWorkerOp(
std::move(args), [](ServiceWorkerOpResult&& aResult) {
MOZ_ASSERT(aResult.type() == ServiceWorkerOpResult::Tnsresult);
});
}
ServiceWorkerPrivateImpl::PendingFunctionalEvent::PendingFunctionalEvent(
ServiceWorkerPrivateImpl* aOwner,
RefPtr<ServiceWorkerRegistrationInfo>&& aRegistration)
: mOwner(aOwner), mRegistration(std::move(aRegistration)) {
AssertIsOnMainThread();
MOZ_ASSERT(mOwner);
MOZ_ASSERT(mOwner->mOuter);
MOZ_ASSERT(mOwner->mOuter->mInfo);
MOZ_ASSERT(mOwner->mOuter->mInfo->State() == ServiceWorkerState::Activating);
MOZ_ASSERT(mRegistration);
}
ServiceWorkerPrivateImpl::PendingFunctionalEvent::~PendingFunctionalEvent() {
AssertIsOnMainThread();
}
ServiceWorkerPrivateImpl::PendingPushEvent::PendingPushEvent(
ServiceWorkerPrivateImpl* aOwner,
RefPtr<ServiceWorkerRegistrationInfo>&& aRegistration,
ServiceWorkerPushEventOpArgs&& aArgs)
: PendingFunctionalEvent(aOwner, std::move(aRegistration)),
mArgs(std::move(aArgs)) {
AssertIsOnMainThread();
}
nsresult ServiceWorkerPrivateImpl::PendingPushEvent::Send() {
AssertIsOnMainThread();
MOZ_ASSERT(mOwner->mOuter);
MOZ_ASSERT(mOwner->mOuter->mInfo);
return mOwner->SendPushEventInternal(std::move(mRegistration),
std::move(mArgs));
}
ServiceWorkerPrivateImpl::PendingFetchEvent::PendingFetchEvent(
ServiceWorkerPrivateImpl* aOwner,
RefPtr<ServiceWorkerRegistrationInfo>&& aRegistration,
ServiceWorkerFetchEventOpArgs&& aArgs,
nsCOMPtr<nsIInterceptedChannel>&& aChannel)
: PendingFunctionalEvent(aOwner, std::move(aRegistration)),
mArgs(std::move(aArgs)),
mChannel(std::move(aChannel)) {
AssertIsOnMainThread();
MOZ_ASSERT(mChannel);
}
nsresult ServiceWorkerPrivateImpl::PendingFetchEvent::Send() {
AssertIsOnMainThread();
MOZ_ASSERT(mOwner->mOuter);
MOZ_ASSERT(mOwner->mOuter->mInfo);
return mOwner->SendFetchEventInternal(std::move(mRegistration),
std::move(mArgs), std::move(mChannel));
}
ServiceWorkerPrivateImpl::PendingFetchEvent::~PendingFetchEvent() {
AssertIsOnMainThread();
if (NS_WARN_IF(mChannel)) {
mChannel->CancelInterception(NS_ERROR_INTERCEPTION_FAILED);
}
}
nsresult ServiceWorkerPrivateImpl::SendFetchEvent(
RefPtr<ServiceWorkerRegistrationInfo> aRegistration,
nsCOMPtr<nsIInterceptedChannel> aChannel, const nsAString& aClientId,
const nsAString& aResultingClientId, bool aIsReload) {
AssertIsOnMainThread();
MOZ_ASSERT(mOuter);
MOZ_ASSERT(aRegistration);
MOZ_ASSERT(aChannel);
auto scopeExit = MakeScopeExit([&] {
aChannel->CancelInterception(NS_ERROR_INTERCEPTION_FAILED);
Shutdown();
});
nsCOMPtr<nsIChannel> channel;
nsresult rv = aChannel->GetChannel(getter_AddRefs(channel));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
scopeExit.release();
MOZ_ASSERT(mOuter->mInfo);
// FetchEventOpChild will fill in the IPCInternalRequest.
ServiceWorkerFetchEventOpArgs args(
mOuter->mInfo->ScriptSpec(), IPCInternalRequest(), nsString(aClientId),
nsString(aResultingClientId), aIsReload,
nsContentUtils::IsNonSubresourceRequest(channel));
if (mOuter->mInfo->State() == ServiceWorkerState::Activating) {
UniquePtr<PendingFunctionalEvent> pendingEvent =
MakeUnique<PendingFetchEvent>(this, std::move(aRegistration),
std::move(args), std::move(aChannel));
mPendingFunctionalEvents.AppendElement(std::move(pendingEvent));
return NS_OK;
}
MOZ_ASSERT(mOuter->mInfo->State() == ServiceWorkerState::Activated);
return SendFetchEventInternal(std::move(aRegistration), std::move(args),
std::move(aChannel));
}
nsresult ServiceWorkerPrivateImpl::SendFetchEventInternal(
RefPtr<ServiceWorkerRegistrationInfo>&& aRegistration,
ServiceWorkerFetchEventOpArgs&& aArgs,
nsCOMPtr<nsIInterceptedChannel>&& aChannel) {
AssertIsOnMainThread();
MOZ_ASSERT(mOuter);
auto scopeExit = MakeScopeExit([&] { Shutdown(); });
if (NS_WARN_IF(!mOuter->mInfo)) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
nsresult rv = SpawnWorkerIfNeeded();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
scopeExit.release();
MOZ_ASSERT(mControllerChild);
RefPtr<RAIIActorPtrHolder> holder = mControllerChild;
FetchEventOpChild::SendFetchEvent(
mControllerChild->get(), std::move(aArgs), std::move(aChannel),
std::move(aRegistration), mOuter->CreateEventKeepAliveToken())
->Then(GetCurrentThreadSerialEventTarget(), __func__,
[holder = std::move(holder)](
const GenericPromise::ResolveOrRejectValue& aResult) {
Unused << NS_WARN_IF(aResult.IsReject());
});
return NS_OK;
}
void ServiceWorkerPrivateImpl::TerminateWorker() {
AssertIsOnMainThread();
MOZ_ASSERT(mOuter);
mOuter->mIdleWorkerTimer->Cancel();
mOuter->mIdleKeepAliveToken = nullptr;
Shutdown();
}
void ServiceWorkerPrivateImpl::Shutdown() {
AssertIsOnMainThread();
if (!WorkerIsDead()) {
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
MOZ_ASSERT(swm,
"All Service Workers should start shutting down before the "
"ServiceWorkerManager does!");
RefPtr<GenericNonExclusivePromise> promise = ShutdownInternal();
swm->BlockShutdownOn(promise);
}
MOZ_ASSERT(WorkerIsDead());
}
RefPtr<GenericNonExclusivePromise>
ServiceWorkerPrivateImpl::ShutdownInternal() {
AssertIsOnMainThread();
MOZ_ASSERT(mControllerChild);
mPendingFunctionalEvents.Clear();
mControllerChild->get()->RevokeObserver(this);
if (StaticPrefs::dom_serviceWorkers_testing_enabled()) {
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
if (os) {
os->NotifyObservers(nullptr, "service-worker-shutdown", nullptr);
}
}
RefPtr<GenericNonExclusivePromise::Private> promise =
new GenericNonExclusivePromise::Private(__func__);
Unused << ExecServiceWorkerOp(
ServiceWorkerTerminateWorkerOpArgs(),
[promise](ServiceWorkerOpResult&& aResult) {
MOZ_ASSERT(aResult.type() == ServiceWorkerOpResult::Tnsresult);
promise->Resolve(true, __func__);
},
[promise]() { promise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__); });
/**
* After dispatching a termination operation, no new operations should
* be routed through this actor anymore.
*/
mControllerChild = nullptr;
return promise;
}
void ServiceWorkerPrivateImpl::UpdateState(ServiceWorkerState aState) {
AssertIsOnMainThread();
MOZ_ASSERT(mOuter);
if (WorkerIsDead()) {
return;
}
nsresult rv = ExecServiceWorkerOp(
ServiceWorkerUpdateStateOpArgs(aState),
[](ServiceWorkerOpResult&& aResult) {
MOZ_ASSERT(aResult.type() == ServiceWorkerOpResult::Tnsresult);
});
if (NS_WARN_IF(NS_FAILED(rv))) {
Shutdown();
return;
}
if (aState != ServiceWorkerState::Activated) {
return;
}
for (auto& event : mPendingFunctionalEvents) {
Unused << NS_WARN_IF(NS_FAILED(event->Send()));
}
mPendingFunctionalEvents.Clear();
}
void ServiceWorkerPrivateImpl::NoteDeadOuter() {
AssertIsOnMainThread();
MOZ_ASSERT(mOuter);
Shutdown();
mOuter = nullptr;
}
void ServiceWorkerPrivateImpl::CreationFailed() {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mOuter);
MOZ_ASSERT(mControllerChild);
Shutdown();
}
void ServiceWorkerPrivateImpl::CreationSucceeded() {
AssertIsOnMainThread();
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mOuter);
MOZ_ASSERT(mControllerChild);
mOuter->RenewKeepAliveToken(ServiceWorkerPrivate::WakeUpReason::Unknown);
}
void ServiceWorkerPrivateImpl::ErrorReceived(const ErrorValue& aError) {
AssertIsOnMainThread();
MOZ_ASSERT(mOuter);
MOZ_ASSERT(mOuter->mInfo);
MOZ_ASSERT(mControllerChild);
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
MOZ_ASSERT(swm);
ServiceWorkerInfo* info = mOuter->mInfo;
swm->HandleError(nullptr, info->Principal(), info->Scope(),
NS_ConvertUTF8toUTF16(info->ScriptSpec()), EmptyString(),
EmptyString(), EmptyString(), 0, 0, JSREPORT_ERROR,
JSEXN_ERR);
}
void ServiceWorkerPrivateImpl::Terminated() {
AssertIsOnMainThread();
MOZ_ASSERT(mOuter);
MOZ_ASSERT(mControllerChild);
Shutdown();
}
bool ServiceWorkerPrivateImpl::WorkerIsDead() const {
AssertIsOnMainThread();
return !mControllerChild;
}
nsresult ServiceWorkerPrivateImpl::ExecServiceWorkerOp(
ServiceWorkerOpArgs&& aArgs,
std::function<void(ServiceWorkerOpResult&&)>&& aSuccessCallback,
std::function<void()>&& aFailureCallback) {
AssertIsOnMainThread();
MOZ_ASSERT(mOuter);
MOZ_ASSERT(
aArgs.type() != ServiceWorkerOpArgs::TServiceWorkerFetchEventOpArgs,
"FetchEvent operations should be sent through FetchEventOp(Proxy) "
"actors!");
MOZ_ASSERT(aSuccessCallback);
nsresult rv = SpawnWorkerIfNeeded();
if (NS_WARN_IF(NS_FAILED(rv))) {
aFailureCallback();
return rv;
}
MOZ_ASSERT(mControllerChild);
RefPtr<ServiceWorkerPrivateImpl> self = this;
RefPtr<RAIIActorPtrHolder> holder = mControllerChild;
RefPtr<KeepAliveToken> token =
aArgs.type() == ServiceWorkerOpArgs::TServiceWorkerTerminateWorkerOpArgs
? nullptr
: mOuter->CreateEventKeepAliveToken();
/**
* NOTE: moving `aArgs` won't do anything until IPDL `SendMethod()` methods
* can accept rvalue references rather than just const references.
*/
mControllerChild->get()->SendExecServiceWorkerOp(aArgs)->Then(
GetCurrentThreadSerialEventTarget(), __func__,
[self = std::move(self), holder = std::move(holder),
token = std::move(token), onSuccess = std::move(aSuccessCallback),
onFailure = std::move(aFailureCallback)](
PRemoteWorkerControllerChild::ExecServiceWorkerOpPromise::
ResolveOrRejectValue&& aResult) {
if (NS_WARN_IF(aResult.IsReject())) {
onFailure();
return;
}
onSuccess(std::move(aResult.ResolveValue()));
});
return NS_OK;
}
} // namespace dom
} // namespace mozilla