/* -*- 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/. */
// 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);
// 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);
// 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);
// request's priority is not copied by the new Request() constructor used by // a fetch() call while request's internal priority is. So let's use the // default, otherwise a fetch(event.request) from a worker on an intercepted // fetch event would adjust priority twice. // https://fetch.spec.whatwg.org/#dom-global-fetch // https://fetch.spec.whatwg.org/#dom-request
RequestPriority requestPriority = RequestPriority::Auto;
// 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, internalPriority, referrer,
referrerPolicy, environmentReferrerPolicy, requestMode,
requestCredentials, cacheMode, requestRedirect, requestPriority,
integrity, false, fragment, principalInfo, interceptionPrincipalInfo,
contentPolicyType, redirectChain, isThirdPartyChannel, embedderPolicy);
}
// 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
}
// We can populate the partitionKey and the fingerprinting protection // overrides using the originAttribute of the principal. If it has // partitionKey set, It's a 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.
Maybe<RFPTargetSet> overriddenFingerprintingSettingsArg;
Maybe<RFPTargetSet> overriddenFingerprintingSettings;
nsCOMPtr<nsIURI> firstPartyURI; bool foreignByAncestorContext = false; bool isOn3PCBExceptionList = false; if (!principal->OriginAttributesRef().mPartitionKey.IsEmpty()) {
net::CookieJarSettings::Cast(cookieJarSettings)
->SetPartitionKey(principal->OriginAttributesRef().mPartitionKey);
// The service worker is for a third-party context, we get first-party // domain from the partitionKey and the third-party domain from the // principal of the service worker. Then, we can get the fingerprinting // protection overrides using them.
nsAutoString scheme;
nsAutoString pkBaseDomain;
int32_t unused; bool _foreignByAncestorContext;
RefPtr<net::CookieService> csSingleton =
net::CookieService::GetSingleton();
isOn3PCBExceptionList =
csSingleton->ThirdPartyCookieBlockingExceptionsRef()
.CheckExceptionForURIs(firstPartyURI, uri);
}
}
} elseif (!principal->OriginAttributesRef().mFirstPartyDomain.IsEmpty()) { // Using the first party domain to know the context of the service worker. // We will run into here if FirstPartyIsolation is enabled. In this case, // the PartitionKey won't get populated. // Because the service worker is only available in secure contexts, so we // don't need to consider http and only use https as scheme to create // the first-party URI
rv = NS_NewURI(
getter_AddRefs(firstPartyURI),
u"https://"_ns + principal->OriginAttributesRef().mFirstPartyDomain); if (NS_SUCCEEDED(rv)) { // If the first party domain is not a third-party domain, the service // worker is running in first-party context. bool isThirdParty;
rv = principal->IsThirdPartyURI(firstPartyURI, &isThirdParty);
NS_ENSURE_SUCCESS(rv, rv);
// The service worker is for a first-party context, we can use the uri of // the service worker as the first-party domain to get the fingerprinting // protection overrides.
overriddenFingerprintingSettings =
nsRFPService::GetOverriddenFingerprintingSettingsForURI(uri, nullptr);
if (overriddenFingerprintingSettings.isSome()) {
overriddenFingerprintingSettingsArg.emplace(
overriddenFingerprintingSettings.ref());
}
}
// Firefox doesn't support service workers in PBM. bool isPBM = principal->GetIsInPrivateBrowsing(); if (ContentBlockingAllowList::Check(principal, isPBM)) {
net::CookieJarSettings::Cast(cookieJarSettings)
->SetIsOnContentBlockingAllowList(true);
}
bool shouldResistFingerprinting =
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. The WorkerPrivate's " "ShouldResistFingerprinting function for the ServiceWorker depends " "on this boolean and will also consider an explicit RFPTarget.",
RFPTarget::IsAlwaysEnabledForPrecompute) &&
!nsContentUtils::ETPSaysShouldNotResistFingerprinting(cookieJarSettings,
isPBM);
if (shouldResistFingerprinting && NS_SUCCEEDED(rv) && firstPartyURI) { auto rfpKey = nsRFPService::GenerateKeyForServiceWorker(
firstPartyURI, principal, foreignByAncestorContext); if (rfpKey.isSome()) {
net::CookieJarSettings::Cast(cookieJarSettings)
->SetFingerprintingRandomizationKey(rfpKey.ref());
}
}
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();
mClientInfo = ClientManager::CreateInfo(
ClientType::Serviceworker, // The partitioned principal for ServiceWorkers is currently always // partitioned and so we only use it when in a third party context.
isThirdPartyContextToTopWindow ? partitionedPrincipal : principal); if (NS_WARN_IF(!mClientInfo.isSome())) { return NS_ERROR_DOM_INVALID_STATE_ERR;
}
// The RemoteWorkerData CTOR doesn't allow to set the referrerInfo via // already_AddRefed<>. Let's set it to null. /* referrerInfo */ nullptr,
storageAccess, isThirdPartyContextToTopWindow, shouldResistFingerprinting,
overriddenFingerprintingSettingsArg, isOn3PCBExceptionList, // Origin trials are associated to a window, so it doesn't make sense on // service workers.
OriginTrials(), std::move(serviceWorkerData), regInfo->AgentClusterId(),
remoteType.unwrap());
// This fills in the rest of mRemoteWorkerData.serviceWorkerData().
RefreshRemoteWorkerData(regInfo);
return NS_OK;
}
void ServiceWorkerPrivate::RegenerateClientInfo() { // inductively, this object can only still be alive after Initialize() if the // mClientInfo was correctly initialized.
MOZ_DIAGNOSTIC_ASSERT(mClientInfo.isSome());
/** * We need to capture the actor associated with the current Service Worker so * we can terminate it if script evaluation failed.
*/
nsresult rv = SpawnWorkerIfNeeded(aLifetimeExtension);
if (NS_WARN_IF(NS_FAILED(rv))) {
aCallback->SetResult(false);
aCallback->Run();
/** * 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();
});
// 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();
});
// We don't need to spawn if we already have a spawned, non-terminated worker. if (mControllerChild) { // We only need to renew the keepalive token if we actually want to extend // the worker's lifetime; we don't for termination requests. if (aLifetimeExtension.LifetimeExtendsIntoTheFuture()) {
RenewKeepAliveToken(aLifetimeExtension);
} return NS_OK;
}
if (!mInfo) { return NS_ERROR_DOM_INVALID_STATE_ERR;
}
// Don't spawn the ServiceWorker if we don't want to extend its life. if (NS_WARN_IF(!aLifetimeExtension.LifetimeExtendsIntoTheFuture())) { return NS_ERROR_DOM_TIMEOUT_ERR;
}
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;
}
}
if (NS_WARN_IF(!regInfo)) { return NS_ERROR_DOM_INVALID_STATE_ERR;
}
RefreshRemoteWorkerData(regInfo);
mLaunchCount++;
RefPtr<RemoteWorkerControllerChild> controllerChild = new RemoteWorkerControllerChild(this);
if (NS_WARN_IF(!bgChild->SendPRemoteWorkerControllerConstructor(
controllerChild, mRemoteWorkerData))) { return NS_ERROR_DOM_INVALID_STATE_ERR;
}
mPendingSpawnLifetime = aLifetimeExtension;
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(
Maybe<RefPtr<Promise>> aMaybePromise) {
MOZ_ASSERT(NS_IsMainThread());
mIdleWorkerTimer->Cancel();
mIdleDeadline = TimeStamp(); // We call the shutdown method prior to dropping mIdleKeepAliveToken in order // to ensure that the passed-in promise tracks the shutdown of the current // worker. // // More detail: Dropping the token can cause re-entrance to this method via // ReleaseToken if it is not already the method calling. Shutdown() is // idempotent except for the promise we pass in; it will only be chained to // track the actual termination if mControllerChild is not null. On the // second call when mControllerChild is null, it will resolved immediately // with undefined. The call from ReleaseToken does not pass a Promise and // does not care, so it goes second. // // We of course could hold onto the underlying shutdown promise until it // resolves so that new calls could chain, but because it's conceptually // possible to have multiple spawns and shutdowns in flight and our promise // argument is really only for testing / devtools where we only expect a // single actively involved party at a time, this way works sufficiently.
Shutdown(std::move(aMaybePromise)); // As per the above, this may potentially
mIdleKeepAliveToken = nullptr;
}
nsresult rv = ExecServiceWorkerOp(
ServiceWorkerUpdateStateOpArgs(aState), // Lifecycle events potentially update the lifetime for ServiceWorkers // controlling a page, but there's no need to update the lifetime to tell // a SW that its state has changed.
ServiceWorkerLifetimeExtension(NoLifetimeExtension{}),
[](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()));
}
// 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(
ServiceWorkerLifetimeExtension(FullLifetimeExtension{}));
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(
ServiceWorkerLifetimeExtension(FullLifetimeExtension{}));
mIdleWorkerTimer->Cancel();
}
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(ServiceWorkerLifetimeExtension(FullLifetimeExtension{}));
} else {
TerminateWorker();
}
}
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*);
// Release ServiceWorkerPrivate's token, since the grace period has ended.
mIdleKeepAliveToken = nullptr; // Null out our deadline as well.
mIdleDeadline = TimeStamp();
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));
}
}
// 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( const ServiceWorkerLifetimeExtension& aLifetimeExtension) { // 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(aLifetimeExtension);
}
if (!mIdleKeepAliveToken) {
mIdleKeepAliveToken = new KeepAliveToken(this);
}
}
void ServiceWorkerPrivate::ResetIdleTimeout( const ServiceWorkerLifetimeExtension& aLifetimeExtension) {
TimeStamp now = TimeStamp::NowLoRes();
TimeStamp existing = mIdleDeadline; // Normalize the extension, returning a Null TimeStamp if the lifetime // extension does not actually extend our lifetime.
TimeStamp normalizedExtension = aLifetimeExtension.match( // No extension means no extension!
[](const NoLifetimeExtension& nle) { return TimeStamp(); },
[&existing, &now](const PropagatedLifetimeExtension& ple) { // Ignore null deadlines or deadlines that are in the past. if (ple.mDeadline.IsNull() || ple.mDeadline < now) { return TimeStamp();
} // Use this new deadline if our existing deadline is null or the // received deadline is after our current deadline. if (existing.IsNull() || ple.mDeadline > existing) { return ple.mDeadline;
} // (This means our existing deadline extends further into the future so // we don't want to change our deadline.) return TimeStamp();
},
[&now](const FullLifetimeExtension& fle) { return now + TimeDuration::FromMilliseconds(Preferences::GetInt( "dom.serviceWorkers.idle_timeout"));
});
if (normalizedExtension.IsNull()) { // Convert the unlikely situation where we are trying to reset the timeout // without extension and where we have no existing timeout into a 0 timeout. // This is important because we don't want to let the ServiceWorker live // forever!
MOZ_ASSERT(!existing.IsNull()); if (NS_WARN_IF(existing.IsNull())) {
normalizedExtension = now;
} else { // Return without altering the deadline or churning the timer. return;
}
}
mIdleDeadline = normalizedExtension;
nsCOMPtr<nsITimerCallback> cb = new ServiceWorkerPrivateTimerCallback( this, &ServiceWorkerPrivate::NoteIdleWorkerCallback); // We don't need high resolution but TimeDuration provides better type safety.
DebugOnly<nsresult> rv = mIdleWorkerTimer->InitHighResolutionWithCallback(
cb, mIdleDeadline - now, nsITimer::TYPE_ONE_SHOT);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
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. elseif (mInfo) {
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); if (swm) {
swm->WorkerIsIdle(mInfo);
}
}
}
}
// 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();
}
if (!regInfo) { return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
}
mInfo->SetSkipWaitingFlag();
RefPtr<GenericPromise::Private> promise = new GenericPromise::Private(__func__);
// The ServiceWorker calling skipWaiting on itself is not a basis for lifetime // extension on its own. `TryToActivate` will upgrade the lifetime to a full // extension iff there are any controlled pages. auto lifetime = ServiceWorkerLifetimeExtension(NoLifetimeExtension{});
/* 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));
}
MOZ_ASSERT(((int64_t)sRunningServiceWorkersFetch) + aFetchDelta >= 0);
sRunningServiceWorkersFetch += aFetchDelta; if (sRunningServiceWorkersFetch > sRunningServiceWorkersFetchMax) {
sRunningServiceWorkersFetchMax = sRunningServiceWorkersFetch;
LOG(("ServiceWorker Fetch max now %d", sRunningServiceWorkersFetchMax));
}
LOG(("ServiceWorkers running now %d/%d", sRunningServiceWorkers,
sRunningServiceWorkersFetch));
}
// It's possible for a request to terminate the worker to happen while the // worker is starting up, in which case we do not want to renew the keepalive // timer and we probably don't want to update the telemetry below either. if (NS_WARN_IF(!mControllerChild)) {
mPendingSpawnLifetime =
ServiceWorkerLifetimeExtension(NoLifetimeExtension{}); return;
}
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().
}
}
}
// 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);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.