mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 22:01:30 +00:00
cd8b8939b9
Differential Revision: https://phabricator.services.mozilla.com/D80860
496 lines
16 KiB
C++
496 lines
16 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 "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, EmptyCString(), &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(EmptyCString());
|
|
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
|