/* -*- 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 #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 "nsIHttpChannelInternal.h" #include "nsIHttpHeaderVisitor.h" #include "nsIInputStream.h" #include "nsILoadInfo.h" #include "nsINetworkInterceptController.h" #include "nsIObserverService.h" #include "nsIPermissionManager.h" #include "nsIURI.h" #include "nsIUploadChannel2.h" #include "nsPermissionManager.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/InternalRequest.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 { namespace { class HeaderFiller final : public nsIHttpHeaderVisitor { public: NS_DECL_ISUPPORTS explicit HeaderFiller(HeadersGuardEnum aGuard) : mInternalHeaders(new InternalHeaders(aGuard)) { MOZ_ASSERT(mInternalHeaders); } NS_IMETHOD VisitHeader(const nsACString& aHeader, const nsACString& aValue) override { ErrorResult result; mInternalHeaders->Append(aHeader, aValue, result); if (NS_WARN_IF(result.Failed())) { return result.StealNSResult(); } return NS_OK; } RefPtr Extract() { return RefPtr(std::move(mInternalHeaders)); } private: ~HeaderFiller() = default; RefPtr mInternalHeaders; }; NS_IMPL_ISUPPORTS(HeaderFiller, nsIHttpHeaderVisitor) nsresult GetIPCInternalRequest(nsIInterceptedChannel* aChannel, IPCInternalRequest* aOutRequest, UniquePtr& aAutoStream) { AssertIsOnMainThread(); nsCOMPtr uri; nsresult rv = aChannel->GetSecureUpgradedChannelURI(getter_AddRefs(uri)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr uriNoFragment; rv = NS_GetURIWithoutRef(uri, getter_AddRefs(uriNoFragment)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr underlyingChannel; rv = aChannel->GetChannel(getter_AddRefs(underlyingChannel)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr httpChannel = do_QueryInterface(underlyingChannel); MOZ_ASSERT(httpChannel, "How come we don't have an HTTP channel?"); nsCOMPtr internalChannel = do_QueryInterface(httpChannel); NS_ENSURE_TRUE(internalChannel, NS_ERROR_NOT_AVAILABLE); nsCOMPtr cacheInfoChannel = do_QueryInterface(underlyingChannel); nsAutoCString spec; rv = uriNoFragment->GetSpec(spec); NS_ENSURE_SUCCESS(rv, rv); nsAutoCString fragment; rv = uri->GetRef(fragment); NS_ENSURE_SUCCESS(rv, rv); nsAutoCString method; rv = httpChannel->GetRequestMethod(method); NS_ENSURE_SUCCESS(rv, rv); // This is safe due to static_asserts in ServiceWorkerManager.cpp uint32_t cacheModeInt; rv = internalChannel->GetFetchCacheMode(&cacheModeInt); MOZ_ASSERT(NS_SUCCEEDED(rv)); RequestCache cacheMode = static_cast(cacheModeInt); RequestMode requestMode = InternalRequest::MapChannelToRequestMode(underlyingChannel); // This is safe due to static_asserts in ServiceWorkerManager.cpp uint32_t redirectMode; rv = internalChannel->GetRedirectMode(&redirectMode); MOZ_ASSERT(NS_SUCCEEDED(rv)); RequestRedirect requestRedirect = static_cast(redirectMode); RequestCredentials requestCredentials = InternalRequest::MapChannelToRequestCredentials(underlyingChannel); nsAutoString referrer; ReferrerPolicy referrerPolicy = ReferrerPolicy::_empty; nsCOMPtr referrerInfo = httpChannel->GetReferrerInfo(); if (referrerInfo) { referrerPolicy = referrerInfo->ReferrerPolicy(); Unused << referrerInfo->GetComputedReferrerSpec(referrer); } uint32_t loadFlags; rv = underlyingChannel->GetLoadFlags(&loadFlags); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr loadInfo; rv = underlyingChannel->GetLoadInfo(getter_AddRefs(loadInfo)); NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_STATE(loadInfo); nsContentPolicyType contentPolicyType = loadInfo->InternalContentPolicyType(); nsAutoString integrity; rv = internalChannel->GetIntegrityMetadata(integrity); NS_ENSURE_SUCCESS(rv, rv); RefPtr headerFiller = MakeRefPtr(HeadersGuardEnum::Request); rv = httpChannel->VisitNonDefaultRequestHeaders(headerFiller); NS_ENSURE_SUCCESS(rv, rv); RefPtr internalHeaders = headerFiller->Extract(); ErrorResult result; internalHeaders->SetGuard(HeadersGuardEnum::Immutable, result); if (NS_WARN_IF(result.Failed())) { return result.StealNSResult(); } nsCOMPtr uploadChannel = do_QueryInterface(httpChannel); nsCOMPtr uploadStream; int64_t uploadStreamContentLength = -1; if (uploadChannel) { rv = uploadChannel->CloneUploadStream(&uploadStreamContentLength, getter_AddRefs(uploadStream)); NS_ENSURE_SUCCESS(rv, rv); } RefPtr internalRequest = new InternalRequest( spec, fragment, method, internalHeaders.forget(), cacheMode, requestMode, requestRedirect, requestCredentials, referrer, referrerPolicy, contentPolicyType, integrity); internalRequest->SetBody(uploadStream, uploadStreamContentLength); internalRequest->SetCreatedByFetchEvent(); nsAutoCString alternativeDataType; if (cacheInfoChannel && !cacheInfoChannel->PreferredAlternativeDataTypes().IsEmpty()) { // TODO: the internal request probably needs all the preferred types. alternativeDataType.Assign( cacheInfoChannel->PreferredAlternativeDataTypes()[0].type()); internalRequest->SetPreferredAlternativeDataType(alternativeDataType); } PBackgroundChild* bgChild = BackgroundChild::GetForCurrentThread(); if (NS_WARN_IF(!bgChild)) { return NS_ERROR_DOM_INVALID_STATE_ERR; } internalRequest->ToIPC(aOutRequest, bgChild, aAutoStream); return NS_OK; } } // anonymous namespace ServiceWorkerPrivateImpl::RAIIActorPtrHolder::RAIIActorPtrHolder( already_AddRefed 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 ServiceWorkerPrivateImpl::RAIIActorPtrHolder::OnDestructor() { AssertIsOnMainThread(); return mDestructorPromiseHolder.Ensure(__func__); } ServiceWorkerPrivateImpl::ServiceWorkerPrivateImpl( RefPtr aOuter) : mOuter(std::move(aOuter)) { AssertIsOnMainThread(); MOZ_ASSERT(mOuter); MOZ_ASSERT(WorkerIsDead()); } nsresult ServiceWorkerPrivateImpl::Initialize() { AssertIsOnMainThread(); MOZ_ASSERT(mOuter); MOZ_ASSERT(mOuter->mInfo); nsCOMPtr principal = mOuter->mInfo->Principal(); nsCOMPtr 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 swm = ServiceWorkerManager::GetInstance(); if (NS_WARN_IF(!swm)) { return NS_ERROR_DOM_ABORT_ERR; } RefPtr regInfo = swm->GetRegistration(principal, mOuter->mInfo->Scope()); if (NS_WARN_IF(!regInfo)) { return NS_ERROR_DOM_INVALID_STATE_ERR; } nsCOMPtr cookieSettings = mozilla::net::CookieSettings::Create(); MOZ_ASSERT(cookieSettings); StorageAccess storageAccess = StorageAllowedForServiceWorker(principal, cookieSettings); ServiceWorkerData serviceWorkerData; serviceWorkerData.cacheName() = mOuter->mInfo->CacheName(); serviceWorkerData.loadFlags() = static_cast(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(); mRemoteWorkerData.storageAccess() = storageAccess; mRemoteWorkerData.serviceWorkerData() = std::move(serviceWorkerData); mRemoteWorkerData.agentClusterId() = regInfo->AgentClusterId(); // This fills in the rest of mRemoteWorkerData.serviceWorkerData(). rv = RefreshRemoteWorkerData(regInfo); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } RefPtr ServiceWorkerPrivateImpl::SetSkipWaitingFlag() { AssertIsOnMainThread(); MOZ_ASSERT(mOuter); MOZ_ASSERT(mOuter->mInfo); RefPtr swm = ServiceWorkerManager::GetInstance(); if (!swm) { return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); } RefPtr regInfo = swm->GetRegistration(mOuter->mInfo->Principal(), mOuter->mInfo->Scope()); if (!regInfo) { return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); } mOuter->mInfo->SetSkipWaitingFlag(); RefPtr promise = new GenericPromise::Private(__func__); regInfo->TryToActivateAsync([promise] { promise->Resolve(true, __func__); }); return promise; } nsresult ServiceWorkerPrivateImpl::RefreshRemoteWorkerData( const RefPtr& aRegistration) { AssertIsOnMainThread(); MOZ_ASSERT(mOuter); MOZ_ASSERT(mOuter->mInfo); nsTArray permissions; nsCOMPtr permManager = services::GetPermissionManager(); nsTArray keys = nsPermissionManager::GetAllKeysForPrincipal(mOuter->mInfo->Principal()); MOZ_ASSERT(!keys.IsEmpty()); for (auto& key : keys) { nsTArray perms; nsresult rv = permManager->GetPermissionsWithKey(key, perms); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } KeyAndPermissions kp; kp.key() = nsCString(key); kp.permissions() = std::move(perms); permissions.AppendElement(std::move(kp)); } MOZ_ASSERT(!permissions.IsEmpty()); ServiceWorkerData& serviceWorkerData = mRemoteWorkerData.serviceWorkerData().get_ServiceWorkerData(); serviceWorkerData.permissionsByKey() = std::move(permissions); serviceWorkerData.descriptor() = mOuter->mInfo->Descriptor().ToIPC(); serviceWorkerData.registrationDescriptor() = aRegistration->Descriptor().ToIPC(); return NS_OK; } 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 swm = ServiceWorkerManager::GetInstance(); if (NS_WARN_IF(!swm)) { return NS_ERROR_DOM_ABORT_ERR; } RefPtr regInfo = swm->GetRegistration(mOuter->mInfo->Principal(), mOuter->mInfo->Scope()); if (NS_WARN_IF(!regInfo)) { return NS_ERROR_DOM_INVALID_STATE_ERR; } nsresult rv = RefreshRemoteWorkerData(regInfo); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } RefPtr 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&& 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 aCallback) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mOuter); MOZ_ASSERT(aCallback); RefPtr 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 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 promise = self->ShutdownInternal(); RefPtr 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 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 aRegistration, const nsAString& aMessageId, const Maybe>& 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 pendingEvent = MakeUnique(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&& 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&& 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&& 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&& aRegistration, ServiceWorkerFetchEventOpArgs&& aArgs, nsCOMPtr&& aChannel, UniquePtr&& aAutoStream) : PendingFunctionalEvent(aOwner, std::move(aRegistration)), mArgs(std::move(aArgs)), mChannel(std::move(aChannel)), mAutoStream(std::move(aAutoStream)) { AssertIsOnMainThread(); MOZ_ASSERT(mChannel); MOZ_ASSERT(mAutoStream); } 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), std::move(mAutoStream)); } ServiceWorkerPrivateImpl::PendingFetchEvent::~PendingFetchEvent() { AssertIsOnMainThread(); if (NS_WARN_IF(mChannel)) { mChannel->CancelInterception(NS_ERROR_INTERCEPTION_FAILED); } } nsresult ServiceWorkerPrivateImpl::SendFetchEvent( RefPtr aRegistration, nsCOMPtr 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 channel; nsresult rv = aChannel->GetChannel(getter_AddRefs(channel)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } IPCInternalRequest internalRequest; UniquePtr autoStream = MakeUnique(); rv = GetIPCInternalRequest(aChannel, &internalRequest, autoStream); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } scopeExit.release(); MOZ_ASSERT(mOuter->mInfo); ServiceWorkerFetchEventOpArgs args( mOuter->mInfo->ScriptSpec(), internalRequest, nsString(aClientId), nsString(aResultingClientId), aIsReload, nsContentUtils::IsNonSubresourceRequest(channel)); if (mOuter->mInfo->State() == ServiceWorkerState::Activating) { UniquePtr pendingEvent = MakeUnique(this, std::move(aRegistration), std::move(args), std::move(aChannel), std::move(autoStream)); 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), std::move(autoStream)); } nsresult ServiceWorkerPrivateImpl::SendFetchEventInternal( RefPtr&& aRegistration, ServiceWorkerFetchEventOpArgs&& aArgs, nsCOMPtr&& aChannel, UniquePtr&& aAutoStream) { 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 holder = mControllerChild; FetchEventOpChild::Create(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()); }); aAutoStream->TakeOptionalValue(); return NS_OK; } void ServiceWorkerPrivateImpl::TerminateWorker() { AssertIsOnMainThread(); MOZ_ASSERT(mOuter); mOuter->mIdleWorkerTimer->Cancel(); mOuter->mIdleKeepAliveToken = nullptr; Shutdown(); } void ServiceWorkerPrivateImpl::Shutdown() { AssertIsOnMainThread(); if (!WorkerIsDead()) { RefPtr swm = ServiceWorkerManager::GetInstance(); MOZ_ASSERT(swm, "All Service Workers should start shutting down before the " "ServiceWorkerManager does!"); RefPtr promise = ShutdownInternal(); swm->BlockShutdownOn(promise); } MOZ_ASSERT(WorkerIsDead()); } RefPtr ServiceWorkerPrivateImpl::ShutdownInternal() { AssertIsOnMainThread(); MOZ_ASSERT(mControllerChild); mPendingFunctionalEvents.Clear(); mControllerChild->get()->RevokeObserver(this); if (StaticPrefs::dom_serviceWorkers_testing_enabled()) { nsCOMPtr os = services::GetObserverService(); if (os) { os->NotifyObservers(nullptr, "service-worker-shutdown", nullptr); } } RefPtr 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 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&& aSuccessCallback, std::function&& 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 self = this; RefPtr holder = mControllerChild; RefPtr 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