gecko-dev/dom/serviceworkers/ServiceWorkerOp.cpp
Nika Layzell 7eef772214 Bug 1773088 - Part 2: Stop requiring an actor to serialize StructuredCloneData over IPC, r=asuth
This is largely a straightforward find and replace of various methods, with the
unnecessary arguments removed and compiler errors fixed.

Differential Revision: https://phabricator.services.mozilla.com/D148532
2022-06-08 15:24:27 +00:00

1914 lines
62 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* -*- 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 "ServiceWorkerOp.h"
#include <utility>
#include "ServiceWorkerOpPromise.h"
#include "js/Exception.h" // JS::ExceptionStack, JS::StealPendingExceptionStack
#include "jsapi.h"
#include "nsCOMPtr.h"
#include "nsContentUtils.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsINamed.h"
#include "nsIPushErrorReporter.h"
#include "nsISupportsImpl.h"
#include "nsITimer.h"
#include "nsProxyRelease.h"
#include "nsServiceManagerUtils.h"
#include "nsTArray.h"
#include "nsThreadUtils.h"
#include "ServiceWorkerCloneData.h"
#include "ServiceWorkerShutdownState.h"
#include "mozilla/Assertions.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/OwningNonNull.h"
#include "mozilla/SchedulerGroup.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/Client.h"
#include "mozilla/dom/ExtendableMessageEventBinding.h"
#include "mozilla/dom/FetchEventBinding.h"
#include "mozilla/dom/FetchEventOpProxyChild.h"
#include "mozilla/dom/InternalHeaders.h"
#include "mozilla/dom/InternalRequest.h"
#include "mozilla/dom/InternalResponse.h"
#include "mozilla/dom/Notification.h"
#include "mozilla/dom/NotificationEvent.h"
#include "mozilla/dom/NotificationEventBinding.h"
#include "mozilla/dom/PerformanceTiming.h"
#include "mozilla/dom/PerformanceStorage.h"
#include "mozilla/dom/PushEventBinding.h"
#include "mozilla/dom/RemoteWorkerChild.h"
#include "mozilla/dom/RemoteWorkerService.h"
#include "mozilla/dom/Request.h"
#include "mozilla/dom/Response.h"
#include "mozilla/dom/RootedDictionary.h"
#include "mozilla/dom/SafeRefPtr.h"
#include "mozilla/dom/ServiceWorkerBinding.h"
#include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
#include "mozilla/dom/WorkerCommon.h"
#include "mozilla/dom/WorkerRef.h"
#include "mozilla/dom/WorkerScope.h"
#include "mozilla/extensions/ExtensionBrowser.h"
#include "mozilla/ipc/IPCStreamUtils.h"
#include "mozilla/net/MozURL.h"
namespace mozilla::dom {
namespace {
class ExtendableEventKeepAliveHandler final
: public ExtendableEvent::ExtensionsHandler,
public PromiseNativeHandler {
public:
NS_DECL_ISUPPORTS
static RefPtr<ExtendableEventKeepAliveHandler> Create(
RefPtr<ExtendableEventCallback> aCallback) {
MOZ_ASSERT(IsCurrentThreadRunningWorker());
RefPtr<ExtendableEventKeepAliveHandler> self =
new ExtendableEventKeepAliveHandler(std::move(aCallback));
self->mWorkerRef = StrongWorkerRef::Create(
GetCurrentThreadWorkerPrivate(), "ExtendableEventKeepAliveHandler",
[self]() { self->Cleanup(); });
if (NS_WARN_IF(!self->mWorkerRef)) {
return nullptr;
}
return self;
}
/**
* ExtendableEvent::ExtensionsHandler interface
*/
bool WaitOnPromise(Promise& aPromise) override {
if (!mAcceptingPromises) {
MOZ_ASSERT(!GetDispatchFlag());
MOZ_ASSERT(!mSelfRef, "We shouldn't be holding a self reference!");
return false;
}
if (!mSelfRef) {
MOZ_ASSERT(!mPendingPromisesCount);
mSelfRef = this;
}
++mPendingPromisesCount;
aPromise.AppendNativeHandler(this);
return true;
}
/**
* PromiseNativeHandler interface
*/
void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
ErrorResult& aRv) override {
RemovePromise(Resolved);
}
void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
ErrorResult& aRv) override {
RemovePromise(Rejected);
}
void MaybeDone() {
MOZ_ASSERT(IsCurrentThreadRunningWorker());
MOZ_ASSERT(!GetDispatchFlag());
if (mPendingPromisesCount) {
return;
}
if (mCallback) {
mCallback->FinishedWithResult(mRejected ? Rejected : Resolved);
mCallback = nullptr;
}
Cleanup();
}
private:
/**
* This class is useful for the case where pending microtasks will continue
* extending the event, which means that the event is not "done." For example:
*
* // `e` is an ExtendableEvent, `p` is a Promise
* e.waitUntil(p);
* p.then(() => e.waitUntil(otherPromise));
*/
class MaybeDoneRunner : public MicroTaskRunnable {
public:
explicit MaybeDoneRunner(RefPtr<ExtendableEventKeepAliveHandler> aHandler)
: mHandler(std::move(aHandler)) {}
void Run(AutoSlowOperation& /* unused */) override {
mHandler->MaybeDone();
}
private:
RefPtr<ExtendableEventKeepAliveHandler> mHandler;
};
explicit ExtendableEventKeepAliveHandler(
RefPtr<ExtendableEventCallback> aCallback)
: mCallback(std::move(aCallback)) {}
~ExtendableEventKeepAliveHandler() { Cleanup(); }
void Cleanup() {
MOZ_ASSERT(IsCurrentThreadRunningWorker());
if (mCallback) {
mCallback->FinishedWithResult(Rejected);
}
mSelfRef = nullptr;
mWorkerRef = nullptr;
mCallback = nullptr;
mAcceptingPromises = false;
}
void RemovePromise(ExtendableEventResult aResult) {
MOZ_ASSERT(IsCurrentThreadRunningWorker());
MOZ_DIAGNOSTIC_ASSERT(mPendingPromisesCount > 0);
// NOTE: mSelfRef can be nullptr here if MaybeCleanup() was just called
// before a promise settled. This can happen, for example, if the worker
// thread is being terminated for running too long, browser shutdown, etc.
mRejected |= (aResult == Rejected);
--mPendingPromisesCount;
if (mPendingPromisesCount || GetDispatchFlag()) {
return;
}
CycleCollectedJSContext* cx = CycleCollectedJSContext::Get();
MOZ_ASSERT(cx);
RefPtr<MaybeDoneRunner> r = new MaybeDoneRunner(this);
cx->DispatchToMicroTask(r.forget());
}
/**
* We start holding a self reference when the first extension promise is
* added, and this reference is released when the last promise settles or
* when the worker is shutting down.
*
* This is needed in the case that we're waiting indefinitely on a to-be-GC'ed
* promise that's no longer reachable and will never be settled.
*/
RefPtr<ExtendableEventKeepAliveHandler> mSelfRef;
RefPtr<StrongWorkerRef> mWorkerRef;
RefPtr<ExtendableEventCallback> mCallback;
uint32_t mPendingPromisesCount = 0;
bool mRejected = false;
bool mAcceptingPromises = true;
};
NS_IMPL_ISUPPORTS0(ExtendableEventKeepAliveHandler)
nsresult DispatchExtendableEventOnWorkerScope(
JSContext* aCx, WorkerGlobalScope* aWorkerScope, ExtendableEvent* aEvent,
RefPtr<ExtendableEventCallback> aCallback) {
MOZ_ASSERT(aCx);
MOZ_ASSERT(aWorkerScope);
MOZ_ASSERT(aEvent);
nsCOMPtr<nsIGlobalObject> globalObject = aWorkerScope;
WidgetEvent* internalEvent = aEvent->WidgetEventPtr();
RefPtr<ExtendableEventKeepAliveHandler> keepAliveHandler =
ExtendableEventKeepAliveHandler::Create(std::move(aCallback));
if (NS_WARN_IF(!keepAliveHandler)) {
return NS_ERROR_FAILURE;
}
// This must be always set *before* dispatching the event, otherwise
// waitUntil() calls will fail.
aEvent->SetKeepAliveHandler(keepAliveHandler);
ErrorResult result;
aWorkerScope->DispatchEvent(*aEvent, result);
if (NS_WARN_IF(result.Failed())) {
result.SuppressException();
return NS_ERROR_FAILURE;
}
keepAliveHandler->MaybeDone();
// We don't block the event when getting an exception but still report the
// error message. NOTE: this will not stop the event.
if (internalEvent->mFlags.mExceptionWasRaised) {
return NS_ERROR_XPC_JS_THREW_EXCEPTION;
}
return NS_OK;
}
bool DispatchFailed(nsresult aStatus) {
return NS_FAILED(aStatus) && aStatus != NS_ERROR_XPC_JS_THREW_EXCEPTION;
}
} // anonymous namespace
class ServiceWorkerOp::ServiceWorkerOpRunnable : public WorkerDebuggeeRunnable {
public:
NS_DECL_ISUPPORTS_INHERITED
ServiceWorkerOpRunnable(RefPtr<ServiceWorkerOp> aOwner,
WorkerPrivate* aWorkerPrivate)
: WorkerDebuggeeRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount),
mOwner(std::move(aOwner)) {
AssertIsOnMainThread();
MOZ_ASSERT(mOwner);
MOZ_ASSERT(aWorkerPrivate);
}
private:
~ServiceWorkerOpRunnable() = default;
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
MOZ_ASSERT(mOwner);
bool rv = mOwner->Exec(aCx, aWorkerPrivate);
Unused << NS_WARN_IF(!rv);
mOwner = nullptr;
return rv;
}
nsresult Cancel() override {
// We need to check first if cancel is permitted
nsresult rv = WorkerRunnable::Cancel();
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ASSERT(mOwner);
mOwner->RejectAll(NS_ERROR_DOM_ABORT_ERR);
mOwner = nullptr;
return NS_OK;
}
RefPtr<ServiceWorkerOp> mOwner;
};
NS_IMPL_ISUPPORTS_INHERITED0(ServiceWorkerOp::ServiceWorkerOpRunnable,
WorkerRunnable)
bool ServiceWorkerOp::MaybeStart(RemoteWorkerChild* aOwner,
RemoteWorkerChild::State& aState) {
MOZ_ASSERT(!mStarted);
MOZ_ASSERT(aOwner);
MOZ_ASSERT(aOwner->GetOwningEventTarget()->IsOnCurrentThread());
auto launcherData = aOwner->mLauncherData.Access();
if (NS_WARN_IF(!launcherData->mIPCActive)) {
RejectAll(NS_ERROR_DOM_ABORT_ERR);
mStarted = true;
return true;
}
// Allow termination to happen while the Service Worker is initializing.
if (aState.is<Pending>() && !IsTerminationOp()) {
return false;
}
if (NS_WARN_IF(aState.is<RemoteWorkerChild::PendingTerminated>()) ||
NS_WARN_IF(aState.is<RemoteWorkerChild::Terminated>())) {
RejectAll(NS_ERROR_DOM_INVALID_STATE_ERR);
mStarted = true;
return true;
}
MOZ_ASSERT(aState.is<RemoteWorkerChild::Running>() || IsTerminationOp());
RefPtr<ServiceWorkerOp> self = this;
if (IsTerminationOp()) {
aOwner->GetTerminationPromise()->Then(
GetCurrentSerialEventTarget(), __func__,
[self](
const GenericNonExclusivePromise::ResolveOrRejectValue& aResult) {
MaybeReportServiceWorkerShutdownProgress(self->mArgs, true);
MOZ_ASSERT(!self->mPromiseHolder.IsEmpty());
if (NS_WARN_IF(aResult.IsReject())) {
self->mPromiseHolder.Reject(aResult.RejectValue(), __func__);
return;
}
self->mPromiseHolder.Resolve(NS_OK, __func__);
});
}
RefPtr<RemoteWorkerChild> owner = aOwner;
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
__func__, [self = std::move(self), owner = std::move(owner)]() mutable {
MaybeReportServiceWorkerShutdownProgress(self->mArgs);
auto lock = owner->mState.Lock();
auto& state = lock.ref();
if (NS_WARN_IF(!state.is<Running>() && !self->IsTerminationOp())) {
self->RejectAll(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
if (self->IsTerminationOp()) {
owner->CloseWorkerOnMainThread(state);
} else {
MOZ_ASSERT(state.is<Running>());
RefPtr<WorkerRunnable> workerRunnable =
self->GetRunnable(state.as<Running>().mWorkerPrivate);
if (NS_WARN_IF(!workerRunnable->Dispatch())) {
self->RejectAll(NS_ERROR_FAILURE);
}
}
nsCOMPtr<nsIEventTarget> target = owner->GetOwningEventTarget();
NS_ProxyRelease(__func__, target, owner.forget());
});
mStarted = true;
MOZ_ALWAYS_SUCCEEDS(
SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()));
return true;
}
void ServiceWorkerOp::Cancel() { RejectAll(NS_ERROR_DOM_ABORT_ERR); }
ServiceWorkerOp::ServiceWorkerOp(
ServiceWorkerOpArgs&& aArgs,
std::function<void(const ServiceWorkerOpResult&)>&& aCallback)
: mArgs(std::move(aArgs)) {
MOZ_ASSERT(RemoteWorkerService::Thread()->IsOnCurrentThread());
RefPtr<ServiceWorkerOpPromise> promise = mPromiseHolder.Ensure(__func__);
promise->Then(
GetCurrentSerialEventTarget(), __func__,
[callback = std::move(aCallback)](
ServiceWorkerOpPromise::ResolveOrRejectValue&& aResult) mutable {
if (NS_WARN_IF(aResult.IsReject())) {
MOZ_ASSERT(NS_FAILED(aResult.RejectValue()));
callback(aResult.RejectValue());
return;
}
callback(aResult.ResolveValue());
});
}
ServiceWorkerOp::~ServiceWorkerOp() {
Unused << NS_WARN_IF(!mPromiseHolder.IsEmpty());
mPromiseHolder.RejectIfExists(NS_ERROR_DOM_ABORT_ERR, __func__);
}
bool ServiceWorkerOp::Started() const {
MOZ_ASSERT(RemoteWorkerService::Thread()->IsOnCurrentThread());
return mStarted;
}
bool ServiceWorkerOp::IsTerminationOp() const {
return mArgs.type() ==
ServiceWorkerOpArgs::TServiceWorkerTerminateWorkerOpArgs;
}
RefPtr<WorkerRunnable> ServiceWorkerOp::GetRunnable(
WorkerPrivate* aWorkerPrivate) {
AssertIsOnMainThread();
MOZ_ASSERT(aWorkerPrivate);
return new ServiceWorkerOpRunnable(this, aWorkerPrivate);
}
void ServiceWorkerOp::RejectAll(nsresult aStatus) {
MOZ_ASSERT(!mPromiseHolder.IsEmpty());
mPromiseHolder.Reject(aStatus, __func__);
}
class CheckScriptEvaluationOp final : public ServiceWorkerOp {
using ServiceWorkerOp::ServiceWorkerOp;
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CheckScriptEvaluationOp, override)
private:
~CheckScriptEvaluationOp() = default;
bool Exec(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
MOZ_ASSERT(!mPromiseHolder.IsEmpty());
ServiceWorkerCheckScriptEvaluationOpResult result;
result.workerScriptExecutedSuccessfully() =
aWorkerPrivate->WorkerScriptExecutedSuccessfully();
result.fetchHandlerWasAdded() = aWorkerPrivate->FetchHandlerWasAdded();
mPromiseHolder.Resolve(result, __func__);
return true;
}
};
class TerminateServiceWorkerOp final : public ServiceWorkerOp {
using ServiceWorkerOp::ServiceWorkerOp;
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TerminateServiceWorkerOp, override)
private:
~TerminateServiceWorkerOp() = default;
bool Exec(JSContext*, WorkerPrivate*) override {
MOZ_ASSERT_UNREACHABLE(
"Worker termination should be handled in "
"`ServiceWorkerOp::MaybeStart()`");
return false;
}
};
class UpdateServiceWorkerStateOp final : public ServiceWorkerOp {
using ServiceWorkerOp::ServiceWorkerOp;
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UpdateServiceWorkerStateOp, override);
private:
class UpdateStateOpRunnable final : public MainThreadWorkerControlRunnable {
public:
NS_DECL_ISUPPORTS_INHERITED
UpdateStateOpRunnable(RefPtr<UpdateServiceWorkerStateOp> aOwner,
WorkerPrivate* aWorkerPrivate)
: MainThreadWorkerControlRunnable(aWorkerPrivate),
mOwner(std::move(aOwner)) {
AssertIsOnMainThread();
MOZ_ASSERT(mOwner);
MOZ_ASSERT(aWorkerPrivate);
}
private:
~UpdateStateOpRunnable() = default;
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
if (mOwner) {
Unused << mOwner->Exec(aCx, aWorkerPrivate);
mOwner = nullptr;
}
return true;
}
nsresult Cancel() override {
MOZ_ASSERT(mOwner);
mOwner->RejectAll(NS_ERROR_DOM_ABORT_ERR);
mOwner = nullptr;
return MainThreadWorkerControlRunnable::Cancel();
}
RefPtr<UpdateServiceWorkerStateOp> mOwner;
};
~UpdateServiceWorkerStateOp() = default;
RefPtr<WorkerRunnable> GetRunnable(WorkerPrivate* aWorkerPrivate) override {
AssertIsOnMainThread();
MOZ_ASSERT(aWorkerPrivate);
MOZ_ASSERT(mArgs.type() ==
ServiceWorkerOpArgs::TServiceWorkerUpdateStateOpArgs);
return new UpdateStateOpRunnable(this, aWorkerPrivate);
}
bool Exec(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
MOZ_ASSERT(!mPromiseHolder.IsEmpty());
ServiceWorkerState state =
mArgs.get_ServiceWorkerUpdateStateOpArgs().state();
aWorkerPrivate->UpdateServiceWorkerState(state);
mPromiseHolder.Resolve(NS_OK, __func__);
return true;
}
};
NS_IMPL_ISUPPORTS_INHERITED0(UpdateServiceWorkerStateOp::UpdateStateOpRunnable,
MainThreadWorkerControlRunnable)
void ExtendableEventOp::FinishedWithResult(ExtendableEventResult aResult) {
MOZ_ASSERT(IsCurrentThreadRunningWorker());
MOZ_ASSERT(!mPromiseHolder.IsEmpty());
mPromiseHolder.Resolve(aResult == Resolved ? NS_OK : NS_ERROR_FAILURE,
__func__);
}
class LifeCycleEventOp final : public ExtendableEventOp {
using ExtendableEventOp::ExtendableEventOp;
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(LifeCycleEventOp, override)
private:
~LifeCycleEventOp() = default;
bool Exec(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
MOZ_ASSERT(!mPromiseHolder.IsEmpty());
RefPtr<ExtendableEvent> event;
RefPtr<EventTarget> target = aWorkerPrivate->GlobalScope();
const nsString& eventName =
mArgs.get_ServiceWorkerLifeCycleEventOpArgs().eventName();
if (eventName.EqualsASCII("install") || eventName.EqualsASCII("activate")) {
ExtendableEventInit init;
init.mBubbles = false;
init.mCancelable = false;
event = ExtendableEvent::Constructor(target, eventName, init);
} else {
MOZ_CRASH("Unexpected lifecycle event");
}
event->SetTrusted(true);
nsresult rv = DispatchExtendableEventOnWorkerScope(
aCx, aWorkerPrivate->GlobalScope(), event, this);
if (NS_WARN_IF(DispatchFailed(rv))) {
RejectAll(rv);
}
return !DispatchFailed(rv);
}
};
/**
* PushEventOp
*/
class PushEventOp final : public ExtendableEventOp {
using ExtendableEventOp::ExtendableEventOp;
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PushEventOp, override)
private:
~PushEventOp() = default;
bool Exec(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
MOZ_ASSERT(!mPromiseHolder.IsEmpty());
ErrorResult result;
auto scopeExit = MakeScopeExit([&] {
MOZ_ASSERT(result.Failed());
RejectAll(result.StealNSResult());
ReportError(aWorkerPrivate);
});
const ServiceWorkerPushEventOpArgs& args =
mArgs.get_ServiceWorkerPushEventOpArgs();
RootedDictionary<PushEventInit> pushEventInit(aCx);
if (args.data().type() != OptionalPushData::Tvoid_t) {
auto& bytes = args.data().get_ArrayOfuint8_t();
JSObject* data =
Uint8Array::Create(aCx, bytes.Length(), bytes.Elements());
if (!data) {
result = ErrorResult(NS_ERROR_FAILURE);
return false;
}
DebugOnly<bool> inited =
pushEventInit.mData.Construct().SetAsArrayBufferView().Init(data);
MOZ_ASSERT(inited);
}
pushEventInit.mBubbles = false;
pushEventInit.mCancelable = false;
GlobalObject globalObj(aCx, aWorkerPrivate->GlobalScope()->GetWrapper());
RefPtr<PushEvent> pushEvent =
PushEvent::Constructor(globalObj, u"push"_ns, pushEventInit, result);
if (NS_WARN_IF(result.Failed())) {
return false;
}
pushEvent->SetTrusted(true);
scopeExit.release();
nsresult rv = DispatchExtendableEventOnWorkerScope(
aCx, aWorkerPrivate->GlobalScope(), pushEvent, this);
if (NS_FAILED(rv)) {
if (NS_WARN_IF(DispatchFailed(rv))) {
RejectAll(rv);
}
// We don't cancel WorkerPrivate when catching an exception.
ReportError(aWorkerPrivate,
nsIPushErrorReporter::DELIVERY_UNCAUGHT_EXCEPTION);
}
return !DispatchFailed(rv);
}
void FinishedWithResult(ExtendableEventResult aResult) override {
MOZ_ASSERT(IsCurrentThreadRunningWorker());
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
if (aResult == Rejected) {
ReportError(workerPrivate,
nsIPushErrorReporter::DELIVERY_UNHANDLED_REJECTION);
}
ExtendableEventOp::FinishedWithResult(aResult);
}
void ReportError(
WorkerPrivate* aWorkerPrivate,
uint16_t aError = nsIPushErrorReporter::DELIVERY_INTERNAL_ERROR) {
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
if (NS_WARN_IF(aError > nsIPushErrorReporter::DELIVERY_INTERNAL_ERROR) ||
mArgs.get_ServiceWorkerPushEventOpArgs().messageId().IsEmpty()) {
return;
}
nsString messageId = mArgs.get_ServiceWorkerPushEventOpArgs().messageId();
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
__func__, [messageId = std::move(messageId), error = aError] {
nsCOMPtr<nsIPushErrorReporter> reporter =
do_GetService("@mozilla.org/push/Service;1");
if (reporter) {
nsresult rv = reporter->ReportDeliveryError(messageId, error);
Unused << NS_WARN_IF(NS_FAILED(rv));
}
});
MOZ_ALWAYS_SUCCEEDS(aWorkerPrivate->DispatchToMainThread(r.forget()));
}
};
class PushSubscriptionChangeEventOp final : public ExtendableEventOp {
using ExtendableEventOp::ExtendableEventOp;
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PushSubscriptionChangeEventOp, override)
private:
~PushSubscriptionChangeEventOp() = default;
bool Exec(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
MOZ_ASSERT(!mPromiseHolder.IsEmpty());
RefPtr<EventTarget> target = aWorkerPrivate->GlobalScope();
ExtendableEventInit init;
init.mBubbles = false;
init.mCancelable = false;
RefPtr<ExtendableEvent> event = ExtendableEvent::Constructor(
target, u"pushsubscriptionchange"_ns, init);
event->SetTrusted(true);
nsresult rv = DispatchExtendableEventOnWorkerScope(
aCx, aWorkerPrivate->GlobalScope(), event, this);
if (NS_WARN_IF(DispatchFailed(rv))) {
RejectAll(rv);
}
return !DispatchFailed(rv);
}
};
class NotificationEventOp : public ExtendableEventOp,
public nsITimerCallback,
public nsINamed {
using ExtendableEventOp::ExtendableEventOp;
public:
NS_DECL_THREADSAFE_ISUPPORTS
private:
~NotificationEventOp() {
MOZ_DIAGNOSTIC_ASSERT(!mTimer);
MOZ_DIAGNOSTIC_ASSERT(!mWorkerRef);
}
void ClearWindowAllowed(WorkerPrivate* aWorkerPrivate) {
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
if (!mTimer) {
return;
}
// This might be executed after the global was unrooted, in which case
// GlobalScope() will return null. Making the check here just to be safe.
WorkerGlobalScope* globalScope = aWorkerPrivate->GlobalScope();
if (!globalScope) {
return;
}
globalScope->ConsumeWindowInteraction();
mTimer->Cancel();
mTimer = nullptr;
mWorkerRef = nullptr;
}
void StartClearWindowTimer(WorkerPrivate* aWorkerPrivate) {
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
MOZ_ASSERT(!mTimer);
nsresult rv;
nsCOMPtr<nsITimer> timer =
NS_NewTimer(aWorkerPrivate->ControlEventTarget());
if (NS_WARN_IF(!timer)) {
return;
}
MOZ_ASSERT(!mWorkerRef);
RefPtr<NotificationEventOp> self = this;
mWorkerRef = StrongWorkerRef::Create(
aWorkerPrivate, "NotificationEventOp", [self = std::move(self)] {
// We could try to hold the worker alive until the timer fires, but
// other APIs are not likely to work in this partially shutdown state.
// We might as well let the worker thread exit.
self->ClearWindowAllowed(self->mWorkerRef->Private());
});
if (!mWorkerRef) {
return;
}
aWorkerPrivate->GlobalScope()->AllowWindowInteraction();
timer.swap(mTimer);
// We swap first and then initialize the timer so that even if initializing
// fails, we still clean the busy count and interaction count correctly.
// The timer can't be initialized before modyfing the busy count since the
// timer thread could run and call the timeout but the worker may
// already be terminating and modifying the busy count could fail.
uint32_t delay = mArgs.get_ServiceWorkerNotificationEventOpArgs()
.disableOpenClickDelay();
rv = mTimer->InitWithCallback(this, delay, nsITimer::TYPE_ONE_SHOT);
if (NS_WARN_IF(NS_FAILED(rv))) {
ClearWindowAllowed(aWorkerPrivate);
return;
}
}
// ExtendableEventOp interface
bool Exec(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
MOZ_ASSERT(!mPromiseHolder.IsEmpty());
RefPtr<EventTarget> target = aWorkerPrivate->GlobalScope();
ServiceWorkerNotificationEventOpArgs& args =
mArgs.get_ServiceWorkerNotificationEventOpArgs();
ErrorResult result;
RefPtr<Notification> notification = Notification::ConstructFromFields(
aWorkerPrivate->GlobalScope(), args.id(), args.title(), args.dir(),
args.lang(), args.body(), args.tag(), args.icon(), args.data(),
args.scope(), result);
if (NS_WARN_IF(result.Failed())) {
return false;
}
NotificationEventInit init;
init.mNotification = notification;
init.mBubbles = false;
init.mCancelable = false;
RefPtr<NotificationEvent> notificationEvent =
NotificationEvent::Constructor(target, args.eventName(), init);
notificationEvent->SetTrusted(true);
if (args.eventName().EqualsLiteral("notificationclick")) {
StartClearWindowTimer(aWorkerPrivate);
}
nsresult rv = DispatchExtendableEventOnWorkerScope(
aCx, aWorkerPrivate->GlobalScope(), notificationEvent, this);
if (NS_WARN_IF(DispatchFailed(rv))) {
// This will reject mPromiseHolder.
FinishedWithResult(Rejected);
}
return !DispatchFailed(rv);
}
void FinishedWithResult(ExtendableEventResult aResult) override {
MOZ_ASSERT(IsCurrentThreadRunningWorker());
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(workerPrivate);
ClearWindowAllowed(workerPrivate);
ExtendableEventOp::FinishedWithResult(aResult);
}
// nsITimerCallback interface
NS_IMETHOD Notify(nsITimer* aTimer) override {
MOZ_DIAGNOSTIC_ASSERT(mTimer == aTimer);
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
ClearWindowAllowed(workerPrivate);
return NS_OK;
}
// nsINamed interface
NS_IMETHOD GetName(nsACString& aName) override {
aName.AssignLiteral("NotificationEventOp");
return NS_OK;
}
nsCOMPtr<nsITimer> mTimer;
RefPtr<StrongWorkerRef> mWorkerRef;
};
NS_IMPL_ISUPPORTS(NotificationEventOp, nsITimerCallback, nsINamed)
class MessageEventOp final : public ExtendableEventOp {
using ExtendableEventOp::ExtendableEventOp;
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MessageEventOp, override)
MessageEventOp(ServiceWorkerOpArgs&& aArgs,
std::function<void(const ServiceWorkerOpResult&)>&& aCallback)
: ExtendableEventOp(std::move(aArgs), std::move(aCallback)),
mData(new ServiceWorkerCloneData()) {
mData->CopyFromClonedMessageData(
mArgs.get_ServiceWorkerMessageEventOpArgs().clonedData());
}
private:
~MessageEventOp() = default;
bool Exec(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
MOZ_ASSERT(!mPromiseHolder.IsEmpty());
JS::Rooted<JS::Value> messageData(aCx);
nsCOMPtr<nsIGlobalObject> sgo = aWorkerPrivate->GlobalScope();
ErrorResult rv;
if (!mData->IsErrorMessageData()) {
mData->Read(aCx, &messageData, rv);
}
// If mData is an error message data, then it means that it failed to
// serialize on the caller side because it contains a shared memory object.
// If deserialization fails, we will fire a messageerror event.
const bool deserializationFailed =
rv.Failed() || mData->IsErrorMessageData();
Sequence<OwningNonNull<MessagePort>> ports;
if (!mData->TakeTransferredPortsAsSequence(ports)) {
RejectAll(NS_ERROR_FAILURE);
rv.SuppressException();
return false;
}
RootedDictionary<ExtendableMessageEventInit> init(aCx);
init.mBubbles = false;
init.mCancelable = false;
// On a messageerror event, we disregard ports:
// https://w3c.github.io/ServiceWorker/#service-worker-postmessage
if (!deserializationFailed) {
init.mData = messageData;
init.mPorts = std::move(ports);
}
RefPtr<net::MozURL> mozUrl;
nsresult result = net::MozURL::Init(
getter_AddRefs(mozUrl), mArgs.get_ServiceWorkerMessageEventOpArgs()
.clientInfoAndState()
.info()
.url());
if (NS_WARN_IF(NS_FAILED(result))) {
RejectAll(result);
rv.SuppressException();
return false;
}
nsCString origin;
mozUrl->Origin(origin);
CopyUTF8toUTF16(origin, init.mOrigin);
init.mSource.SetValue().SetAsClient() = new Client(
sgo, mArgs.get_ServiceWorkerMessageEventOpArgs().clientInfoAndState());
rv.SuppressException();
RefPtr<EventTarget> target = aWorkerPrivate->GlobalScope();
RefPtr<ExtendableMessageEvent> extendableEvent =
ExtendableMessageEvent::Constructor(
target, deserializationFailed ? u"messageerror"_ns : u"message"_ns,
init);
extendableEvent->SetTrusted(true);
nsresult rv2 = DispatchExtendableEventOnWorkerScope(
aCx, aWorkerPrivate->GlobalScope(), extendableEvent, this);
if (NS_WARN_IF(DispatchFailed(rv2))) {
RejectAll(rv2);
}
return !DispatchFailed(rv2);
}
RefPtr<ServiceWorkerCloneData> mData;
};
/**
* Used for ScopeExit-style network request cancelation in
* `ResolvedCallback()` (e.g. if `FetchEvent::RespondWith()` is resolved with
* a non-JS object).
*/
class MOZ_STACK_CLASS FetchEventOp::AutoCancel {
public:
explicit AutoCancel(FetchEventOp* aOwner)
: mOwner(aOwner),
mLine(0),
mColumn(0),
mMessageName("InterceptionFailedWithURL"_ns) {
MOZ_ASSERT(IsCurrentThreadRunningWorker());
MOZ_ASSERT(mOwner);
nsAutoString requestURL;
mOwner->GetRequestURL(requestURL);
mParams.AppendElement(requestURL);
}
~AutoCancel() {
if (mOwner) {
if (mSourceSpec.IsEmpty()) {
mOwner->AsyncLog(mMessageName, std::move(mParams));
} else {
mOwner->AsyncLog(mSourceSpec, mLine, mColumn, mMessageName,
std::move(mParams));
}
MOZ_ASSERT(!mOwner->mRespondWithPromiseHolder.IsEmpty());
mOwner->mHandled->MaybeRejectWithNetworkError("AutoCancel"_ns);
mOwner->mRespondWithPromiseHolder.Reject(
CancelInterceptionArgs(
NS_ERROR_INTERCEPTION_FAILED,
FetchEventTimeStamps(mOwner->mFetchHandlerStart,
mOwner->mFetchHandlerFinish)),
__func__);
}
}
// This function steals the error message from a ErrorResult.
void SetCancelErrorResult(JSContext* aCx, ErrorResult& aRv) {
MOZ_DIAGNOSTIC_ASSERT(aRv.Failed());
MOZ_DIAGNOSTIC_ASSERT(!JS_IsExceptionPending(aCx));
// Storing the error as exception in the JSContext.
if (!aRv.MaybeSetPendingException(aCx)) {
return;
}
MOZ_ASSERT(!aRv.Failed());
// Let's take the pending exception.
JS::ExceptionStack exnStack(aCx);
if (!JS::StealPendingExceptionStack(aCx, &exnStack)) {
return;
}
// Converting the exception in a JS::ErrorReportBuilder.
JS::ErrorReportBuilder report(aCx);
if (!report.init(aCx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) {
JS_ClearPendingException(aCx);
return;
}
MOZ_ASSERT(mOwner);
MOZ_ASSERT(mMessageName.EqualsLiteral("InterceptionFailedWithURL"));
MOZ_ASSERT(mParams.Length() == 1);
// Let's store the error message here.
mMessageName.Assign(report.toStringResult().c_str());
mParams.Clear();
}
template <typename... Params>
void SetCancelMessage(const nsACString& aMessageName, Params&&... aParams) {
MOZ_ASSERT(mOwner);
MOZ_ASSERT(mMessageName.EqualsLiteral("InterceptionFailedWithURL"));
MOZ_ASSERT(mParams.Length() == 1);
mMessageName = aMessageName;
mParams.Clear();
StringArrayAppender::Append(mParams, sizeof...(Params),
std::forward<Params>(aParams)...);
}
template <typename... Params>
void SetCancelMessageAndLocation(const nsACString& aSourceSpec,
uint32_t aLine, uint32_t aColumn,
const nsACString& aMessageName,
Params&&... aParams) {
MOZ_ASSERT(mOwner);
MOZ_ASSERT(mMessageName.EqualsLiteral("InterceptionFailedWithURL"));
MOZ_ASSERT(mParams.Length() == 1);
mSourceSpec = aSourceSpec;
mLine = aLine;
mColumn = aColumn;
mMessageName = aMessageName;
mParams.Clear();
StringArrayAppender::Append(mParams, sizeof...(Params),
std::forward<Params>(aParams)...);
}
void Reset() { mOwner = nullptr; }
private:
FetchEventOp* MOZ_NON_OWNING_REF mOwner;
nsCString mSourceSpec;
uint32_t mLine;
uint32_t mColumn;
nsCString mMessageName;
nsTArray<nsString> mParams;
};
NS_IMPL_ISUPPORTS0(FetchEventOp)
void FetchEventOp::SetActor(RefPtr<FetchEventOpProxyChild> aActor) {
MOZ_ASSERT(RemoteWorkerService::Thread()->IsOnCurrentThread());
MOZ_ASSERT(!Started());
MOZ_ASSERT(!mActor);
mActor = std::move(aActor);
}
void FetchEventOp::RevokeActor(FetchEventOpProxyChild* aActor) {
MOZ_ASSERT(aActor);
MOZ_ASSERT_IF(mActor, mActor == aActor);
mActor = nullptr;
}
RefPtr<FetchEventRespondWithPromise> FetchEventOp::GetRespondWithPromise() {
MOZ_ASSERT(RemoteWorkerService::Thread()->IsOnCurrentThread());
MOZ_ASSERT(!Started());
MOZ_ASSERT(mRespondWithPromiseHolder.IsEmpty());
return mRespondWithPromiseHolder.Ensure(__func__);
}
void FetchEventOp::RespondWithCalledAt(const nsCString& aRespondWithScriptSpec,
uint32_t aRespondWithLineNumber,
uint32_t aRespondWithColumnNumber) {
MOZ_ASSERT(IsCurrentThreadRunningWorker());
MOZ_ASSERT(!mRespondWithClosure);
mRespondWithClosure.emplace(aRespondWithScriptSpec, aRespondWithLineNumber,
aRespondWithColumnNumber);
}
void FetchEventOp::ReportCanceled(const nsCString& aPreventDefaultScriptSpec,
uint32_t aPreventDefaultLineNumber,
uint32_t aPreventDefaultColumnNumber) {
MOZ_ASSERT(IsCurrentThreadRunningWorker());
MOZ_ASSERT(mActor);
MOZ_ASSERT(!mPromiseHolder.IsEmpty());
nsString requestURL;
GetRequestURL(requestURL);
AsyncLog(aPreventDefaultScriptSpec, aPreventDefaultLineNumber,
aPreventDefaultColumnNumber, "InterceptionCanceledWithURL"_ns,
{std::move(requestURL)});
}
FetchEventOp::~FetchEventOp() {
mRespondWithPromiseHolder.RejectIfExists(
CancelInterceptionArgs(
NS_ERROR_DOM_ABORT_ERR,
FetchEventTimeStamps(mFetchHandlerStart, mFetchHandlerFinish)),
__func__);
if (mActor) {
NS_ProxyRelease("FetchEventOp::mActor", RemoteWorkerService::Thread(),
mActor.forget());
}
}
void FetchEventOp::RejectAll(nsresult aStatus) {
MOZ_ASSERT(!mRespondWithPromiseHolder.IsEmpty());
MOZ_ASSERT(!mPromiseHolder.IsEmpty());
if (mFetchHandlerStart.IsNull()) {
mFetchHandlerStart = TimeStamp::Now();
}
if (mFetchHandlerFinish.IsNull()) {
mFetchHandlerFinish = TimeStamp::Now();
}
mRespondWithPromiseHolder.Reject(
CancelInterceptionArgs(
aStatus,
FetchEventTimeStamps(mFetchHandlerStart, mFetchHandlerFinish)),
__func__);
mPromiseHolder.Reject(aStatus, __func__);
}
void FetchEventOp::FinishedWithResult(ExtendableEventResult aResult) {
MOZ_ASSERT(IsCurrentThreadRunningWorker());
MOZ_ASSERT(!mPromiseHolder.IsEmpty());
MOZ_ASSERT(!mResult);
mResult.emplace(aResult);
/**
* This should only return early if neither waitUntil() nor respondWith()
* are called. The early return is so that mRespondWithPromiseHolder has a
* chance to settle before mPromiseHolder does.
*/
if (!mPostDispatchChecksDone) {
return;
}
MaybeFinished();
}
void FetchEventOp::MaybeFinished() {
MOZ_ASSERT(IsCurrentThreadRunningWorker());
MOZ_ASSERT(!mPromiseHolder.IsEmpty());
if (mResult) {
// It's possible that mRespondWithPromiseHolder wasn't settled. That happens
// if the worker was terminated before the respondWith promise settled.
mHandled = nullptr;
mPreloadResponse = nullptr;
mPreloadResponseAvailablePromiseRequestHolder.DisconnectIfExists();
mPreloadResponseEndPromiseRequestHolder.DisconnectIfExists();
ServiceWorkerFetchEventOpResult result(
mResult.value() == Resolved ? NS_OK : NS_ERROR_FAILURE);
mPromiseHolder.Resolve(result, __func__);
}
}
bool FetchEventOp::Exec(JSContext* aCx, WorkerPrivate* aWorkerPrivate) {
aWorkerPrivate->AssertIsOnWorkerThread();
MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
MOZ_ASSERT(!mRespondWithPromiseHolder.IsEmpty());
MOZ_ASSERT(!mPromiseHolder.IsEmpty());
nsresult rv = DispatchFetchEvent(aCx, aWorkerPrivate);
if (NS_WARN_IF(NS_FAILED(rv))) {
RejectAll(rv);
}
return NS_SUCCEEDED(rv);
}
void FetchEventOp::AsyncLog(const nsCString& aMessageName,
nsTArray<nsString> aParams) {
MOZ_ASSERT(mActor);
MOZ_ASSERT(!mPromiseHolder.IsEmpty());
MOZ_ASSERT(mRespondWithClosure);
const FetchEventRespondWithClosure& closure = mRespondWithClosure.ref();
AsyncLog(closure.respondWithScriptSpec(), closure.respondWithLineNumber(),
closure.respondWithColumnNumber(), aMessageName, std::move(aParams));
}
void FetchEventOp::AsyncLog(const nsCString& aScriptSpec, uint32_t aLineNumber,
uint32_t aColumnNumber,
const nsCString& aMessageName,
nsTArray<nsString> aParams) {
MOZ_ASSERT(mActor);
MOZ_ASSERT(!mPromiseHolder.IsEmpty());
// Capture `this` because FetchEventOpProxyChild (mActor) is not thread
// safe, so an AddRef from RefPtr<FetchEventOpProxyChild>'s constructor will
// assert.
RefPtr<FetchEventOp> self = this;
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
__func__, [self = std::move(self), spec = aScriptSpec, line = aLineNumber,
column = aColumnNumber, messageName = aMessageName,
params = std::move(aParams)] {
if (NS_WARN_IF(!self->mActor)) {
return;
}
Unused << self->mActor->SendAsyncLog(spec, line, column, messageName,
params);
});
MOZ_ALWAYS_SUCCEEDS(
RemoteWorkerService::Thread()->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
}
void FetchEventOp::GetRequestURL(nsAString& aOutRequestURL) {
nsTArray<nsCString>& urls =
mArgs.get_ParentToChildServiceWorkerFetchEventOpArgs()
.common()
.internalRequest()
.urlList();
MOZ_ASSERT(!urls.IsEmpty());
CopyUTF8toUTF16(urls.LastElement(), aOutRequestURL);
}
void FetchEventOp::ResolvedCallback(JSContext* aCx,
JS::Handle<JS::Value> aValue,
ErrorResult& aRv) {
MOZ_ASSERT(IsCurrentThreadRunningWorker());
MOZ_ASSERT(mRespondWithClosure);
MOZ_ASSERT(!mRespondWithPromiseHolder.IsEmpty());
MOZ_ASSERT(!mPromiseHolder.IsEmpty());
mFetchHandlerFinish = TimeStamp::Now();
nsAutoString requestURL;
GetRequestURL(requestURL);
AutoCancel autoCancel(this);
if (!aValue.isObject()) {
NS_WARNING(
"FetchEvent::RespondWith was passed a promise resolved to a "
"non-Object "
"value");
nsCString sourceSpec;
uint32_t line = 0;
uint32_t column = 0;
nsString valueString;
nsContentUtils::ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column,
valueString);
autoCancel.SetCancelMessageAndLocation(sourceSpec, line, column,
"InterceptedNonResponseWithURL"_ns,
requestURL, valueString);
return;
}
RefPtr<Response> response;
nsresult rv = UNWRAP_OBJECT(Response, &aValue.toObject(), response);
if (NS_FAILED(rv)) {
nsCString sourceSpec;
uint32_t line = 0;
uint32_t column = 0;
nsString valueString;
nsContentUtils::ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column,
valueString);
autoCancel.SetCancelMessageAndLocation(sourceSpec, line, column,
"InterceptedNonResponseWithURL"_ns,
requestURL, valueString);
return;
}
WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(worker);
worker->AssertIsOnWorkerThread();
// Section "HTTP Fetch", step 3.3:
// If one of the following conditions is true, return a network error:
// * response's type is "error".
// * request's mode is not "no-cors" and response's type is "opaque".
// * request's redirect mode is not "manual" and response's type is
// "opaqueredirect".
// * request's redirect mode is not "follow" and response's url list
// has more than one item.
if (response->Type() == ResponseType::Error) {
autoCancel.SetCancelMessage("InterceptedErrorResponseWithURL"_ns,
requestURL);
return;
}
const ParentToChildServiceWorkerFetchEventOpArgs& args =
mArgs.get_ParentToChildServiceWorkerFetchEventOpArgs();
const RequestMode requestMode = args.common().internalRequest().requestMode();
if (response->Type() == ResponseType::Opaque &&
requestMode != RequestMode::No_cors) {
NS_ConvertASCIItoUTF16 modeString(
RequestModeValues::GetString(requestMode));
nsAutoString requestURL;
GetRequestURL(requestURL);
autoCancel.SetCancelMessage("BadOpaqueInterceptionRequestModeWithURL"_ns,
requestURL, modeString);
return;
}
const RequestRedirect requestRedirectMode =
args.common().internalRequest().requestRedirect();
if (requestRedirectMode != RequestRedirect::Manual &&
response->Type() == ResponseType::Opaqueredirect) {
autoCancel.SetCancelMessage("BadOpaqueRedirectInterceptionWithURL"_ns,
requestURL);
return;
}
if (requestRedirectMode != RequestRedirect::Follow &&
response->Redirected()) {
autoCancel.SetCancelMessage("BadRedirectModeInterceptionWithURL"_ns,
requestURL);
return;
}
{
ErrorResult error;
bool bodyUsed = response->GetBodyUsed(error);
error.WouldReportJSException();
if (NS_WARN_IF(error.Failed())) {
autoCancel.SetCancelErrorResult(aCx, error);
return;
}
if (NS_WARN_IF(bodyUsed)) {
autoCancel.SetCancelMessage("InterceptedUsedResponseWithURL"_ns,
requestURL);
return;
}
}
SafeRefPtr<InternalResponse> ir = response->GetInternalResponse();
if (NS_WARN_IF(!ir)) {
return;
}
// An extra safety check to make sure our invariant that opaque and cors
// responses always have a URL does not break.
if (NS_WARN_IF((response->Type() == ResponseType::Opaque ||
response->Type() == ResponseType::Cors) &&
ir->GetUnfilteredURL().IsEmpty())) {
MOZ_DIAGNOSTIC_ASSERT(false, "Cors or opaque Response without a URL");
return;
}
if (requestMode == RequestMode::Same_origin &&
response->Type() == ResponseType::Cors) {
Telemetry::ScalarAdd(Telemetry::ScalarID::SW_CORS_RES_FOR_SO_REQ_COUNT, 1);
// XXXtt: Will have a pref to enable the quirk response in bug 1419684.
// The variadic template provided by StringArrayAppender requires exactly
// an nsString.
NS_ConvertUTF8toUTF16 responseURL(ir->GetUnfilteredURL());
autoCancel.SetCancelMessage("CorsResponseForSameOriginRequest"_ns,
requestURL, responseURL);
return;
}
nsCOMPtr<nsIInputStream> body;
ir->GetUnfilteredBody(getter_AddRefs(body));
// Errors and redirects may not have a body.
if (body) {
ErrorResult error;
response->SetBodyUsed(aCx, error);
error.WouldReportJSException();
if (NS_WARN_IF(error.Failed())) {
autoCancel.SetCancelErrorResult(aCx, error);
return;
}
}
if (!ir->GetChannelInfo().IsInitialized()) {
// This is a synthetic response (I think and hope so).
ir->InitChannelInfo(worker->GetChannelInfo());
}
autoCancel.Reset();
// https://w3c.github.io/ServiceWorker/#on-fetch-request-algorithm Step 26: If
// eventHandled is not null, then resolve eventHandled.
//
// mRespondWithPromiseHolder will resolve a MozPromise that will resolve on
// the worker owner's thread, so it's fine to resolve the mHandled promise now
// because content will not interfere with respondWith getting the Response to
// where it's going.
mHandled->MaybeResolveWithUndefined();
mRespondWithPromiseHolder.Resolve(
FetchEventRespondWithResult(MakeTuple(
std::move(ir), mRespondWithClosure.ref(),
FetchEventTimeStamps(mFetchHandlerStart, mFetchHandlerFinish))),
__func__);
}
void FetchEventOp::RejectedCallback(JSContext* aCx,
JS::Handle<JS::Value> aValue,
ErrorResult& aRv) {
MOZ_ASSERT(IsCurrentThreadRunningWorker());
MOZ_ASSERT(mRespondWithClosure);
MOZ_ASSERT(!mRespondWithPromiseHolder.IsEmpty());
MOZ_ASSERT(!mPromiseHolder.IsEmpty());
mFetchHandlerFinish = TimeStamp::Now();
FetchEventRespondWithClosure& closure = mRespondWithClosure.ref();
nsCString sourceSpec = closure.respondWithScriptSpec();
uint32_t line = closure.respondWithLineNumber();
uint32_t column = closure.respondWithColumnNumber();
nsString valueString;
nsContentUtils::ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column,
valueString);
nsString requestURL;
GetRequestURL(requestURL);
AsyncLog(sourceSpec, line, column, "InterceptionRejectedResponseWithURL"_ns,
{std::move(requestURL), valueString});
// https://w3c.github.io/ServiceWorker/#on-fetch-request-algorithm Step 25.1:
// If eventHandled is not null, then reject eventHandled with a "NetworkError"
// DOMException in workerRealm.
mHandled->MaybeRejectWithNetworkError(
"FetchEvent.respondWith() Promise rejected"_ns);
mRespondWithPromiseHolder.Resolve(
FetchEventRespondWithResult(CancelInterceptionArgs(
NS_ERROR_INTERCEPTION_FAILED,
FetchEventTimeStamps(mFetchHandlerStart, mFetchHandlerFinish))),
__func__);
}
nsresult FetchEventOp::DispatchFetchEvent(JSContext* aCx,
WorkerPrivate* aWorkerPrivate) {
MOZ_ASSERT(aCx);
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
ParentToChildServiceWorkerFetchEventOpArgs& args =
mArgs.get_ParentToChildServiceWorkerFetchEventOpArgs();
/**
* Testing: Failure injection.
*
* There are a number of different ways that this fetch event could have
* failed that would result in cancellation. This injection point helps
* simulate them without worrying about shifting implementation details with
* full fidelity reproductions of current scenarios.
*
* Broadly speaking, we expect fetch event scenarios to fail because of:
* - Script load failure, which results in the CompileScriptRunnable closing
* the worker and thereby cancelling all pending operations, including this
* fetch. The `ServiceWorkerOp::Cancel` impl just calls
* RejectAll(NS_ERROR_DOM_ABORT_ERR) which we are able to approximate by
* returning the same nsresult here, as our caller also calls RejectAll.
* (And timing-wise, this rejection will happen in the correct sequence.)
* - An exception gets thrown in the processing of the promise that was passed
* to respondWith and it ends up rejecting. The rejection will be converted
* by `FetchEventOp::RejectedCallback` into a cancellation with
* NS_ERROR_INTERCEPTION_FAILED, and by returning that here we approximate
* that failure mode.
*/
if (NS_FAILED(args.common().testingInjectCancellation())) {
return args.common().testingInjectCancellation();
}
/**
* Step 1: get the InternalRequest. The InternalRequest can't be constructed
* here from mArgs because the IPCStream has to be deserialized on the
* thread receiving the ServiceWorkerFetchEventOpArgs.
* FetchEventOpProxyChild will have already deserialized the stream on the
* correct thread before creating this op, so we can take its saved
* InternalRequest.
*/
SafeRefPtr<InternalRequest> internalRequest =
mActor->ExtractInternalRequest();
/**
* Step 2: get the worker's global object
*/
GlobalObject globalObject(aCx, aWorkerPrivate->GlobalScope()->GetWrapper());
nsCOMPtr<nsIGlobalObject> globalObjectAsSupports =
do_QueryInterface(globalObject.GetAsSupports());
if (NS_WARN_IF(!globalObjectAsSupports)) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
/**
* Step 3: create the public DOM Request object
* TODO: this Request object should be created with an AbortSignal object
* which should be aborted if the loading is aborted. See but 1394102.
*/
RefPtr<Request> request =
new Request(globalObjectAsSupports, internalRequest.clonePtr(), nullptr);
MOZ_ASSERT_IF(internalRequest->IsNavigationRequest(),
request->Redirect() == RequestRedirect::Manual);
/**
* Step 4a: create the FetchEventInit
*/
RootedDictionary<FetchEventInit> fetchEventInit(aCx);
fetchEventInit.mRequest = request;
fetchEventInit.mBubbles = false;
fetchEventInit.mCancelable = true;
/**
* TODO: only expose the FetchEvent.clientId on subresource requests for
* now. Once we implement .targetClientId we can then start exposing
* .clientId on non-subresource requests as well. See bug 1487534.
*/
if (!args.common().clientId().IsEmpty() &&
!internalRequest->IsNavigationRequest()) {
fetchEventInit.mClientId = args.common().clientId();
}
/*
* https://w3c.github.io/ServiceWorker/#on-fetch-request-algorithm
*
* "If request is a non-subresource request and requests
* destination is not "report", initialize es resultingClientId attribute
* to reservedClients [resultingClient's] id, and to the empty string
* otherwise." (Step 18.8)
*/
if (!args.common().resultingClientId().IsEmpty() &&
args.common().isNonSubresourceRequest() &&
internalRequest->Destination() != RequestDestination::Report) {
fetchEventInit.mResultingClientId = args.common().resultingClientId();
}
/**
* Step 4b: create the FetchEvent
*/
RefPtr<FetchEvent> fetchEvent =
FetchEvent::Constructor(globalObject, u"fetch"_ns, fetchEventInit);
fetchEvent->SetTrusted(true);
fetchEvent->PostInit(args.common().workerScriptSpec(), this);
mHandled = fetchEvent->Handled();
mPreloadResponse = fetchEvent->PreloadResponse();
if (args.common().preloadNavigation()) {
RefPtr<FetchEventPreloadResponseAvailablePromise> preloadResponsePromise =
mActor->GetPreloadResponseAvailablePromise();
MOZ_ASSERT(preloadResponsePromise);
// If preloadResponsePromise has already settled then this callback will get
// run synchronously here.
RefPtr<FetchEventOp> self = this;
preloadResponsePromise
->Then(
GetCurrentSerialEventTarget(), __func__,
[self, globalObjectAsSupports](
SafeRefPtr<InternalResponse>&& aPreloadResponse) {
self->mPreloadResponse->MaybeResolve(
MakeRefPtr<Response>(globalObjectAsSupports,
std::move(aPreloadResponse), nullptr));
self->mPreloadResponseAvailablePromiseRequestHolder.Complete();
},
[self](int) {
self->mPreloadResponseAvailablePromiseRequestHolder.Complete();
})
->Track(mPreloadResponseAvailablePromiseRequestHolder);
RefPtr<PerformanceStorage> performanceStorage =
aWorkerPrivate->GetPerformanceStorage();
RefPtr<FetchEventPreloadResponseEndPromise> preloadResponseEndPromise =
mActor->GetPreloadResponseEndPromise();
MOZ_ASSERT(preloadResponseEndPromise);
preloadResponseEndPromise
->Then(
GetCurrentSerialEventTarget(), __func__,
[self, performanceStorage,
globalObjectAsSupports](ResponseEndArgs&& aArgs) {
if (aArgs.timing().isSome() && performanceStorage) {
performanceStorage->AddEntry(
aArgs.timing().ref().entryName(),
aArgs.timing().ref().initiatorType(),
MakeUnique<PerformanceTimingData>(
aArgs.timing().ref().timingData()));
}
if (aArgs.endReason() == FetchDriverObserver::eAborted) {
self->mPreloadResponse->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
}
self->mPreloadResponseEndPromiseRequestHolder.Complete();
},
[self](int) {
self->mPreloadResponseEndPromiseRequestHolder.Complete();
})
->Track(mPreloadResponseEndPromiseRequestHolder);
} else {
// preload navigation is disabled, resolved preload response promise with
// undefined as default behavior.
mPreloadResponse->MaybeResolveWithUndefined();
}
mFetchHandlerStart = TimeStamp::Now();
/**
* Step 5: Dispatch the FetchEvent to the worker's global object
*/
nsresult rv = DispatchExtendableEventOnWorkerScope(
aCx, aWorkerPrivate->GlobalScope(), fetchEvent, this);
bool dispatchFailed = NS_FAILED(rv) && rv != NS_ERROR_XPC_JS_THREW_EXCEPTION;
if (NS_WARN_IF(dispatchFailed)) {
mHandled = nullptr;
mPreloadResponse = nullptr;
return rv;
}
/**
* At this point, there are 4 (legal) scenarios:
*
* 1) If neither waitUntil() nor respondWith() are called,
* DispatchExtendableEventOnWorkerScope() will have already called
* FinishedWithResult(), but this call will have recorded the result
* (mResult) and returned early so that mRespondWithPromiseHolder can be
* settled first. mRespondWithPromiseHolder will be settled below, followed
* by a call to MaybeFinished() which settles mPromiseHolder.
*
* 2) If waitUntil() is called at least once, and respondWith() is not
* called, DispatchExtendableEventOnWorkerScope() will NOT have called
* FinishedWithResult(). We'll settle mRespondWithPromiseHolder first, and
* at some point in the future when the last waitUntil() promise settles,
* FinishedWithResult() will be called, settling mPromiseHolder.
*
* 3) If waitUntil() is not called, and respondWith() is called,
* DispatchExtendableEventOnWorkerScope() will NOT have called
* FinishedWithResult(). We can also guarantee that
* mRespondWithPromiseHolder will be settled before mPromiseHolder, due to
* the Promise::AppendNativeHandler() call ordering in
* FetchEvent::RespondWith().
*
* 4) If waitUntil() is called at least once, and respondWith() is also
* called, the effect is similar to scenario 3), with the most imporant
* property being mRespondWithPromiseHolder settling before mPromiseHolder.
*
* Note that if mPromiseHolder is settled before mRespondWithPromiseHolder,
* FetchEventOpChild will cancel the interception.
*/
if (!fetchEvent->WaitToRespond()) {
MOZ_ASSERT(!mRespondWithPromiseHolder.IsEmpty());
MOZ_ASSERT(!aWorkerPrivate->UsesSystemPrincipal(),
"We don't support system-principal serviceworkers");
mFetchHandlerFinish = TimeStamp::Now();
if (fetchEvent->DefaultPrevented(CallerType::NonSystem)) {
// https://w3c.github.io/ServiceWorker/#on-fetch-request-algorithm
// Step 24.1.1: If eventHandled is not null, then reject eventHandled with
// a "NetworkError" DOMException in workerRealm.
mHandled->MaybeRejectWithNetworkError(
"FetchEvent.preventDefault() called"_ns);
mRespondWithPromiseHolder.Resolve(
FetchEventRespondWithResult(CancelInterceptionArgs(
NS_ERROR_INTERCEPTION_FAILED,
FetchEventTimeStamps(mFetchHandlerStart, mFetchHandlerFinish))),
__func__);
} else {
// https://w3c.github.io/ServiceWorker/#on-fetch-request-algorithm
// Step 24.2: If eventHandled is not null, then resolve eventHandled.
mHandled->MaybeResolveWithUndefined();
mRespondWithPromiseHolder.Resolve(
FetchEventRespondWithResult(ResetInterceptionArgs(
FetchEventTimeStamps(mFetchHandlerStart, mFetchHandlerFinish))),
__func__);
}
} else {
MOZ_ASSERT(mRespondWithClosure);
}
mPostDispatchChecksDone = true;
MaybeFinished();
return NS_OK;
}
class ExtensionAPIEventOp final : public ServiceWorkerOp {
using ServiceWorkerOp::ServiceWorkerOp;
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ExtensionAPIEventOp, override)
private:
~ExtensionAPIEventOp() = default;
bool Exec(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
MOZ_ASSERT(aWorkerPrivate->ExtensionAPIAllowed());
MOZ_ASSERT(!mPromiseHolder.IsEmpty());
ServiceWorkerExtensionAPIEventOpArgs& args =
mArgs.get_ServiceWorkerExtensionAPIEventOpArgs();
ServiceWorkerExtensionAPIEventOpResult result;
result.extensionAPIEventListenerWasAdded() = false;
if (aWorkerPrivate->WorkerScriptExecutedSuccessfully()) {
GlobalObject globalObj(aCx, aWorkerPrivate->GlobalScope()->GetWrapper());
RefPtr<ServiceWorkerGlobalScope> scope;
UNWRAP_OBJECT(ServiceWorkerGlobalScope, globalObj.Get(), scope);
SafeRefPtr<extensions::ExtensionBrowser> extensionAPI =
scope->AcquireExtensionBrowser();
if (!extensionAPI) {
// If the worker script did never access the WebExtension APIs
// then we can return earlier, no event listener could have been added.
mPromiseHolder.Resolve(result, __func__);
return true;
}
// Check if a listener has been subscribed on the expected WebExtensions
// API event.
bool hasWakeupListener = extensionAPI->HasWakeupEventListener(
args.apiNamespace(), args.apiEventName());
result.extensionAPIEventListenerWasAdded() = hasWakeupListener;
mPromiseHolder.Resolve(result, __func__);
} else {
mPromiseHolder.Resolve(result, __func__);
}
return true;
}
};
/* static */ already_AddRefed<ServiceWorkerOp> ServiceWorkerOp::Create(
ServiceWorkerOpArgs&& aArgs,
std::function<void(const ServiceWorkerOpResult&)>&& aCallback) {
MOZ_ASSERT(RemoteWorkerService::Thread()->IsOnCurrentThread());
RefPtr<ServiceWorkerOp> op;
switch (aArgs.type()) {
case ServiceWorkerOpArgs::TServiceWorkerCheckScriptEvaluationOpArgs:
op = MakeRefPtr<CheckScriptEvaluationOp>(std::move(aArgs),
std::move(aCallback));
break;
case ServiceWorkerOpArgs::TServiceWorkerUpdateStateOpArgs:
op = MakeRefPtr<UpdateServiceWorkerStateOp>(std::move(aArgs),
std::move(aCallback));
break;
case ServiceWorkerOpArgs::TServiceWorkerTerminateWorkerOpArgs:
op = MakeRefPtr<TerminateServiceWorkerOp>(std::move(aArgs),
std::move(aCallback));
break;
case ServiceWorkerOpArgs::TServiceWorkerLifeCycleEventOpArgs:
op = MakeRefPtr<LifeCycleEventOp>(std::move(aArgs), std::move(aCallback));
break;
case ServiceWorkerOpArgs::TServiceWorkerPushEventOpArgs:
op = MakeRefPtr<PushEventOp>(std::move(aArgs), std::move(aCallback));
break;
case ServiceWorkerOpArgs::TServiceWorkerPushSubscriptionChangeEventOpArgs:
op = MakeRefPtr<PushSubscriptionChangeEventOp>(std::move(aArgs),
std::move(aCallback));
break;
case ServiceWorkerOpArgs::TServiceWorkerNotificationEventOpArgs:
op = MakeRefPtr<NotificationEventOp>(std::move(aArgs),
std::move(aCallback));
break;
case ServiceWorkerOpArgs::TServiceWorkerMessageEventOpArgs:
op = MakeRefPtr<MessageEventOp>(std::move(aArgs), std::move(aCallback));
break;
case ServiceWorkerOpArgs::TParentToChildServiceWorkerFetchEventOpArgs:
op = MakeRefPtr<FetchEventOp>(std::move(aArgs), std::move(aCallback));
break;
case ServiceWorkerOpArgs::TServiceWorkerExtensionAPIEventOpArgs:
op = MakeRefPtr<ExtensionAPIEventOp>(std::move(aArgs),
std::move(aCallback));
break;
default:
MOZ_CRASH("Unknown Service Worker operation!");
return nullptr;
}
return op.forget();
}
} // namespace mozilla::dom