summaryrefslogtreecommitdiffstats
path: root/dom/serviceworkers/FetchEventOpChild.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/serviceworkers/FetchEventOpChild.cpp495
1 files changed, 495 insertions, 0 deletions
diff --git a/dom/serviceworkers/FetchEventOpChild.cpp b/dom/serviceworkers/FetchEventOpChild.cpp
new file mode 100644
index 0000000000..5b0e90bf25
--- /dev/null
+++ b/dom/serviceworkers/FetchEventOpChild.cpp
@@ -0,0 +1,495 @@
+/* -*- 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/Telemetry.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/ipc/BackgroundChild.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"
+
+namespace mozilla {
+
+using ipc::AutoIPCStream;
+using ipc::BackgroundChild;
+using ipc::PBackgroundChild;
+
+namespace dom {
+
+namespace {
+
+bool CSPPermitsResponse(nsILoadInfo* aLoadInfo, 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, ""_ns, &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,
+ ServiceWorkerFetchEventOpArgs&& aArgs,
+ nsCOMPtr<nsIInterceptedChannel> aInterceptedChannel,
+ RefPtr<ServiceWorkerRegistrationInfo> aRegistration,
+ 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(aKeepAliveToken));
+
+ Unused << aManager->SendPFetchEventOpConstructor(actor, actor->mArgs);
+
+ return actor->mPromiseHolder.Ensure(__func__);
+}
+
+FetchEventOpChild::~FetchEventOpChild() {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mInterceptedChannelHandled);
+ MOZ_DIAGNOSTIC_ASSERT(mPromiseHolder.IsEmpty());
+}
+
+FetchEventOpChild::FetchEventOpChild(
+ ServiceWorkerFetchEventOpArgs&& aArgs,
+ nsCOMPtr<nsIInterceptedChannel>&& aInterceptedChannel,
+ RefPtr<ServiceWorkerRegistrationInfo>&& aRegistration,
+ RefPtr<KeepAliveToken>&& aKeepAliveToken)
+ : mArgs(std::move(aArgs)),
+ mInterceptedChannel(std::move(aInterceptedChannel)),
+ mRegistration(std::move(aRegistration)),
+ mKeepAliveToken(std::move(aKeepAliveToken)) {}
+
+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(
+ IPCFetchEventRespondWithResult&& aResult) {
+ AssertIsOnMainThread();
+
+ switch (aResult.type()) {
+ case IPCFetchEventRespondWithResult::TIPCSynthesizeResponseArgs:
+ SynthesizeResponse(std::move(aResult.get_IPCSynthesizeResponseArgs()));
+ break;
+ case IPCFetchEventRespondWithResult::TResetInterceptionArgs:
+ ResetInterception();
+ break;
+ case IPCFetchEventRespondWithResult::TCancelInterceptionArgs:
+ 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__);
+
+ /**
+ * 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(
+ IPCSynthesizeResponseArgs&& 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().`
+ */
+ RefPtr<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, mArgs.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.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.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(IPCSynthesizeResponseArgs&& 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() {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mInterceptedChannel);
+ MOZ_ASSERT(!mInterceptedChannelHandled);
+
+ nsresult rv = mInterceptedChannel->ResetInterception();
+
+ 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));
+
+ 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.isNonSubresourceRequest()) {
+ mRegistration->MaybeScheduleUpdate();
+ } else {
+ mRegistration->MaybeScheduleTimeCheckAndUpdate();
+ }
+}
+
+} // namespace dom
+} // namespace mozilla