зеркало из https://github.com/mozilla/gecko-dev.git
1687 строки
54 KiB
C++
1687 строки
54 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 "ServiceWorkerPrivate.h"
|
|
|
|
#include <utility>
|
|
|
|
#include "MainThreadUtils.h"
|
|
#include "ServiceWorkerCloneData.h"
|
|
#include "ServiceWorkerManager.h"
|
|
#include "ServiceWorkerRegistrationInfo.h"
|
|
#include "ServiceWorkerUtils.h"
|
|
#include "js/ErrorReport.h"
|
|
#include "mozIThirdPartyUtil.h"
|
|
#include "mozilla/Assertions.h"
|
|
#include "mozilla/CycleCollectedJSContext.h" // for MicroTaskRunnable
|
|
#include "mozilla/ErrorResult.h"
|
|
#include "mozilla/JSObjectHolder.h"
|
|
#include "mozilla/Maybe.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "mozilla/RemoteLazyInputStreamStorage.h"
|
|
#include "mozilla/Result.h"
|
|
#include "mozilla/ResultExtensions.h"
|
|
#include "mozilla/ScopeExit.h"
|
|
#include "mozilla/Services.h"
|
|
#include "mozilla/StaticPrefs_dom.h"
|
|
#include "mozilla/StoragePrincipalHelper.h"
|
|
#include "mozilla/Telemetry.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/RemoteType.h"
|
|
#include "mozilla/dom/RemoteWorkerControllerChild.h"
|
|
#include "mozilla/dom/RemoteWorkerManager.h" // RemoteWorkerManager::GetRemoteType
|
|
#include "mozilla/dom/ServiceWorkerBinding.h"
|
|
#include "mozilla/extensions/WebExtensionPolicy.h" // WebExtensionPolicy
|
|
#include "mozilla/ipc/BackgroundChild.h"
|
|
#include "mozilla/ipc/IPCStreamUtils.h"
|
|
#include "mozilla/ipc/PBackgroundChild.h"
|
|
#include "mozilla/ipc/URIUtils.h"
|
|
#include "mozilla/net/CookieJarSettings.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 "nsINetworkInterceptController.h"
|
|
#include "nsINamed.h"
|
|
#include "nsIObserverService.h"
|
|
#include "nsIRedirectHistoryEntry.h"
|
|
#include "nsIScriptError.h"
|
|
#include "nsIScriptSecurityManager.h"
|
|
#include "nsISupportsImpl.h"
|
|
#include "nsIURI.h"
|
|
#include "nsIUploadChannel2.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsProxyRelease.h"
|
|
#include "nsQueryObject.h"
|
|
#include "nsStreamUtils.h"
|
|
#include "nsStringStream.h"
|
|
#include "nsThreadUtils.h"
|
|
|
|
#include "mozilla/dom/Client.h"
|
|
#include "mozilla/dom/FetchUtil.h"
|
|
#include "mozilla/dom/IndexedDatabaseManager.h"
|
|
#include "mozilla/dom/NotificationEvent.h"
|
|
#include "mozilla/dom/PromiseNativeHandler.h"
|
|
#include "mozilla/dom/PushEventBinding.h"
|
|
#include "mozilla/dom/RequestBinding.h"
|
|
#include "mozilla/dom/RootedDictionary.h"
|
|
#include "mozilla/dom/WorkerDebugger.h"
|
|
#include "mozilla/dom/WorkerRef.h"
|
|
#include "mozilla/dom/WorkerRunnable.h"
|
|
#include "mozilla/dom/WorkerScope.h"
|
|
#include "mozilla/dom/ipc/StructuredCloneData.h"
|
|
#include "mozilla/ipc/BackgroundUtils.h"
|
|
#include "mozilla/net/NeckoChannelParams.h"
|
|
#include "mozilla/StaticPrefs_privacy.h"
|
|
#include "nsIReferrerInfo.h"
|
|
|
|
extern mozilla::LazyLogModule sWorkerTelemetryLog;
|
|
|
|
#ifdef LOG
|
|
# undef LOG
|
|
#endif
|
|
#define LOG(_args) MOZ_LOG(sWorkerTelemetryLog, LogLevel::Debug, _args);
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::dom;
|
|
using namespace mozilla::ipc;
|
|
|
|
namespace mozilla::dom {
|
|
|
|
uint32_t ServiceWorkerPrivate::sRunningServiceWorkers = 0;
|
|
uint32_t ServiceWorkerPrivate::sRunningServiceWorkersFetch = 0;
|
|
uint32_t ServiceWorkerPrivate::sRunningServiceWorkersMax = 0;
|
|
uint32_t ServiceWorkerPrivate::sRunningServiceWorkersFetchMax = 0;
|
|
|
|
// Tracks the "dom.serviceWorkers.disable_open_click_delay" preference. Modified
|
|
// on main thread, read on worker threads.
|
|
// It is updated every time a "notificationclick" event is dispatched. While
|
|
// this is done without synchronization, at the worst, the thread will just get
|
|
// an older value within which a popup is allowed to be displayed, which will
|
|
// still be a valid value since it was set prior to dispatching the runnable.
|
|
Atomic<uint32_t> gDOMDisableOpenClickDelay(0);
|
|
|
|
/**
|
|
* KeepAliveToken
|
|
*/
|
|
KeepAliveToken::KeepAliveToken(ServiceWorkerPrivate* aPrivate)
|
|
: mPrivate(aPrivate) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(aPrivate);
|
|
mPrivate->AddToken();
|
|
}
|
|
|
|
KeepAliveToken::~KeepAliveToken() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
mPrivate->ReleaseToken();
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS0(KeepAliveToken)
|
|
|
|
/**
|
|
* RAIIActorPtrHolder
|
|
*/
|
|
ServiceWorkerPrivate::RAIIActorPtrHolder::RAIIActorPtrHolder(
|
|
already_AddRefed<RemoteWorkerControllerChild> aActor)
|
|
: mActor(aActor) {
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(mActor);
|
|
MOZ_ASSERT(mActor->Manager());
|
|
}
|
|
|
|
ServiceWorkerPrivate::RAIIActorPtrHolder::~RAIIActorPtrHolder() {
|
|
AssertIsOnMainThread();
|
|
|
|
mDestructorPromiseHolder.ResolveIfExists(true, __func__);
|
|
|
|
mActor->MaybeSendDelete();
|
|
}
|
|
|
|
RemoteWorkerControllerChild*
|
|
ServiceWorkerPrivate::RAIIActorPtrHolder::operator->() const {
|
|
AssertIsOnMainThread();
|
|
|
|
return get();
|
|
}
|
|
|
|
RemoteWorkerControllerChild* ServiceWorkerPrivate::RAIIActorPtrHolder::get()
|
|
const {
|
|
AssertIsOnMainThread();
|
|
|
|
return mActor.get();
|
|
}
|
|
|
|
RefPtr<GenericPromise>
|
|
ServiceWorkerPrivate::RAIIActorPtrHolder::OnDestructor() {
|
|
AssertIsOnMainThread();
|
|
|
|
return mDestructorPromiseHolder.Ensure(__func__);
|
|
}
|
|
|
|
/**
|
|
* PendingFunctionEvent
|
|
*/
|
|
ServiceWorkerPrivate::PendingFunctionalEvent::PendingFunctionalEvent(
|
|
ServiceWorkerPrivate* aOwner,
|
|
RefPtr<ServiceWorkerRegistrationInfo>&& aRegistration)
|
|
: mOwner(aOwner), mRegistration(std::move(aRegistration)) {
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(mOwner);
|
|
MOZ_ASSERT(mOwner->mInfo);
|
|
MOZ_ASSERT(mOwner->mInfo->State() == ServiceWorkerState::Activating);
|
|
MOZ_ASSERT(mRegistration);
|
|
}
|
|
|
|
ServiceWorkerPrivate::PendingFunctionalEvent::~PendingFunctionalEvent() {
|
|
AssertIsOnMainThread();
|
|
}
|
|
|
|
ServiceWorkerPrivate::PendingPushEvent::PendingPushEvent(
|
|
ServiceWorkerPrivate* aOwner,
|
|
RefPtr<ServiceWorkerRegistrationInfo>&& aRegistration,
|
|
ServiceWorkerPushEventOpArgs&& aArgs)
|
|
: PendingFunctionalEvent(aOwner, std::move(aRegistration)),
|
|
mArgs(std::move(aArgs)) {
|
|
AssertIsOnMainThread();
|
|
}
|
|
|
|
nsresult ServiceWorkerPrivate::PendingPushEvent::Send() {
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(mOwner);
|
|
MOZ_ASSERT(mOwner->mInfo);
|
|
|
|
return mOwner->SendPushEventInternal(std::move(mRegistration),
|
|
std::move(mArgs));
|
|
}
|
|
|
|
ServiceWorkerPrivate::PendingFetchEvent::PendingFetchEvent(
|
|
ServiceWorkerPrivate* aOwner,
|
|
RefPtr<ServiceWorkerRegistrationInfo>&& aRegistration,
|
|
ParentToParentServiceWorkerFetchEventOpArgs&& aArgs,
|
|
nsCOMPtr<nsIInterceptedChannel>&& aChannel,
|
|
RefPtr<FetchServicePromises>&& aPreloadResponseReadyPromises)
|
|
: PendingFunctionalEvent(aOwner, std::move(aRegistration)),
|
|
mArgs(std::move(aArgs)),
|
|
mChannel(std::move(aChannel)),
|
|
mPreloadResponseReadyPromises(std::move(aPreloadResponseReadyPromises)) {
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(mChannel);
|
|
}
|
|
|
|
nsresult ServiceWorkerPrivate::PendingFetchEvent::Send() {
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(mOwner);
|
|
MOZ_ASSERT(mOwner->mInfo);
|
|
|
|
return mOwner->SendFetchEventInternal(
|
|
std::move(mRegistration), std::move(mArgs), std::move(mChannel),
|
|
std::move(mPreloadResponseReadyPromises));
|
|
}
|
|
|
|
ServiceWorkerPrivate::PendingFetchEvent::~PendingFetchEvent() {
|
|
AssertIsOnMainThread();
|
|
|
|
if (NS_WARN_IF(mChannel)) {
|
|
mChannel->CancelInterception(NS_ERROR_INTERCEPTION_FAILED);
|
|
}
|
|
}
|
|
|
|
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<InternalHeaders> Extract() {
|
|
return RefPtr<InternalHeaders>(std::move(mInternalHeaders));
|
|
}
|
|
|
|
private:
|
|
~HeaderFiller() = default;
|
|
|
|
RefPtr<InternalHeaders> mInternalHeaders;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(HeaderFiller, nsIHttpHeaderVisitor)
|
|
|
|
Result<IPCInternalRequest, nsresult> GetIPCInternalRequest(
|
|
nsIInterceptedChannel* aChannel) {
|
|
AssertIsOnMainThread();
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
MOZ_TRY(aChannel->GetSecureUpgradedChannelURI(getter_AddRefs(uri)));
|
|
|
|
nsCOMPtr<nsIURI> uriNoFragment;
|
|
MOZ_TRY(NS_GetURIWithoutRef(uri, getter_AddRefs(uriNoFragment)));
|
|
|
|
nsCOMPtr<nsIChannel> underlyingChannel;
|
|
MOZ_TRY(aChannel->GetChannel(getter_AddRefs(underlyingChannel)));
|
|
|
|
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(underlyingChannel);
|
|
MOZ_ASSERT(httpChannel, "How come we don't have an HTTP channel?");
|
|
|
|
nsCOMPtr<nsIHttpChannelInternal> internalChannel =
|
|
do_QueryInterface(httpChannel);
|
|
NS_ENSURE_TRUE(internalChannel, Err(NS_ERROR_NOT_AVAILABLE));
|
|
|
|
nsCOMPtr<nsICacheInfoChannel> cacheInfoChannel =
|
|
do_QueryInterface(underlyingChannel);
|
|
|
|
nsAutoCString spec;
|
|
MOZ_TRY(uriNoFragment->GetSpec(spec));
|
|
|
|
nsAutoCString fragment;
|
|
MOZ_TRY(uri->GetRef(fragment));
|
|
|
|
nsAutoCString method;
|
|
MOZ_TRY(httpChannel->GetRequestMethod(method));
|
|
|
|
// This is safe due to static_asserts in ServiceWorkerManager.cpp
|
|
uint32_t cacheModeInt;
|
|
MOZ_ALWAYS_SUCCEEDS(internalChannel->GetFetchCacheMode(&cacheModeInt));
|
|
RequestCache cacheMode = static_cast<RequestCache>(cacheModeInt);
|
|
|
|
RequestMode requestMode =
|
|
InternalRequest::MapChannelToRequestMode(underlyingChannel);
|
|
|
|
// This is safe due to static_asserts in ServiceWorkerManager.cpp
|
|
uint32_t redirectMode;
|
|
MOZ_ALWAYS_SUCCEEDS(internalChannel->GetRedirectMode(&redirectMode));
|
|
RequestRedirect requestRedirect = static_cast<RequestRedirect>(redirectMode);
|
|
|
|
RequestCredentials requestCredentials =
|
|
InternalRequest::MapChannelToRequestCredentials(underlyingChannel);
|
|
|
|
nsAutoString referrer;
|
|
ReferrerPolicy referrerPolicy = ReferrerPolicy::_empty;
|
|
ReferrerPolicy environmentReferrerPolicy = ReferrerPolicy::_empty;
|
|
|
|
nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo();
|
|
if (referrerInfo) {
|
|
referrerPolicy = referrerInfo->ReferrerPolicy();
|
|
Unused << referrerInfo->GetComputedReferrerSpec(referrer);
|
|
}
|
|
|
|
uint32_t loadFlags;
|
|
MOZ_TRY(underlyingChannel->GetLoadFlags(&loadFlags));
|
|
|
|
nsCOMPtr<nsILoadInfo> loadInfo = underlyingChannel->LoadInfo();
|
|
nsContentPolicyType contentPolicyType = loadInfo->InternalContentPolicyType();
|
|
|
|
nsAutoString integrity;
|
|
MOZ_TRY(internalChannel->GetIntegrityMetadata(integrity));
|
|
|
|
RefPtr<HeaderFiller> headerFiller =
|
|
MakeRefPtr<HeaderFiller>(HeadersGuardEnum::Request);
|
|
MOZ_TRY(httpChannel->VisitNonDefaultRequestHeaders(headerFiller));
|
|
|
|
RefPtr<InternalHeaders> internalHeaders = headerFiller->Extract();
|
|
|
|
ErrorResult result;
|
|
internalHeaders->SetGuard(HeadersGuardEnum::Immutable, result);
|
|
if (NS_WARN_IF(result.Failed())) {
|
|
return Err(result.StealNSResult());
|
|
}
|
|
|
|
nsTArray<HeadersEntry> ipcHeaders;
|
|
HeadersGuardEnum ipcHeadersGuard;
|
|
internalHeaders->ToIPC(ipcHeaders, ipcHeadersGuard);
|
|
|
|
nsAutoCString alternativeDataType;
|
|
if (cacheInfoChannel &&
|
|
!cacheInfoChannel->PreferredAlternativeDataTypes().IsEmpty()) {
|
|
// TODO: the internal request probably needs all the preferred types.
|
|
alternativeDataType.Assign(
|
|
cacheInfoChannel->PreferredAlternativeDataTypes()[0].type());
|
|
}
|
|
|
|
Maybe<PrincipalInfo> principalInfo;
|
|
Maybe<PrincipalInfo> interceptionPrincipalInfo;
|
|
if (loadInfo->TriggeringPrincipal()) {
|
|
principalInfo.emplace();
|
|
interceptionPrincipalInfo.emplace();
|
|
MOZ_ALWAYS_SUCCEEDS(PrincipalToPrincipalInfo(
|
|
loadInfo->TriggeringPrincipal(), principalInfo.ptr()));
|
|
MOZ_ALWAYS_SUCCEEDS(PrincipalToPrincipalInfo(
|
|
loadInfo->TriggeringPrincipal(), interceptionPrincipalInfo.ptr()));
|
|
}
|
|
|
|
nsTArray<RedirectHistoryEntryInfo> redirectChain;
|
|
for (const nsCOMPtr<nsIRedirectHistoryEntry>& redirectEntry :
|
|
loadInfo->RedirectChain()) {
|
|
RedirectHistoryEntryInfo* entry = redirectChain.AppendElement();
|
|
MOZ_ALWAYS_SUCCEEDS(RHEntryToRHEntryInfo(redirectEntry, entry));
|
|
}
|
|
|
|
bool isThirdPartyChannel;
|
|
// ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
|
|
nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
|
|
do_GetService(THIRDPARTYUTIL_CONTRACTID);
|
|
if (thirdPartyUtil) {
|
|
nsCOMPtr<nsIURI> uri;
|
|
MOZ_TRY(underlyingChannel->GetURI(getter_AddRefs(uri)));
|
|
MOZ_TRY(thirdPartyUtil->IsThirdPartyChannel(underlyingChannel, uri,
|
|
&isThirdPartyChannel));
|
|
}
|
|
|
|
nsILoadInfo::CrossOriginEmbedderPolicy embedderPolicy =
|
|
loadInfo->GetLoadingEmbedderPolicy();
|
|
|
|
// Note: all the arguments are copied rather than moved, which would be more
|
|
// efficient, because there's no move-friendly constructor generated.
|
|
return IPCInternalRequest(
|
|
method, {spec}, ipcHeadersGuard, ipcHeaders, Nothing(), -1,
|
|
alternativeDataType, contentPolicyType, referrer, referrerPolicy,
|
|
environmentReferrerPolicy, requestMode, requestCredentials, cacheMode,
|
|
requestRedirect, integrity, fragment, principalInfo,
|
|
interceptionPrincipalInfo, contentPolicyType, redirectChain,
|
|
isThirdPartyChannel, embedderPolicy);
|
|
}
|
|
|
|
nsresult MaybeStoreStreamForBackgroundThread(nsIInterceptedChannel* aChannel,
|
|
IPCInternalRequest& aIPCRequest) {
|
|
nsCOMPtr<nsIChannel> channel;
|
|
MOZ_ALWAYS_SUCCEEDS(aChannel->GetChannel(getter_AddRefs(channel)));
|
|
|
|
Maybe<BodyStreamVariant> body;
|
|
nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(channel);
|
|
|
|
if (uploadChannel) {
|
|
nsCOMPtr<nsIInputStream> uploadStream;
|
|
MOZ_TRY(uploadChannel->CloneUploadStream(&aIPCRequest.bodySize(),
|
|
getter_AddRefs(uploadStream)));
|
|
|
|
if (uploadStream) {
|
|
Maybe<BodyStreamVariant>& body = aIPCRequest.body();
|
|
body.emplace(ParentToParentStream());
|
|
|
|
MOZ_TRY(
|
|
nsID::GenerateUUIDInPlace(body->get_ParentToParentStream().uuid()));
|
|
|
|
auto storageOrErr = RemoteLazyInputStreamStorage::Get();
|
|
if (NS_WARN_IF(storageOrErr.isErr())) {
|
|
return storageOrErr.unwrapErr();
|
|
}
|
|
|
|
auto storage = storageOrErr.unwrap();
|
|
storage->AddStream(uploadStream, body->get_ParentToParentStream().uuid());
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
/**
|
|
* ServiceWorkerPrivate
|
|
*/
|
|
ServiceWorkerPrivate::ServiceWorkerPrivate(ServiceWorkerInfo* aInfo)
|
|
: mInfo(aInfo), mDebuggerCount(0), mTokenCount(0) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(aInfo);
|
|
MOZ_ASSERT(!mControllerChild);
|
|
|
|
mIdleWorkerTimer = NS_NewTimer();
|
|
MOZ_ASSERT(mIdleWorkerTimer);
|
|
|
|
// Assert in all debug builds as well as non-debug Nightly and Dev Edition.
|
|
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
|
|
MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(Initialize()));
|
|
#else
|
|
MOZ_ALWAYS_SUCCEEDS(Initialize());
|
|
#endif
|
|
}
|
|
|
|
ServiceWorkerPrivate::~ServiceWorkerPrivate() {
|
|
MOZ_ASSERT(!mTokenCount);
|
|
MOZ_ASSERT(!mInfo);
|
|
MOZ_ASSERT(!mControllerChild);
|
|
MOZ_ASSERT(mIdlePromiseHolder.IsEmpty());
|
|
|
|
mIdleWorkerTimer->Cancel();
|
|
}
|
|
|
|
nsresult ServiceWorkerPrivate::Initialize() {
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(mInfo);
|
|
|
|
nsCOMPtr<nsIPrincipal> principal = mInfo->Principal();
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
auto* basePrin = BasePrincipal::Cast(principal);
|
|
nsresult rv = basePrin->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 = 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, mInfo->Scope());
|
|
|
|
if (NS_WARN_IF(!regInfo)) {
|
|
return NS_ERROR_DOM_INVALID_STATE_ERR;
|
|
}
|
|
|
|
nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
|
|
net::CookieJarSettings::Create(principal);
|
|
MOZ_ASSERT(cookieJarSettings);
|
|
|
|
// We can populate the partitionKey from the originAttribute of the principal
|
|
// if it has partitionKey set. It's because ServiceWorker is using the foreign
|
|
// partitioned principal and it implies that it's a third-party service
|
|
// worker. So, the cookieJarSettings can directly use the partitionKey from
|
|
// it. For first-party case, we can populate the partitionKey from the
|
|
// principal URI.
|
|
if (!principal->OriginAttributesRef().mPartitionKey.IsEmpty()) {
|
|
net::CookieJarSettings::Cast(cookieJarSettings)
|
|
->SetPartitionKey(principal->OriginAttributesRef().mPartitionKey);
|
|
} else {
|
|
net::CookieJarSettings::Cast(cookieJarSettings)->SetPartitionKey(uri);
|
|
}
|
|
|
|
net::CookieJarSettingsArgs cjsData;
|
|
net::CookieJarSettings::Cast(cookieJarSettings)->Serialize(cjsData);
|
|
|
|
nsCOMPtr<nsIPrincipal> partitionedPrincipal;
|
|
rv = StoragePrincipalHelper::CreatePartitionedPrincipalForServiceWorker(
|
|
principal, cookieJarSettings, getter_AddRefs(partitionedPrincipal));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
PrincipalInfo partitionedPrincipalInfo;
|
|
rv =
|
|
PrincipalToPrincipalInfo(partitionedPrincipal, &partitionedPrincipalInfo);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
StorageAccess storageAccess =
|
|
StorageAllowedForServiceWorker(principal, cookieJarSettings);
|
|
|
|
ServiceWorkerData serviceWorkerData;
|
|
serviceWorkerData.cacheName() = mInfo->CacheName();
|
|
serviceWorkerData.loadFlags() = static_cast<uint32_t>(
|
|
mInfo->GetImportsLoadFlags() | nsIChannel::LOAD_BYPASS_SERVICE_WORKER);
|
|
serviceWorkerData.id() = std::move(id);
|
|
|
|
nsAutoCString domain;
|
|
rv = uri->GetHost(domain);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
auto remoteType = RemoteWorkerManager::GetRemoteType(
|
|
principal, WorkerKind::WorkerKindService);
|
|
if (NS_WARN_IF(remoteType.isErr())) {
|
|
return remoteType.unwrapErr();
|
|
}
|
|
|
|
// Determine if the service worker is registered under a third-party context
|
|
// by checking if it's running under a partitioned principal.
|
|
bool isThirdPartyContextToTopWindow =
|
|
!principal->OriginAttributesRef().mPartitionKey.IsEmpty();
|
|
|
|
mRemoteWorkerData = RemoteWorkerData(
|
|
NS_ConvertUTF8toUTF16(mInfo->ScriptSpec()), baseScriptURL, baseScriptURL,
|
|
/* name */ VoidString(),
|
|
/* workerType */ WorkerType::Classic,
|
|
/* credentials */ RequestCredentials::Omit,
|
|
/* loading principal */ principalInfo, principalInfo,
|
|
partitionedPrincipalInfo,
|
|
/* useRegularPrincipal */ true,
|
|
|
|
// ServiceWorkers run as first-party, no storage-access permission needed.
|
|
/* hasStorageAccessPermissionGranted */ false,
|
|
|
|
cjsData, domain,
|
|
/* isSecureContext */ true,
|
|
/* clientInfo*/ Nothing(),
|
|
|
|
// The RemoteWorkerData CTOR doesn't allow to set the referrerInfo via
|
|
// already_AddRefed<>. Let's set it to null.
|
|
/* referrerInfo */ nullptr,
|
|
|
|
storageAccess, isThirdPartyContextToTopWindow,
|
|
nsContentUtils::ShouldResistFingerprinting_dangerous(
|
|
principal,
|
|
"Service Workers exist outside a Document or Channel; as a property "
|
|
"of the domain (and origin attributes). We don't have a "
|
|
"CookieJarSettings to perform the nested check, but we can rely on"
|
|
"the FPI/dFPI partition key check."),
|
|
// Origin trials are associated to a window, so it doesn't make sense on
|
|
// service workers.
|
|
OriginTrials(), std::move(serviceWorkerData), regInfo->AgentClusterId(),
|
|
remoteType.unwrap());
|
|
|
|
mRemoteWorkerData.referrerInfo() = MakeAndAddRef<ReferrerInfo>();
|
|
|
|
// This fills in the rest of mRemoteWorkerData.serviceWorkerData().
|
|
RefreshRemoteWorkerData(regInfo);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult ServiceWorkerPrivate::CheckScriptEvaluation(
|
|
RefPtr<LifeCycleEventCallback> aCallback) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(aCallback);
|
|
|
|
RefPtr<ServiceWorkerPrivate> 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()) {
|
|
self->SetHandlesFetch(result.fetchHandlerWasAdded());
|
|
if (self->mHandlesFetch == Unknown) {
|
|
self->mHandlesFetch =
|
|
result.fetchHandlerWasAdded() ? Enabled : Disabled;
|
|
// Update telemetry for # of running SW - the already-running SW
|
|
// handles fetch
|
|
if (self->mHandlesFetch == Enabled) {
|
|
self->UpdateRunning(0, 1);
|
|
}
|
|
}
|
|
|
|
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(
|
|
GetCurrentSerialEventTarget(), __func__,
|
|
[callback = std::move(callback)](
|
|
const GenericPromise::ResolveOrRejectValue&) {
|
|
callback->SetResult(false);
|
|
callback->Run();
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
|
|
MOZ_ASSERT(swm);
|
|
|
|
auto shutdownStateId = swm->MaybeInitServiceWorkerShutdownProgress();
|
|
|
|
RefPtr<GenericNonExclusivePromise> promise =
|
|
self->ShutdownInternal(shutdownStateId);
|
|
|
|
swm->BlockShutdownOn(promise, shutdownStateId);
|
|
|
|
promise->Then(
|
|
GetCurrentSerialEventTarget(), __func__,
|
|
[callback = std::move(callback)](
|
|
const GenericNonExclusivePromise::ResolveOrRejectValue&) {
|
|
callback->SetResult(false);
|
|
callback->Run();
|
|
});
|
|
},
|
|
[callback = aCallback] {
|
|
callback->SetResult(false);
|
|
callback->Run();
|
|
});
|
|
}
|
|
|
|
nsresult ServiceWorkerPrivate::SendMessageEvent(
|
|
RefPtr<ServiceWorkerCloneData>&& aData,
|
|
const ClientInfoAndState& aClientInfoAndState) {
|
|
AssertIsOnMainThread();
|
|
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->BuildClonedMessageData(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 ServiceWorkerPrivate::SendLifeCycleEvent(
|
|
const nsAString& aEventType, RefPtr<LifeCycleEventCallback> aCallback) {
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(aCallback);
|
|
|
|
return ExecServiceWorkerOp(
|
|
ServiceWorkerLifeCycleEventOpArgs(nsString(aEventType)),
|
|
[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 ServiceWorkerPrivate::SendPushEvent(
|
|
const nsAString& aMessageId, const Maybe<nsTArray<uint8_t>>& aData,
|
|
RefPtr<ServiceWorkerRegistrationInfo> aRegistration) {
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(mInfo);
|
|
MOZ_ASSERT(aRegistration);
|
|
|
|
ServiceWorkerPushEventOpArgs args;
|
|
args.messageId() = nsString(aMessageId);
|
|
|
|
if (aData) {
|
|
args.data() = aData.ref();
|
|
} else {
|
|
args.data() = void_t();
|
|
}
|
|
|
|
if (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(mInfo->State() == ServiceWorkerState::Activated);
|
|
|
|
return SendPushEventInternal(std::move(aRegistration), std::move(args));
|
|
}
|
|
|
|
nsresult ServiceWorkerPrivate::SendPushEventInternal(
|
|
RefPtr<ServiceWorkerRegistrationInfo>&& aRegistration,
|
|
ServiceWorkerPushEventOpArgs&& aArgs) {
|
|
AssertIsOnMainThread();
|
|
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 ServiceWorkerPrivate::SendPushSubscriptionChangeEvent() {
|
|
AssertIsOnMainThread();
|
|
|
|
return ExecServiceWorkerOp(
|
|
ServiceWorkerPushSubscriptionChangeEventOpArgs(),
|
|
[](ServiceWorkerOpResult&& aResult) {
|
|
MOZ_ASSERT(aResult.type() == ServiceWorkerOpResult::Tnsresult);
|
|
});
|
|
}
|
|
|
|
nsresult ServiceWorkerPrivate::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) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (aEventName.EqualsLiteral(NOTIFICATION_CLICK_EVENT_NAME)) {
|
|
gDOMDisableOpenClickDelay =
|
|
Preferences::GetInt("dom.serviceWorkers.disable_open_click_delay");
|
|
} else if (!aEventName.EqualsLiteral(NOTIFICATION_CLOSE_EVENT_NAME)) {
|
|
MOZ_ASSERT_UNREACHABLE("Invalid notification event name");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
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() = gDOMDisableOpenClickDelay;
|
|
|
|
return ExecServiceWorkerOp(
|
|
std::move(args), [](ServiceWorkerOpResult&& aResult) {
|
|
MOZ_ASSERT(aResult.type() == ServiceWorkerOpResult::Tnsresult);
|
|
});
|
|
}
|
|
|
|
nsresult ServiceWorkerPrivate::SendFetchEvent(
|
|
nsCOMPtr<nsIInterceptedChannel> aChannel, nsILoadGroup* aLoadGroup,
|
|
const nsAString& aClientId, const nsAString& aResultingClientId) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(aChannel);
|
|
|
|
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
|
|
if (NS_WARN_IF(!mInfo || !swm)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsCOMPtr<nsIChannel> channel;
|
|
nsresult rv = aChannel->GetChannel(getter_AddRefs(channel));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
bool isNonSubresourceRequest =
|
|
nsContentUtils::IsNonSubresourceRequest(channel);
|
|
|
|
RefPtr<ServiceWorkerRegistrationInfo> registration;
|
|
if (isNonSubresourceRequest) {
|
|
registration = swm->GetRegistration(mInfo->Principal(), mInfo->Scope());
|
|
} else {
|
|
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
|
|
|
|
// We'll check for a null registration below rather than an error code here.
|
|
Unused << swm->GetClientRegistration(loadInfo->GetClientInfo().ref(),
|
|
getter_AddRefs(registration));
|
|
}
|
|
|
|
// Its possible the registration is removed between starting the interception
|
|
// and actually dispatching the fetch event. In these cases we simply
|
|
// want to restart the original network request. Since this is a normal
|
|
// condition we handle the reset here instead of returning an error which
|
|
// would in turn trigger a console report.
|
|
if (!registration) {
|
|
nsresult rv = aChannel->ResetInterception(false);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("Failed to resume intercepted network request");
|
|
aChannel->CancelInterception(rv);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
// Handle Fetch algorithm - step 16. If the service worker didn't register
|
|
// any fetch event handlers, then abort the interception and maybe trigger
|
|
// the soft update algorithm.
|
|
if (!mInfo->HandlesFetch()) {
|
|
nsresult rv = aChannel->ResetInterception(false);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("Failed to resume intercepted network request");
|
|
aChannel->CancelInterception(rv);
|
|
}
|
|
|
|
// Trigger soft updates if necessary.
|
|
registration->MaybeScheduleTimeCheckAndUpdate();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
auto scopeExit = MakeScopeExit([&] {
|
|
aChannel->CancelInterception(NS_ERROR_INTERCEPTION_FAILED);
|
|
Shutdown();
|
|
});
|
|
|
|
IPCInternalRequest request;
|
|
MOZ_TRY_VAR(request, GetIPCInternalRequest(aChannel));
|
|
|
|
scopeExit.release();
|
|
|
|
bool preloadNavigation = isNonSubresourceRequest &&
|
|
request.method().LowerCaseEqualsASCII("get") &&
|
|
registration->GetNavigationPreloadState().enabled();
|
|
|
|
RefPtr<FetchServicePromises> preloadResponsePromises;
|
|
if (preloadNavigation) {
|
|
preloadResponsePromises = SetupNavigationPreload(aChannel, registration);
|
|
}
|
|
|
|
ParentToParentServiceWorkerFetchEventOpArgs args(
|
|
ServiceWorkerFetchEventOpArgsCommon(
|
|
mInfo->ScriptSpec(), request, nsString(aClientId),
|
|
nsString(aResultingClientId), isNonSubresourceRequest,
|
|
preloadNavigation, mInfo->TestingInjectCancellation()),
|
|
Nothing(), Nothing(), Nothing());
|
|
|
|
if (mInfo->State() == ServiceWorkerState::Activating) {
|
|
UniquePtr<PendingFunctionalEvent> pendingEvent =
|
|
MakeUnique<PendingFetchEvent>(this, std::move(registration),
|
|
std::move(args), std::move(aChannel),
|
|
std::move(preloadResponsePromises));
|
|
|
|
mPendingFunctionalEvents.AppendElement(std::move(pendingEvent));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_ASSERT(mInfo->State() == ServiceWorkerState::Activated);
|
|
|
|
return SendFetchEventInternal(std::move(registration), std::move(args),
|
|
std::move(aChannel),
|
|
std::move(preloadResponsePromises));
|
|
}
|
|
|
|
nsresult ServiceWorkerPrivate::SendFetchEventInternal(
|
|
RefPtr<ServiceWorkerRegistrationInfo>&& aRegistration,
|
|
ParentToParentServiceWorkerFetchEventOpArgs&& aArgs,
|
|
nsCOMPtr<nsIInterceptedChannel>&& aChannel,
|
|
RefPtr<FetchServicePromises>&& aPreloadResponseReadyPromises) {
|
|
AssertIsOnMainThread();
|
|
|
|
auto scopeExit = MakeScopeExit([&] { Shutdown(); });
|
|
|
|
if (NS_WARN_IF(!mInfo)) {
|
|
return NS_ERROR_DOM_INVALID_STATE_ERR;
|
|
}
|
|
|
|
MOZ_TRY(SpawnWorkerIfNeeded());
|
|
MOZ_TRY(MaybeStoreStreamForBackgroundThread(
|
|
aChannel, aArgs.common().internalRequest()));
|
|
|
|
scopeExit.release();
|
|
|
|
MOZ_ASSERT(mControllerChild);
|
|
|
|
RefPtr<RAIIActorPtrHolder> holder = mControllerChild;
|
|
|
|
FetchEventOpChild::SendFetchEvent(
|
|
mControllerChild->get(), std::move(aArgs), std::move(aChannel),
|
|
std::move(aRegistration), std::move(aPreloadResponseReadyPromises),
|
|
CreateEventKeepAliveToken())
|
|
->Then(GetCurrentSerialEventTarget(), __func__,
|
|
[holder = std::move(holder)](
|
|
const GenericPromise::ResolveOrRejectValue& aResult) {
|
|
Unused << NS_WARN_IF(aResult.IsReject());
|
|
});
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
Result<RefPtr<ServiceWorkerPrivate::PromiseExtensionWorkerHasListener>,
|
|
nsresult>
|
|
ServiceWorkerPrivate::WakeForExtensionAPIEvent(
|
|
const nsAString& aExtensionAPINamespace,
|
|
const nsAString& aExtensionAPIEventName) {
|
|
AssertIsOnMainThread();
|
|
|
|
ServiceWorkerExtensionAPIEventOpArgs args;
|
|
args.apiNamespace() = nsString(aExtensionAPINamespace);
|
|
args.apiEventName() = nsString(aExtensionAPIEventName);
|
|
|
|
auto promise =
|
|
MakeRefPtr<PromiseExtensionWorkerHasListener::Private>(__func__);
|
|
|
|
nsresult rv = ExecServiceWorkerOp(
|
|
std::move(args),
|
|
[promise](ServiceWorkerOpResult&& aResult) {
|
|
MOZ_ASSERT(
|
|
aResult.type() ==
|
|
ServiceWorkerOpResult::TServiceWorkerExtensionAPIEventOpResult);
|
|
auto& result = aResult.get_ServiceWorkerExtensionAPIEventOpResult();
|
|
promise->Resolve(result.extensionAPIEventListenerWasAdded(), __func__);
|
|
},
|
|
[promise]() { promise->Reject(NS_ERROR_FAILURE, __func__); });
|
|
|
|
if (NS_FAILED(rv)) {
|
|
promise->Reject(rv, __func__);
|
|
}
|
|
|
|
RefPtr<PromiseExtensionWorkerHasListener> outPromise(promise);
|
|
return outPromise;
|
|
}
|
|
|
|
nsresult ServiceWorkerPrivate::SpawnWorkerIfNeeded() {
|
|
AssertIsOnMainThread();
|
|
|
|
if (mControllerChild) {
|
|
RenewKeepAliveToken();
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!mInfo) {
|
|
return NS_ERROR_DOM_INVALID_STATE_ERR;
|
|
}
|
|
|
|
mServiceWorkerLaunchTimeStart = TimeStamp::Now();
|
|
|
|
PBackgroundChild* bgChild = BackgroundChild::GetForCurrentThread();
|
|
|
|
if (NS_WARN_IF(!bgChild)) {
|
|
return NS_ERROR_DOM_INVALID_STATE_ERR;
|
|
}
|
|
|
|
// If the worker principal is an extension principal, then we should not spawn
|
|
// a worker if there is no WebExtensionPolicy associated to that principal
|
|
// or if the WebExtensionPolicy is not active.
|
|
auto* principal = mInfo->Principal();
|
|
if (principal->SchemeIs("moz-extension")) {
|
|
auto* addonPolicy = BasePrincipal::Cast(principal)->AddonPolicy();
|
|
if (!addonPolicy || !addonPolicy->Active()) {
|
|
NS_WARNING(
|
|
"Trying to wake up a service worker for a disabled webextension.");
|
|
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(principal, mInfo->Scope());
|
|
|
|
if (NS_WARN_IF(!regInfo)) {
|
|
return NS_ERROR_DOM_INVALID_STATE_ERR;
|
|
}
|
|
|
|
RefreshRemoteWorkerData(regInfo);
|
|
|
|
RefPtr<RemoteWorkerControllerChild> controllerChild =
|
|
new RemoteWorkerControllerChild(this);
|
|
|
|
if (NS_WARN_IF(!bgChild->SendPRemoteWorkerControllerConstructor(
|
|
controllerChild, mRemoteWorkerData))) {
|
|
return NS_ERROR_DOM_INVALID_STATE_ERR;
|
|
}
|
|
|
|
/**
|
|
* Manually `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());
|
|
|
|
// Update Running count here because we may Terminate before we get
|
|
// CreationSucceeded(). We'll update if it handles Fetch if that changes
|
|
// (
|
|
UpdateRunning(1, mHandlesFetch == Enabled ? 1 : 0);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void ServiceWorkerPrivate::TerminateWorker() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
mIdleWorkerTimer->Cancel();
|
|
mIdleKeepAliveToken = nullptr;
|
|
Shutdown();
|
|
}
|
|
|
|
void ServiceWorkerPrivate::NoteDeadServiceWorkerInfo() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
TerminateWorker();
|
|
mInfo = nullptr;
|
|
}
|
|
|
|
void ServiceWorkerPrivate::UpdateState(ServiceWorkerState aState) {
|
|
AssertIsOnMainThread();
|
|
|
|
if (!mControllerChild) {
|
|
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();
|
|
}
|
|
|
|
nsresult ServiceWorkerPrivate::GetDebugger(nsIWorkerDebugger** aResult) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(aResult);
|
|
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
nsresult ServiceWorkerPrivate::AttachDebugger() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// When the first debugger attaches to a worker, we spawn a worker if needed,
|
|
// and cancel the idle timeout. The idle timeout should not be reset until
|
|
// the last debugger detached from the worker.
|
|
if (!mDebuggerCount) {
|
|
nsresult rv = SpawnWorkerIfNeeded();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
/**
|
|
* Renewing the idle KeepAliveToken for spawning workers happens
|
|
* asynchronously, rather than synchronously.
|
|
* The asynchronous renewal is because the actual spawning of workers occurs
|
|
* in a content process, so we will only renew once notified that the worker
|
|
* has been successfully created
|
|
*
|
|
* This means that the DevTools way of starting up a worker by calling
|
|
* `AttachDebugger` immediately followed by `DetachDebugger` will spawn and
|
|
* immediately terminate a worker (because `mTokenCount` is possibly 0
|
|
* due to the idle KeepAliveToken being created asynchronously). So, just
|
|
* renew the KeepAliveToken right now.
|
|
*/
|
|
RenewKeepAliveToken();
|
|
mIdleWorkerTimer->Cancel();
|
|
}
|
|
|
|
++mDebuggerCount;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult ServiceWorkerPrivate::DetachDebugger() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!mDebuggerCount) {
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
--mDebuggerCount;
|
|
|
|
// When the last debugger detaches from a worker, we either reset the idle
|
|
// timeout, or terminate the worker if there are no more active tokens.
|
|
if (!mDebuggerCount) {
|
|
if (mTokenCount) {
|
|
ResetIdleTimeout();
|
|
} else {
|
|
TerminateWorker();
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
bool ServiceWorkerPrivate::IsIdle() const {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
return mTokenCount == 0 || (mTokenCount == 1 && mIdleKeepAliveToken);
|
|
}
|
|
|
|
RefPtr<GenericPromise> ServiceWorkerPrivate::GetIdlePromise() {
|
|
#ifdef DEBUG
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(!IsIdle());
|
|
MOZ_ASSERT(!mIdlePromiseObtained, "Idle promise may only be obtained once!");
|
|
mIdlePromiseObtained = true;
|
|
#endif
|
|
|
|
return mIdlePromiseHolder.Ensure(__func__);
|
|
}
|
|
|
|
namespace {
|
|
|
|
class ServiceWorkerPrivateTimerCallback final : public nsITimerCallback,
|
|
public nsINamed {
|
|
public:
|
|
using Method = void (ServiceWorkerPrivate::*)(nsITimer*);
|
|
|
|
ServiceWorkerPrivateTimerCallback(ServiceWorkerPrivate* aServiceWorkerPrivate,
|
|
Method aMethod)
|
|
: mServiceWorkerPrivate(aServiceWorkerPrivate), mMethod(aMethod) {}
|
|
|
|
NS_IMETHOD
|
|
Notify(nsITimer* aTimer) override {
|
|
(mServiceWorkerPrivate->*mMethod)(aTimer);
|
|
mServiceWorkerPrivate = nullptr;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHOD
|
|
GetName(nsACString& aName) override {
|
|
aName.AssignLiteral("ServiceWorkerPrivateTimerCallback");
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
~ServiceWorkerPrivateTimerCallback() = default;
|
|
|
|
RefPtr<ServiceWorkerPrivate> mServiceWorkerPrivate;
|
|
Method mMethod;
|
|
|
|
NS_DECL_THREADSAFE_ISUPPORTS
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(ServiceWorkerPrivateTimerCallback, nsITimerCallback,
|
|
nsINamed);
|
|
|
|
} // anonymous namespace
|
|
|
|
void ServiceWorkerPrivate::NoteIdleWorkerCallback(nsITimer* aTimer) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
MOZ_ASSERT(aTimer == mIdleWorkerTimer, "Invalid timer!");
|
|
|
|
// Release ServiceWorkerPrivate's token, since the grace period has ended.
|
|
mIdleKeepAliveToken = nullptr;
|
|
|
|
if (mControllerChild) {
|
|
// If we still have a living worker at this point it means that either there
|
|
// are pending waitUntil promises or the worker is doing some long-running
|
|
// computation. Wait a bit more until we forcibly terminate the worker.
|
|
uint32_t timeout =
|
|
Preferences::GetInt("dom.serviceWorkers.idle_extended_timeout");
|
|
nsCOMPtr<nsITimerCallback> cb = new ServiceWorkerPrivateTimerCallback(
|
|
this, &ServiceWorkerPrivate::TerminateWorkerCallback);
|
|
DebugOnly<nsresult> rv = mIdleWorkerTimer->InitWithCallback(
|
|
cb, timeout, nsITimer::TYPE_ONE_SHOT);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
}
|
|
}
|
|
|
|
void ServiceWorkerPrivate::TerminateWorkerCallback(nsITimer* aTimer) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
MOZ_ASSERT(aTimer == this->mIdleWorkerTimer, "Invalid timer!");
|
|
|
|
// mInfo must be non-null at this point because NoteDeadServiceWorkerInfo
|
|
// which zeroes it calls TerminateWorker which cancels our timer which will
|
|
// ensure we don't get invoked even if the nsTimerEvent is in the event queue.
|
|
ServiceWorkerManager::LocalizeAndReportToAllClients(
|
|
mInfo->Scope(), "ServiceWorkerGraceTimeoutTermination",
|
|
nsTArray<nsString>{NS_ConvertUTF8toUTF16(mInfo->Scope())});
|
|
|
|
TerminateWorker();
|
|
}
|
|
|
|
void ServiceWorkerPrivate::RenewKeepAliveToken() {
|
|
// We should have an active worker if we're renewing the keep alive token.
|
|
MOZ_ASSERT(mControllerChild);
|
|
|
|
// If there is at least one debugger attached to the worker, the idle worker
|
|
// timeout was canceled when the first debugger attached to the worker. It
|
|
// should not be reset until the last debugger detaches from the worker.
|
|
if (!mDebuggerCount) {
|
|
ResetIdleTimeout();
|
|
}
|
|
|
|
if (!mIdleKeepAliveToken) {
|
|
mIdleKeepAliveToken = new KeepAliveToken(this);
|
|
}
|
|
}
|
|
|
|
void ServiceWorkerPrivate::ResetIdleTimeout() {
|
|
uint32_t timeout = Preferences::GetInt("dom.serviceWorkers.idle_timeout");
|
|
nsCOMPtr<nsITimerCallback> cb = new ServiceWorkerPrivateTimerCallback(
|
|
this, &ServiceWorkerPrivate::NoteIdleWorkerCallback);
|
|
DebugOnly<nsresult> rv =
|
|
mIdleWorkerTimer->InitWithCallback(cb, timeout, nsITimer::TYPE_ONE_SHOT);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
}
|
|
|
|
void ServiceWorkerPrivate::AddToken() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
++mTokenCount;
|
|
}
|
|
|
|
void ServiceWorkerPrivate::ReleaseToken() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
MOZ_ASSERT(mTokenCount > 0);
|
|
--mTokenCount;
|
|
|
|
if (IsIdle()) {
|
|
mIdlePromiseHolder.ResolveIfExists(true, __func__);
|
|
|
|
if (!mTokenCount) {
|
|
TerminateWorker();
|
|
}
|
|
|
|
// mInfo can be nullptr here if NoteDeadServiceWorkerInfo() is called while
|
|
// the KeepAliveToken is being proxy released as a runnable.
|
|
else if (mInfo) {
|
|
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
|
|
if (swm) {
|
|
swm->WorkerIsIdle(mInfo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
already_AddRefed<KeepAliveToken>
|
|
ServiceWorkerPrivate::CreateEventKeepAliveToken() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// When the WorkerPrivate is in a separate process, we first hold a normal
|
|
// KeepAliveToken. Then, after we're notified that the worker is alive, we
|
|
// create the idle KeepAliveToken.
|
|
MOZ_ASSERT(mIdleKeepAliveToken || mControllerChild);
|
|
|
|
RefPtr<KeepAliveToken> ref = new KeepAliveToken(this);
|
|
return ref.forget();
|
|
}
|
|
|
|
void ServiceWorkerPrivate::SetHandlesFetch(bool aValue) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (NS_WARN_IF(!mInfo)) {
|
|
return;
|
|
}
|
|
|
|
mInfo->SetHandlesFetch(aValue);
|
|
}
|
|
|
|
RefPtr<GenericPromise> ServiceWorkerPrivate::SetSkipWaitingFlag() {
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(mInfo);
|
|
|
|
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
|
|
|
|
if (!swm) {
|
|
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
|
}
|
|
|
|
RefPtr<ServiceWorkerRegistrationInfo> regInfo =
|
|
swm->GetRegistration(mInfo->Principal(), mInfo->Scope());
|
|
|
|
if (!regInfo) {
|
|
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
|
}
|
|
|
|
mInfo->SetSkipWaitingFlag();
|
|
|
|
RefPtr<GenericPromise::Private> promise =
|
|
new GenericPromise::Private(__func__);
|
|
|
|
regInfo->TryToActivateAsync([promise] { promise->Resolve(true, __func__); });
|
|
|
|
return promise;
|
|
}
|
|
|
|
/* static */
|
|
void ServiceWorkerPrivate::UpdateRunning(int32_t aDelta, int32_t aFetchDelta) {
|
|
// Record values for time we were running at the current values
|
|
RefPtr<ServiceWorkerManager> manager(ServiceWorkerManager::GetInstance());
|
|
manager->RecordTelemetry(sRunningServiceWorkers, sRunningServiceWorkersFetch);
|
|
|
|
MOZ_ASSERT(((int64_t)sRunningServiceWorkers) + aDelta >= 0);
|
|
sRunningServiceWorkers += aDelta;
|
|
if (sRunningServiceWorkers > sRunningServiceWorkersMax) {
|
|
sRunningServiceWorkersMax = sRunningServiceWorkers;
|
|
LOG(("ServiceWorker max now %d", sRunningServiceWorkersMax));
|
|
Telemetry::ScalarSet(Telemetry::ScalarID::SERVICEWORKER_RUNNING_MAX,
|
|
u"All"_ns, sRunningServiceWorkersMax);
|
|
}
|
|
MOZ_ASSERT(((int64_t)sRunningServiceWorkersFetch) + aFetchDelta >= 0);
|
|
sRunningServiceWorkersFetch += aFetchDelta;
|
|
if (sRunningServiceWorkersFetch > sRunningServiceWorkersFetchMax) {
|
|
sRunningServiceWorkersFetchMax = sRunningServiceWorkersFetch;
|
|
LOG(("ServiceWorker Fetch max now %d", sRunningServiceWorkersFetchMax));
|
|
Telemetry::ScalarSet(Telemetry::ScalarID::SERVICEWORKER_RUNNING_MAX,
|
|
u"Fetch"_ns, sRunningServiceWorkersFetchMax);
|
|
}
|
|
LOG(("ServiceWorkers running now %d/%d", sRunningServiceWorkers,
|
|
sRunningServiceWorkersFetch));
|
|
}
|
|
|
|
void ServiceWorkerPrivate::CreationFailed() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(mControllerChild);
|
|
|
|
if (mRemoteWorkerData.remoteType().Find(SERVICEWORKER_REMOTE_TYPE) !=
|
|
kNotFound) {
|
|
Telemetry::AccumulateTimeDelta(
|
|
Telemetry::SERVICE_WORKER_ISOLATED_LAUNCH_TIME,
|
|
mServiceWorkerLaunchTimeStart);
|
|
} else {
|
|
Telemetry::AccumulateTimeDelta(Telemetry::SERVICE_WORKER_LAUNCH_TIME_2,
|
|
mServiceWorkerLaunchTimeStart);
|
|
}
|
|
|
|
Shutdown();
|
|
}
|
|
|
|
void ServiceWorkerPrivate::CreationSucceeded() {
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(mInfo);
|
|
MOZ_ASSERT(mControllerChild);
|
|
|
|
if (mRemoteWorkerData.remoteType().Find(SERVICEWORKER_REMOTE_TYPE) !=
|
|
kNotFound) {
|
|
Telemetry::AccumulateTimeDelta(
|
|
Telemetry::SERVICE_WORKER_ISOLATED_LAUNCH_TIME,
|
|
mServiceWorkerLaunchTimeStart);
|
|
} else {
|
|
Telemetry::AccumulateTimeDelta(Telemetry::SERVICE_WORKER_LAUNCH_TIME_2,
|
|
mServiceWorkerLaunchTimeStart);
|
|
}
|
|
|
|
RenewKeepAliveToken();
|
|
|
|
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
|
|
nsCOMPtr<nsIPrincipal> principal = mInfo->Principal();
|
|
RefPtr<ServiceWorkerRegistrationInfo> regInfo =
|
|
swm->GetRegistration(principal, mInfo->Scope());
|
|
if (regInfo) {
|
|
// If it's already set, we're done and the running count is already set
|
|
if (mHandlesFetch == Unknown) {
|
|
if (regInfo->GetActive()) {
|
|
mHandlesFetch =
|
|
regInfo->GetActive()->HandlesFetch() ? Enabled : Disabled;
|
|
if (mHandlesFetch == Enabled) {
|
|
UpdateRunning(0, 1);
|
|
}
|
|
}
|
|
// else we're likely still in Evaluating state, and don't know if it
|
|
// handles fetch. If so, defer updating the counter for Fetch until we
|
|
// finish evaluation. We already updated the Running count for All in
|
|
// SpawnWorkerIfNeeded().
|
|
}
|
|
}
|
|
}
|
|
|
|
void ServiceWorkerPrivate::ErrorReceived(const ErrorValue& aError) {
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(mInfo);
|
|
MOZ_ASSERT(mControllerChild);
|
|
|
|
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
|
|
MOZ_ASSERT(swm);
|
|
|
|
ServiceWorkerInfo* info = mInfo;
|
|
|
|
swm->HandleError(nullptr, info->Principal(), info->Scope(),
|
|
NS_ConvertUTF8toUTF16(info->ScriptSpec()), u""_ns, u""_ns,
|
|
u""_ns, 0, 0, nsIScriptError::errorFlag, JSEXN_ERR);
|
|
}
|
|
|
|
void ServiceWorkerPrivate::Terminated() {
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(mInfo);
|
|
MOZ_ASSERT(mControllerChild);
|
|
|
|
Shutdown();
|
|
}
|
|
|
|
void ServiceWorkerPrivate::RefreshRemoteWorkerData(
|
|
const RefPtr<ServiceWorkerRegistrationInfo>& aRegistration) {
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(mInfo);
|
|
|
|
ServiceWorkerData& serviceWorkerData =
|
|
mRemoteWorkerData.serviceWorkerData().get_ServiceWorkerData();
|
|
serviceWorkerData.descriptor() = mInfo->Descriptor().ToIPC();
|
|
serviceWorkerData.registrationDescriptor() =
|
|
aRegistration->Descriptor().ToIPC();
|
|
}
|
|
|
|
RefPtr<FetchServicePromises> ServiceWorkerPrivate::SetupNavigationPreload(
|
|
nsCOMPtr<nsIInterceptedChannel>& aChannel,
|
|
const RefPtr<ServiceWorkerRegistrationInfo>& aRegistration) {
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
AssertIsOnMainThread();
|
|
|
|
// create IPC request from the intercepted channel.
|
|
auto result = GetIPCInternalRequest(aChannel);
|
|
if (result.isErr()) {
|
|
return nullptr;
|
|
}
|
|
IPCInternalRequest ipcRequest = result.unwrap();
|
|
|
|
// Step 1. Clone the request for preload
|
|
// Create the InternalResponse from the created IPCRequest.
|
|
SafeRefPtr<InternalRequest> preloadRequest =
|
|
MakeSafeRefPtr<InternalRequest>(ipcRequest);
|
|
// Copy the request body from uploadChannel
|
|
nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(aChannel);
|
|
if (uploadChannel) {
|
|
nsCOMPtr<nsIInputStream> uploadStream;
|
|
nsresult rv = uploadChannel->CloneUploadStream(
|
|
&ipcRequest.bodySize(), getter_AddRefs(uploadStream));
|
|
// Fail to get the request's body, stop navigation preload by returning
|
|
// nullptr.
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return FetchService::NetworkErrorResponse(rv);
|
|
}
|
|
preloadRequest->SetBody(uploadStream, ipcRequest.bodySize());
|
|
}
|
|
|
|
// Set SkipServiceWorker for the navigation preload request
|
|
preloadRequest->SetSkipServiceWorker();
|
|
|
|
// Step 2. Append Service-Worker-Navigation-Preload header with
|
|
// registration->GetNavigationPreloadState().headerValue() on
|
|
// request's header list.
|
|
IgnoredErrorResult err;
|
|
auto headersGuard = preloadRequest->Headers()->Guard();
|
|
preloadRequest->Headers()->SetGuard(HeadersGuardEnum::None, err);
|
|
preloadRequest->Headers()->Append(
|
|
"Service-Worker-Navigation-Preload"_ns,
|
|
aRegistration->GetNavigationPreloadState().headerValue(), err);
|
|
preloadRequest->Headers()->SetGuard(headersGuard, err);
|
|
|
|
// Step 3. Perform fetch through FetchService with the cloned request
|
|
if (!err.Failed()) {
|
|
nsCOMPtr<nsIChannel> underlyingChannel;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
aChannel->GetChannel(getter_AddRefs(underlyingChannel)));
|
|
RefPtr<FetchService> fetchService = FetchService::GetInstance();
|
|
return fetchService->Fetch(AsVariant(FetchService::NavigationPreloadArgs{
|
|
std::move(preloadRequest), underlyingChannel}));
|
|
}
|
|
return FetchService::NetworkErrorResponse(NS_ERROR_UNEXPECTED);
|
|
}
|
|
|
|
void ServiceWorkerPrivate::Shutdown() {
|
|
AssertIsOnMainThread();
|
|
|
|
if (mControllerChild) {
|
|
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
|
|
|
|
MOZ_ASSERT(swm,
|
|
"All Service Workers should start shutting down before the "
|
|
"ServiceWorkerManager does!");
|
|
|
|
auto shutdownStateId = swm->MaybeInitServiceWorkerShutdownProgress();
|
|
|
|
RefPtr<GenericNonExclusivePromise> promise =
|
|
ShutdownInternal(shutdownStateId);
|
|
swm->BlockShutdownOn(promise, shutdownStateId);
|
|
}
|
|
|
|
MOZ_ASSERT(!mControllerChild);
|
|
}
|
|
|
|
RefPtr<GenericNonExclusivePromise> ServiceWorkerPrivate::ShutdownInternal(
|
|
uint32_t aShutdownStateId) {
|
|
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(aShutdownStateId),
|
|
[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;
|
|
|
|
// Update here, since Evaluation failures directly call ShutdownInternal
|
|
UpdateRunning(-1, mHandlesFetch == Enabled ? -1 : 0);
|
|
|
|
return promise;
|
|
}
|
|
|
|
nsresult ServiceWorkerPrivate::ExecServiceWorkerOp(
|
|
ServiceWorkerOpArgs&& aArgs,
|
|
std::function<void(ServiceWorkerOpResult&&)>&& aSuccessCallback,
|
|
std::function<void()>&& aFailureCallback) {
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(
|
|
aArgs.type() !=
|
|
ServiceWorkerOpArgs::TParentToChildServiceWorkerFetchEventOpArgs,
|
|
"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<ServiceWorkerPrivate> self = this;
|
|
RefPtr<RAIIActorPtrHolder> holder = mControllerChild;
|
|
RefPtr<KeepAliveToken> token =
|
|
aArgs.type() == ServiceWorkerOpArgs::TServiceWorkerTerminateWorkerOpArgs
|
|
? nullptr
|
|
: 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(
|
|
GetCurrentSerialEventTarget(), __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 mozilla::dom
|