Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 "nsIURI.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"
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 final
: public WorkerDebuggeeRunnable {
public:
NS_DECL_ISUPPORTS_INHERITED
ServiceWorkerOpRunnable(RefPtr<ServiceWorkerOp> aOwner,
WorkerPrivate* aWorkerPrivate)
: WorkerDebuggeeRunnable(aWorkerPrivate, "ServiceWorkerOpRunnable",
WorkerThread),
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);
if (aWorkerPrivate->GlobalScope()->IsDying()) {
Unused << Cancel();
return true;
}
bool rv = mOwner->Exec(aCx, aWorkerPrivate);
Unused << NS_WARN_IF(!rv);
mOwner = nullptr;
return rv;
}
nsresult Cancel() override {
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->GetActorEventTarget()->IsOnCurrentThread());
auto launcherData = aOwner->mLauncherData.Access();
if (NS_WARN_IF(!aOwner->CanSend())) {
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::Canceled>()) ||
NS_WARN_IF(aState.is<RemoteWorkerChild::Killed>())) {
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__);
});
}
// NewRunnableMethod doesn't work here because the template does not appear to
// be able to deal with the owner argument having storage as a RefPtr but
// with the method taking a RefPtr&.
RefPtr<RemoteWorkerChild> owner = aOwner;
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
__func__, [self = std::move(self), owner = std::move(owner)]() mutable {
self->StartOnMainThread(owner);
});
mStarted = true;
MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r.forget()));
return true;
}
void ServiceWorkerOp::StartOnMainThread(RefPtr<RemoteWorkerChild>& aOwner) {
MaybeReportServiceWorkerShutdownProgress(mArgs);
{
auto lock = aOwner->mState.Lock();
if (NS_WARN_IF(!lock->is<Running>() && !IsTerminationOp())) {
RejectAll(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
}
if (IsTerminationOp()) {
aOwner->CloseWorkerOnMainThread();
} else {
auto lock = aOwner->mState.Lock();
MOZ_ASSERT(lock->is<Running>());
RefPtr<WorkerRunnable> workerRunnable =
GetRunnable(lock->as<Running>().mWorkerPrivate);
if (NS_WARN_IF(!workerRunnable->Dispatch())) {
RejectAll(NS_ERROR_FAILURE);
}
}
}
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 NS_OK;
}
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) {
const auto& bytes = args.data().get_ArrayOfuint8_t();
JSObject* data = Uint8Array::Create(aCx, bytes, result);
if (result.Failed()) {
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 interaction count correctly.
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();
auto result = Notification::ConstructFromFields(
aWorkerPrivate->GlobalScope(), args.id(), args.title(), args.dir(),
args.lang(), args.body(), args.tag(), args.icon(), args.data(),
args.scope());
if (NS_WARN_IF(result.isErr())) {
return false;
}
NotificationEventInit init;
init.mNotification = result.unwrap();
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:
if (!deserializationFailed) {
init.mData = messageData;
init.mPorts = std::move(ports);
}
nsCOMPtr<nsIURI> url;
nsresult result = NS_NewURI(getter_AddRefs(url),
mArgs.get_ServiceWorkerMessageEventOpArgs()
.clientInfoAndState()
.info()
.url());
if (NS_WARN_IF(NS_FAILED(result))) {
RejectAll(result);
rv.SuppressException();
return false;
}
OriginAttributes attrs;
nsCOMPtr<nsIPrincipal> principal =
BasePrincipal::CreateContentPrincipal(url, attrs);
if (!principal) {
return false;
}
nsCString origin;
result = principal->GetOriginNoSuffix(origin);
if (NS_WARN_IF(NS_FAILED(result))) {
RejectAll(result);
rv.SuppressException();
return false;
}
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__);
}
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();
mPreloadResponseTimingPromiseRequestHolder.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(GetEnumString(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;
}
if (NS_WARN_IF(response->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();
// 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(std::make_tuple(
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});
// 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();
}
/*
*
* "If request is a non-subresource request and request’s
* destination is not "report", initialize e’s resultingClientId attribute
* to reservedClient’s [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<FetchEventPreloadResponseTimingPromise>
preloadResponseTimingPromise =
mActor->GetPreloadResponseTimingPromise();
MOZ_ASSERT(preloadResponseTimingPromise);
preloadResponseTimingPromise
->Then(
GetCurrentSerialEventTarget(), __func__,
[self, performanceStorage,
globalObjectAsSupports](ResponseTiming&& aTiming) {
if (performanceStorage && !aTiming.entryName().IsEmpty() &&
aTiming.initiatorType().Equals(u"navigation"_ns)) {
performanceStorage->AddEntry(
aTiming.entryName(), aTiming.initiatorType(),
MakeUnique<PerformanceTimingData>(aTiming.timingData()));
}
self->mPreloadResponseTimingPromiseRequestHolder.Complete();
},
[self](int) {
self->mPreloadResponseTimingPromiseRequestHolder.Complete();
})
->Track(mPreloadResponseTimingPromiseRequestHolder);
RefPtr<FetchEventPreloadResponseEndPromise> preloadResponseEndPromise =
mActor->GetPreloadResponseEndPromise();
MOZ_ASSERT(preloadResponseEndPromise);
preloadResponseEndPromise
->Then(
GetCurrentSerialEventTarget(), __func__,
[self, globalObjectAsSupports](ResponseEndArgs&& aArgs) {
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)) {
// 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 {
// 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