summaryrefslogtreecommitdiffstats
path: root/dom/serviceworkers/FetchEventOpChild.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/serviceworkers/FetchEventOpChild.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/serviceworkers/FetchEventOpChild.cpp')
-rw-r--r--dom/serviceworkers/FetchEventOpChild.cpp629
1 files changed, 629 insertions, 0 deletions
diff --git a/dom/serviceworkers/FetchEventOpChild.cpp b/dom/serviceworkers/FetchEventOpChild.cpp
new file mode 100644
index 0000000000..b4a74770cd
--- /dev/null
+++ b/dom/serviceworkers/FetchEventOpChild.cpp
@@ -0,0 +1,629 @@
+/* -*- 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 "FetchEventOpChild.h"
+
+#include <utility>
+
+#include "MainThreadUtils.h"
+#include "nsContentPolicyUtils.h"
+#include "nsContentUtils.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsIChannel.h"
+#include "nsIConsoleReportCollector.h"
+#include "nsIContentPolicy.h"
+#include "nsIInputStream.h"
+#include "nsILoadInfo.h"
+#include "nsINetworkInterceptController.h"
+#include "nsIObserverService.h"
+#include "nsIScriptError.h"
+#include "nsISupportsImpl.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsProxyRelease.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+
+#include "ServiceWorkerPrivate.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/dom/FetchService.h"
+#include "mozilla/dom/InternalHeaders.h"
+#include "mozilla/dom/InternalResponse.h"
+#include "mozilla/dom/PRemoteWorkerControllerChild.h"
+#include "mozilla/dom/ServiceWorkerRegistrationInfo.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozilla/dom/RemoteWorkerControllerChild.h"
+
+namespace mozilla::dom {
+
+namespace {
+
+bool CSPPermitsResponse(nsILoadInfo* aLoadInfo,
+ SafeRefPtr<InternalResponse> aResponse,
+ const nsACString& aWorkerScriptSpec) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aLoadInfo);
+
+ nsCString url = aResponse->GetUnfilteredURL();
+ if (url.IsEmpty()) {
+ // Synthetic response.
+ url = aWorkerScriptSpec;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), url, nullptr, nullptr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ int16_t decision = nsIContentPolicy::ACCEPT;
+ rv = NS_CheckContentLoadPolicy(uri, aLoadInfo, &decision);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ return decision == nsIContentPolicy::ACCEPT;
+}
+
+void AsyncLog(nsIInterceptedChannel* aChannel, const nsACString& aScriptSpec,
+ uint32_t aLineNumber, uint32_t aColumnNumber,
+ const nsACString& aMessageName, nsTArray<nsString>&& aParams) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aChannel);
+
+ nsCOMPtr<nsIConsoleReportCollector> reporter =
+ aChannel->GetConsoleReportCollector();
+
+ if (reporter) {
+ // NOTE: is appears that `const nsTArray<nsString>&` is required for
+ // nsIConsoleReportCollector::AddConsoleReport to resolve to the correct
+ // overload.
+ const nsTArray<nsString> params = std::move(aParams);
+
+ reporter->AddConsoleReport(
+ nsIScriptError::errorFlag, "Service Worker Interception"_ns,
+ nsContentUtils::eDOM_PROPERTIES, aScriptSpec, aLineNumber,
+ aColumnNumber, aMessageName, params);
+ }
+}
+
+class SynthesizeResponseWatcher final : public nsIInterceptedBodyCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ SynthesizeResponseWatcher(
+ const nsMainThreadPtrHandle<nsIInterceptedChannel>& aInterceptedChannel,
+ const nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
+ const bool aIsNonSubresourceRequest,
+ FetchEventRespondWithClosure&& aClosure, nsAString&& aRequestURL)
+ : mInterceptedChannel(aInterceptedChannel),
+ mRegistration(aRegistration),
+ mIsNonSubresourceRequest(aIsNonSubresourceRequest),
+ mClosure(std::move(aClosure)),
+ mRequestURL(std::move(aRequestURL)) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mInterceptedChannel);
+ MOZ_ASSERT(mRegistration);
+ }
+
+ NS_IMETHOD
+ BodyComplete(nsresult aRv) override {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mInterceptedChannel);
+
+ if (NS_WARN_IF(NS_FAILED(aRv))) {
+ AsyncLog(mInterceptedChannel, mClosure.respondWithScriptSpec(),
+ mClosure.respondWithLineNumber(),
+ mClosure.respondWithColumnNumber(),
+ "InterceptionFailedWithURL"_ns, {mRequestURL});
+
+ CancelInterception(NS_ERROR_INTERCEPTION_FAILED);
+
+ return NS_OK;
+ }
+
+ nsresult rv = mInterceptedChannel->FinishSynthesizedResponse();
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ CancelInterception(rv);
+ }
+
+ mInterceptedChannel = nullptr;
+
+ return NS_OK;
+ }
+
+ // See FetchEventOpChild::MaybeScheduleRegistrationUpdate() for comments.
+ void CancelInterception(nsresult aStatus) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mInterceptedChannel);
+ MOZ_ASSERT(mRegistration);
+
+ mInterceptedChannel->CancelInterception(aStatus);
+
+ if (mIsNonSubresourceRequest) {
+ mRegistration->MaybeScheduleUpdate();
+ } else {
+ mRegistration->MaybeScheduleTimeCheckAndUpdate();
+ }
+
+ mInterceptedChannel = nullptr;
+ mRegistration = nullptr;
+ }
+
+ private:
+ ~SynthesizeResponseWatcher() {
+ if (NS_WARN_IF(mInterceptedChannel)) {
+ CancelInterception(NS_ERROR_DOM_ABORT_ERR);
+ }
+ }
+
+ nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
+ nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
+ const bool mIsNonSubresourceRequest;
+ const FetchEventRespondWithClosure mClosure;
+ const nsString mRequestURL;
+};
+
+NS_IMPL_ISUPPORTS(SynthesizeResponseWatcher, nsIInterceptedBodyCallback)
+
+} // anonymous namespace
+
+/* static */ RefPtr<GenericPromise> FetchEventOpChild::SendFetchEvent(
+ PRemoteWorkerControllerChild* aManager,
+ ParentToParentServiceWorkerFetchEventOpArgs&& aArgs,
+ nsCOMPtr<nsIInterceptedChannel> aInterceptedChannel,
+ RefPtr<ServiceWorkerRegistrationInfo> aRegistration,
+ RefPtr<FetchServicePromises>&& aPreloadResponseReadyPromises,
+ RefPtr<KeepAliveToken>&& aKeepAliveToken) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aManager);
+ MOZ_ASSERT(aInterceptedChannel);
+ MOZ_ASSERT(aKeepAliveToken);
+
+ FetchEventOpChild* actor = new FetchEventOpChild(
+ std::move(aArgs), std::move(aInterceptedChannel),
+ std::move(aRegistration), std::move(aPreloadResponseReadyPromises),
+ std::move(aKeepAliveToken));
+
+ actor->mWasSent = true;
+ RefPtr<GenericPromise> promise = actor->mPromiseHolder.Ensure(__func__);
+ Unused << aManager->SendPFetchEventOpConstructor(actor, actor->mArgs);
+ // NOTE: actor may have been destroyed
+ return promise;
+}
+
+FetchEventOpChild::~FetchEventOpChild() {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mInterceptedChannelHandled);
+ MOZ_DIAGNOSTIC_ASSERT(mPromiseHolder.IsEmpty());
+}
+
+FetchEventOpChild::FetchEventOpChild(
+ ParentToParentServiceWorkerFetchEventOpArgs&& aArgs,
+ nsCOMPtr<nsIInterceptedChannel>&& aInterceptedChannel,
+ RefPtr<ServiceWorkerRegistrationInfo>&& aRegistration,
+ RefPtr<FetchServicePromises>&& aPreloadResponseReadyPromises,
+ RefPtr<KeepAliveToken>&& aKeepAliveToken)
+ : mArgs(std::move(aArgs)),
+ mInterceptedChannel(std::move(aInterceptedChannel)),
+ mRegistration(std::move(aRegistration)),
+ mKeepAliveToken(std::move(aKeepAliveToken)),
+ mPreloadResponseReadyPromises(std::move(aPreloadResponseReadyPromises)) {
+ if (mPreloadResponseReadyPromises) {
+ // This promise should be configured to use synchronous dispatch, so if it's
+ // already resolved when we run this code then the callback will be called
+ // synchronously and pass the preload response with the constructor message.
+ //
+ // Note that it's fine to capture the this pointer in the callbacks because
+ // we disconnect the request in Recv__delete__().
+ mPreloadResponseReadyPromises->GetResponseAvailablePromise()
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [this](FetchServiceResponse&& aResponse) {
+ if (!mWasSent) {
+ // The actor wasn't sent yet, we can still send the preload
+ // response with it.
+ mArgs.preloadResponse() =
+ Some(aResponse->ToParentToParentInternalResponse());
+ } else {
+ // It's too late to send the preload response with the actor, we
+ // have to send it in a separate message.
+ SendPreloadResponse(
+ aResponse->ToParentToParentInternalResponse());
+ }
+ mPreloadResponseAvailablePromiseRequestHolder.Complete();
+ },
+ [this](const CopyableErrorResult&) {
+ mPreloadResponseAvailablePromiseRequestHolder.Complete();
+ })
+ ->Track(mPreloadResponseAvailablePromiseRequestHolder);
+
+ mPreloadResponseReadyPromises->GetResponseTimingPromise()
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [this](ResponseTiming&& aTiming) {
+ if (!mWasSent) {
+ // The actor wasn't sent yet, we can still send the preload
+ // response timing with it.
+ mArgs.preloadResponseTiming() = Some(std::move(aTiming));
+ } else {
+ SendPreloadResponseTiming(aTiming);
+ }
+ mPreloadResponseTimingPromiseRequestHolder.Complete();
+ },
+ [this](const CopyableErrorResult&) {
+ mPreloadResponseTimingPromiseRequestHolder.Complete();
+ })
+ ->Track(mPreloadResponseTimingPromiseRequestHolder);
+
+ mPreloadResponseReadyPromises->GetResponseEndPromise()
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [this](ResponseEndArgs&& aResponse) {
+ if (!mWasSent) {
+ // The actor wasn't sent yet, we can still send the preload
+ // response end args with it.
+ mArgs.preloadResponseEndArgs() = Some(std::move(aResponse));
+ } else {
+ // It's too late to send the preload response end with the
+ // actor, we have to send it in a separate message.
+ SendPreloadResponseEnd(aResponse);
+ }
+ mPreloadResponseReadyPromises = nullptr;
+ mPreloadResponseEndPromiseRequestHolder.Complete();
+ },
+ [this](const CopyableErrorResult&) {
+ mPreloadResponseReadyPromises = nullptr;
+ mPreloadResponseEndPromiseRequestHolder.Complete();
+ })
+ ->Track(mPreloadResponseEndPromiseRequestHolder);
+ }
+}
+
+mozilla::ipc::IPCResult FetchEventOpChild::RecvAsyncLog(
+ const nsCString& aScriptSpec, const uint32_t& aLineNumber,
+ const uint32_t& aColumnNumber, const nsCString& aMessageName,
+ nsTArray<nsString>&& aParams) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mInterceptedChannel);
+
+ AsyncLog(mInterceptedChannel, aScriptSpec, aLineNumber, aColumnNumber,
+ aMessageName, std::move(aParams));
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult FetchEventOpChild::RecvRespondWith(
+ ParentToParentFetchEventRespondWithResult&& aResult) {
+ AssertIsOnMainThread();
+
+ RefPtr<RemoteWorkerControllerChild> mgr =
+ static_cast<RemoteWorkerControllerChild*>(Manager());
+
+ mInterceptedChannel->SetRemoteWorkerLaunchStart(
+ mgr->GetRemoteWorkerLaunchStart());
+ mInterceptedChannel->SetRemoteWorkerLaunchEnd(
+ mgr->GetRemoteWorkerLaunchEnd());
+
+ switch (aResult.type()) {
+ case ParentToParentFetchEventRespondWithResult::
+ TParentToParentSynthesizeResponseArgs:
+ mInterceptedChannel->SetFetchHandlerStart(
+ aResult.get_ParentToParentSynthesizeResponseArgs()
+ .timeStamps()
+ .fetchHandlerStart());
+ mInterceptedChannel->SetFetchHandlerFinish(
+ aResult.get_ParentToParentSynthesizeResponseArgs()
+ .timeStamps()
+ .fetchHandlerFinish());
+ SynthesizeResponse(
+ std::move(aResult.get_ParentToParentSynthesizeResponseArgs()));
+ break;
+ case ParentToParentFetchEventRespondWithResult::TResetInterceptionArgs:
+ mInterceptedChannel->SetFetchHandlerStart(
+ aResult.get_ResetInterceptionArgs().timeStamps().fetchHandlerStart());
+ mInterceptedChannel->SetFetchHandlerFinish(
+ aResult.get_ResetInterceptionArgs()
+ .timeStamps()
+ .fetchHandlerFinish());
+ ResetInterception(false);
+ break;
+ case ParentToParentFetchEventRespondWithResult::TCancelInterceptionArgs:
+ mInterceptedChannel->SetFetchHandlerStart(
+ aResult.get_CancelInterceptionArgs()
+ .timeStamps()
+ .fetchHandlerStart());
+ mInterceptedChannel->SetFetchHandlerFinish(
+ aResult.get_CancelInterceptionArgs()
+ .timeStamps()
+ .fetchHandlerFinish());
+ CancelInterception(aResult.get_CancelInterceptionArgs().status());
+ break;
+ default:
+ MOZ_CRASH("Unknown IPCFetchEventRespondWithResult type!");
+ break;
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult FetchEventOpChild::Recv__delete__(
+ const ServiceWorkerFetchEventOpResult& aResult) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mRegistration);
+
+ if (NS_WARN_IF(!mInterceptedChannelHandled)) {
+ MOZ_ASSERT(NS_FAILED(aResult.rv()));
+ NS_WARNING(
+ "Failed to handle intercepted network request; canceling "
+ "interception!");
+
+ CancelInterception(aResult.rv());
+ }
+
+ mPromiseHolder.ResolveIfExists(true, __func__);
+
+ // FetchEvent is completed.
+ // Disconnect preload response related promises and cancel the preload.
+ mPreloadResponseAvailablePromiseRequestHolder.DisconnectIfExists();
+ mPreloadResponseTimingPromiseRequestHolder.DisconnectIfExists();
+ mPreloadResponseEndPromiseRequestHolder.DisconnectIfExists();
+ if (mPreloadResponseReadyPromises) {
+ RefPtr<FetchService> fetchService = FetchService::GetInstance();
+ fetchService->CancelFetch(std::move(mPreloadResponseReadyPromises));
+ }
+
+ /**
+ * This corresponds to the "Fire Functional Event" algorithm's step 9:
+ *
+ * "If the time difference in seconds calculated by the current time minus
+ * registration's last update check time is greater than 84600, invoke Soft
+ * Update algorithm with registration."
+ *
+ * TODO: this is probably being called later than it should be; it should be
+ * called ASAP after dispatching the FetchEvent.
+ */
+ mRegistration->MaybeScheduleTimeCheckAndUpdate();
+
+ return IPC_OK();
+}
+
+void FetchEventOpChild::ActorDestroy(ActorDestroyReason) {
+ AssertIsOnMainThread();
+
+ // If `Recv__delete__` was called, it would have resolved the promise already.
+ mPromiseHolder.RejectIfExists(NS_ERROR_DOM_ABORT_ERR, __func__);
+
+ if (NS_WARN_IF(!mInterceptedChannelHandled)) {
+ Unused << Recv__delete__(NS_ERROR_DOM_ABORT_ERR);
+ }
+}
+
+nsresult FetchEventOpChild::StartSynthesizedResponse(
+ ParentToParentSynthesizeResponseArgs&& aArgs) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mInterceptedChannel);
+ MOZ_ASSERT(!mInterceptedChannelHandled);
+ MOZ_ASSERT(mRegistration);
+
+ /**
+ * TODO: moving the IPCInternalResponse won't do anything right now because
+ * there isn't a prefect-forwarding or rvalue-ref-parameter overload of
+ * `InternalResponse::FromIPC().`
+ */
+ SafeRefPtr<InternalResponse> response =
+ InternalResponse::FromIPC(aArgs.internalResponse());
+ if (NS_WARN_IF(!response)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIChannel> underlyingChannel;
+ nsresult rv =
+ mInterceptedChannel->GetChannel(getter_AddRefs(underlyingChannel));
+ if (NS_WARN_IF(NS_FAILED(rv)) || NS_WARN_IF(!underlyingChannel)) {
+ return NS_FAILED(rv) ? rv : NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = underlyingChannel->LoadInfo();
+ if (!CSPPermitsResponse(loadInfo, response.clonePtr(),
+ mArgs.common().workerScriptSpec())) {
+ return NS_ERROR_CONTENT_BLOCKED;
+ }
+
+ MOZ_ASSERT(response->GetChannelInfo().IsInitialized());
+ ChannelInfo channelInfo = response->GetChannelInfo();
+ rv = mInterceptedChannel->SetChannelInfo(&channelInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_INTERCEPTION_FAILED;
+ }
+
+ rv = mInterceptedChannel->SynthesizeStatus(
+ response->GetUnfilteredStatus(), response->GetUnfilteredStatusText());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ AutoTArray<InternalHeaders::Entry, 5> entries;
+ response->UnfilteredHeaders()->GetEntries(entries);
+ for (auto& entry : entries) {
+ mInterceptedChannel->SynthesizeHeader(entry.mName, entry.mValue);
+ }
+
+ auto castLoadInfo = static_cast<mozilla::net::LoadInfo*>(loadInfo.get());
+ castLoadInfo->SynthesizeServiceWorkerTainting(response->GetTainting());
+
+ // Get the preferred alternative data type of the outer channel
+ nsAutoCString preferredAltDataType(""_ns);
+ nsCOMPtr<nsICacheInfoChannel> outerChannel =
+ do_QueryInterface(underlyingChannel);
+ if (outerChannel &&
+ !outerChannel->PreferredAlternativeDataTypes().IsEmpty()) {
+ preferredAltDataType.Assign(
+ outerChannel->PreferredAlternativeDataTypes()[0].type());
+ }
+
+ nsCOMPtr<nsIInputStream> body;
+ if (preferredAltDataType.Equals(response->GetAlternativeDataType())) {
+ body = response->TakeAlternativeBody();
+ }
+ if (!body) {
+ response->GetUnfilteredBody(getter_AddRefs(body));
+ } else {
+ Telemetry::ScalarAdd(Telemetry::ScalarID::SW_ALTERNATIVE_BODY_USED_COUNT,
+ 1);
+ }
+
+ // Propagate the URL to the content if the request mode is not "navigate".
+ // Note that, we only reflect the final URL if the response.redirected is
+ // false. We propagate all the URLs if the response.redirected is true.
+ const IPCInternalRequest& request = mArgs.common().internalRequest();
+ nsAutoCString responseURL;
+ if (request.requestMode() != RequestMode::Navigate) {
+ responseURL = response->GetUnfilteredURL();
+
+ // Similar to how we apply the request fragment to redirects automatically
+ // we also want to apply it automatically when propagating the response
+ // URL from a service worker interception. Currently response.url strips
+ // the fragment, so this will never conflict with an existing fragment
+ // on the response. In the future we will have to check for a response
+ // fragment and avoid overriding in that case.
+ if (!request.fragment().IsEmpty() && !responseURL.IsEmpty()) {
+ MOZ_ASSERT(!responseURL.Contains('#'));
+ responseURL.AppendLiteral("#");
+ responseURL.Append(request.fragment());
+ }
+ }
+
+ nsMainThreadPtrHandle<nsIInterceptedChannel> interceptedChannel(
+ new nsMainThreadPtrHolder<nsIInterceptedChannel>(
+ "nsIInterceptedChannel", mInterceptedChannel, false));
+
+ nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> registration(
+ new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(
+ "ServiceWorkerRegistrationInfo", mRegistration, false));
+
+ nsCString requestURL = request.urlList().LastElement();
+ if (!request.fragment().IsEmpty()) {
+ requestURL.AppendLiteral("#");
+ requestURL.Append(request.fragment());
+ }
+
+ RefPtr<SynthesizeResponseWatcher> watcher = new SynthesizeResponseWatcher(
+ interceptedChannel, registration,
+ mArgs.common().isNonSubresourceRequest(), std::move(aArgs.closure()),
+ NS_ConvertUTF8toUTF16(responseURL));
+
+ rv = mInterceptedChannel->StartSynthesizedResponse(
+ body, watcher, nullptr /* TODO */, responseURL, response->IsRedirected());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ if (obsService) {
+ obsService->NotifyObservers(underlyingChannel,
+ "service-worker-synthesized-response", nullptr);
+ }
+
+ return rv;
+}
+
+void FetchEventOpChild::SynthesizeResponse(
+ ParentToParentSynthesizeResponseArgs&& aArgs) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mInterceptedChannel);
+ MOZ_ASSERT(!mInterceptedChannelHandled);
+
+ nsresult rv = StartSynthesizedResponse(std::move(aArgs));
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ NS_WARNING("Failed to synthesize response!");
+
+ mInterceptedChannel->CancelInterception(rv);
+ }
+
+ mInterceptedChannelHandled = true;
+
+ MaybeScheduleRegistrationUpdate();
+}
+
+void FetchEventOpChild::ResetInterception(bool aBypass) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mInterceptedChannel);
+ MOZ_ASSERT(!mInterceptedChannelHandled);
+
+ nsresult rv = mInterceptedChannel->ResetInterception(aBypass);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ NS_WARNING("Failed to resume intercepted network request!");
+
+ mInterceptedChannel->CancelInterception(rv);
+ }
+
+ mInterceptedChannelHandled = true;
+
+ MaybeScheduleRegistrationUpdate();
+}
+
+void FetchEventOpChild::CancelInterception(nsresult aStatus) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mInterceptedChannel);
+ MOZ_ASSERT(!mInterceptedChannelHandled);
+ MOZ_ASSERT(NS_FAILED(aStatus));
+
+ // Report a navigation fault if this is a navigation (and we have an active
+ // worker, which should be the case in non-shutdown/content-process-crash
+ // situations).
+ RefPtr<ServiceWorkerInfo> mActive = mRegistration->GetActive();
+ if (mActive && mArgs.common().isNonSubresourceRequest()) {
+ mActive->ReportNavigationFault();
+ // Additional mitigations such as unregistering the registration are handled
+ // in ServiceWorkerRegistrationInfo::MaybeScheduleUpdate which will be
+ // called by MaybeScheduleRegistrationUpdate which gets called by our call
+ // to ResetInterception.
+ if (StaticPrefs::dom_serviceWorkers_mitigations_bypass_on_fault()) {
+ ResetInterception(true);
+ return;
+ }
+ }
+
+ mInterceptedChannel->CancelInterception(aStatus);
+ mInterceptedChannelHandled = true;
+
+ MaybeScheduleRegistrationUpdate();
+}
+
+/**
+ * This corresponds to the "Handle Fetch" algorithm's steps 20.3, 21.2, and
+ * 22.2:
+ *
+ * "If request is a non-subresource request, or request is a subresource
+ * request and the time difference in seconds calculated by the current time
+ * minus registration's last update check time is greater than 86400, invoke
+ * Soft Update algorithm with registration."
+ */
+void FetchEventOpChild::MaybeScheduleRegistrationUpdate() const {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mRegistration);
+ MOZ_ASSERT(mInterceptedChannelHandled);
+
+ if (mArgs.common().isNonSubresourceRequest()) {
+ mRegistration->MaybeScheduleUpdate();
+ } else {
+ mRegistration->MaybeScheduleTimeCheckAndUpdate();
+ }
+}
+
+} // namespace mozilla::dom