gecko-dev/dom/serviceworkers/ServiceWorkerOp.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1914 lines
62 KiB
C++
Raw Normal View History

/* -*- 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>
Bug 1577346 - P4 IPC implementation for FetchEvent.preloadResponse. r=dom-worker-reviewers,ytausky This patch implements the IPC for propagating the preload response from the parent process' main thread to the content process' worker thread. The following is the complicated propagation path. FetchEventOpChild(Parent process main thread) => FetchEventOpParent(Parent process background thread) => FetchEventOpProxyParent(Parent process background thread) => FetchEventOpProxyChild(content process worker launch thread) => ServiceWorker(content process worker thread) However, since preload response fetching is an asynchronous behavior, the preload response can be ready at any time point during the fetch event dispatching. This patch also handles different situations. Basically, it can be separated into the following stages. 1. Preload response is ready when the fetch event dispatching is in the parent process main thread, which means the fetch event is pending as a ServiceWorkerPrivateImpl::PendingFetchEvent. Details in PendingFetchEvent and FetchEventOpChild 2. Preload response is ready when the fetch event dispatching is in the parent process background thread, which means fetch event is still waiting for worker launching. Details in FetchEventOpParent and FetchEventOpProxyParent. 3. Preload response is ready when constructing the fetch event. Details in FetchEventOpProxyChild and ServiceWorkerOp::FetchEventOp. Depends on D126244 Differential Revision: https://phabricator.services.mozilla.com/D122821
2021-11-09 20:24:27 +00:00
#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"
Bug 1728327 - Introduce a new nsIServiceWorkerManager.wakeForExtensionAPIEvent method. r=asuth This patch includes a set of changes to the ServiceWorker internals to introduce a new nsIServiceWorkerManager.wakeForExtensionAPIEvent method, to be used by the WebExtensions internals to request an active background service worker to be spawned (if it is not yet) in response to a WebExtension API event. The new method gets as parameters: - the scope URL for the extension background service worker to spawn - WebExtensions API namespace and API event name which we are going to spawn an active worker for and return a promise which would be: - rejected if the worker could not be spawned - resolved to a boolean if the worker was spawned successfully (or already running) The value of the boolean value resolved is meant to represent if the worker did actually have any listener subscribed for the given WebExtensions API event listener (which the WebExtensions internals may then use to decide if it is worth to send that event to be handled by the worker script or not). In this patch the ExtensionBrowser::HasWakeupEventListener used to determine if an WebExtensions API event was subscribed syncronously when the worker was being loaded is not implemented yet and it is always returning false (a proposed implementation for that method is going to be added in a separate patch part of this same bugzilla issue). A unit test for the new proposed nsIServiceWorkerManager method is also part of a separate patch (attached to this bugzilla issue as the child revision for this one). Differential Revision: https://phabricator.services.mozilla.com/D130756
2021-12-15 18:29:36 +00:00
#include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
#include "mozilla/dom/WorkerCommon.h"
#include "mozilla/dom/WorkerRef.h"
#include "mozilla/dom/WorkerScope.h"
Bug 1728327 - Introduce a new nsIServiceWorkerManager.wakeForExtensionAPIEvent method. r=asuth This patch includes a set of changes to the ServiceWorker internals to introduce a new nsIServiceWorkerManager.wakeForExtensionAPIEvent method, to be used by the WebExtensions internals to request an active background service worker to be spawned (if it is not yet) in response to a WebExtension API event. The new method gets as parameters: - the scope URL for the extension background service worker to spawn - WebExtensions API namespace and API event name which we are going to spawn an active worker for and return a promise which would be: - rejected if the worker could not be spawned - resolved to a boolean if the worker was spawned successfully (or already running) The value of the boolean value resolved is meant to represent if the worker did actually have any listener subscribed for the given WebExtensions API event listener (which the WebExtensions internals may then use to decide if it is worth to send that event to be handled by the worker script or not). In this patch the ExtensionBrowser::HasWakeupEventListener used to determine if an WebExtensions API event was subscribed syncronously when the worker was being loaded is not implemented yet and it is always returning false (a proposed implementation for that method is going to be added in a separate patch part of this same bugzilla issue). A unit test for the new proposed nsIServiceWorkerManager method is also part of a separate patch (attached to this bugzilla issue as the child revision for this one). Differential Revision: https://phabricator.services.mozilla.com/D130756
2021-12-15 18:29:36 +00:00
#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);
Bug 1693074 - Adding telemetry for evaluating the duration of fetch event dispatching and response synthesizing. r=dom-worker-reviewers,asuth This patch tries to record the fetch event dispatching time, the response's synthesizing time, and interception resetting time. Fetch event dispatching time is the time duration between interception starts, which is the time point of InterceptedHttpChannel::AsyncOpenInternal(), and the fetch handler starts. It includes the InterceptedHttpChannel setup time, ServiceWorker launch time, FetchEventOp propagation through IPC, a FetchEvent creation, initialization, and dispatching/scheduling on worker scope. Response synthesizing time is the time duration between the fetch handler finishes, which is the resolving/rejecting promise of respondWith(), to the finish of pumping the synthesized response to InterceptedHttpChannel, which is the time point of calling InterceptedHttpChannel::OnStopRequest(). It includes the response propagation through IPC, response header and body synthesis, and pumping synthesized response to the channel. Interception resetting time is the time duration between the fetch handler finishes and redirecting InterceptedHttpChannel to a normal HTTP channel. Since the fetch handler is executed on the process where the service worker spawned, the timestamps related to the fetch handler need to be get on that process. So this patch adds the FetchHandlerStart and FetchHandlerFinish on IPCFetchEventRespondWithResult related types to propagate the timestamps to the parent process. Depends on D118398 Differential Revision: https://phabricator.services.mozilla.com/D118399
2021-08-22 11:02:18 +00:00
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() {
Bug 1693074 - Adding telemetry for evaluating the duration of fetch event dispatching and response synthesizing. r=dom-worker-reviewers,asuth This patch tries to record the fetch event dispatching time, the response's synthesizing time, and interception resetting time. Fetch event dispatching time is the time duration between interception starts, which is the time point of InterceptedHttpChannel::AsyncOpenInternal(), and the fetch handler starts. It includes the InterceptedHttpChannel setup time, ServiceWorker launch time, FetchEventOp propagation through IPC, a FetchEvent creation, initialization, and dispatching/scheduling on worker scope. Response synthesizing time is the time duration between the fetch handler finishes, which is the resolving/rejecting promise of respondWith(), to the finish of pumping the synthesized response to InterceptedHttpChannel, which is the time point of calling InterceptedHttpChannel::OnStopRequest(). It includes the response propagation through IPC, response header and body synthesis, and pumping synthesized response to the channel. Interception resetting time is the time duration between the fetch handler finishes and redirecting InterceptedHttpChannel to a normal HTTP channel. Since the fetch handler is executed on the process where the service worker spawned, the timestamps related to the fetch handler need to be get on that process. So this patch adds the FetchHandlerStart and FetchHandlerFinish on IPCFetchEventRespondWithResult related types to propagate the timestamps to the parent process. Depends on D118398 Differential Revision: https://phabricator.services.mozilla.com/D118399
2021-08-22 11:02:18 +00:00
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());
Bug 1693074 - Adding telemetry for evaluating the duration of fetch event dispatching and response synthesizing. r=dom-worker-reviewers,asuth This patch tries to record the fetch event dispatching time, the response's synthesizing time, and interception resetting time. Fetch event dispatching time is the time duration between interception starts, which is the time point of InterceptedHttpChannel::AsyncOpenInternal(), and the fetch handler starts. It includes the InterceptedHttpChannel setup time, ServiceWorker launch time, FetchEventOp propagation through IPC, a FetchEvent creation, initialization, and dispatching/scheduling on worker scope. Response synthesizing time is the time duration between the fetch handler finishes, which is the resolving/rejecting promise of respondWith(), to the finish of pumping the synthesized response to InterceptedHttpChannel, which is the time point of calling InterceptedHttpChannel::OnStopRequest(). It includes the response propagation through IPC, response header and body synthesis, and pumping synthesized response to the channel. Interception resetting time is the time duration between the fetch handler finishes and redirecting InterceptedHttpChannel to a normal HTTP channel. Since the fetch handler is executed on the process where the service worker spawned, the timestamps related to the fetch handler need to be get on that process. So this patch adds the FetchHandlerStart and FetchHandlerFinish on IPCFetchEventRespondWithResult related types to propagate the timestamps to the parent process. Depends on D118398 Differential Revision: https://phabricator.services.mozilla.com/D118399
2021-08-22 11:02:18 +00:00
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;
Bug 1577346 - P4 IPC implementation for FetchEvent.preloadResponse. r=dom-worker-reviewers,ytausky This patch implements the IPC for propagating the preload response from the parent process' main thread to the content process' worker thread. The following is the complicated propagation path. FetchEventOpChild(Parent process main thread) => FetchEventOpParent(Parent process background thread) => FetchEventOpProxyParent(Parent process background thread) => FetchEventOpProxyChild(content process worker launch thread) => ServiceWorker(content process worker thread) However, since preload response fetching is an asynchronous behavior, the preload response can be ready at any time point during the fetch event dispatching. This patch also handles different situations. Basically, it can be separated into the following stages. 1. Preload response is ready when the fetch event dispatching is in the parent process main thread, which means the fetch event is pending as a ServiceWorkerPrivateImpl::PendingFetchEvent. Details in PendingFetchEvent and FetchEventOpChild 2. Preload response is ready when the fetch event dispatching is in the parent process background thread, which means fetch event is still waiting for worker launching. Details in FetchEventOpParent and FetchEventOpProxyParent. 3. Preload response is ready when constructing the fetch event. Details in FetchEventOpProxyChild and ServiceWorkerOp::FetchEventOp. Depends on D126244 Differential Revision: https://phabricator.services.mozilla.com/D122821
2021-11-09 20:24:27 +00:00
mPreloadResponse = nullptr;
Bug 1765777 - Resolve FetchEvent.preloadResponse when the response is available not the response end. r=dom-worker-reviewers,jesup. In the previous implementation, FetchEvent.preloadResponse is resolved when the response fetching finishes. However, ServiceWorker responding letency could be increased since waiting for preloadResponse finishes. The patch resolves FetchEvent.preloadResponse earlier when the response is available. The basic idea is to resolve the preload response when FetchInstance::OnResponseAvailableInternal() is called. Then propagating the response from the parent process main thread to the content process worker thread. This is achieved by IPC PFetchEventOp::Send/RecvPreloadResponse -> PFetchEventOpProxy::Send/RecvPreloadResponse. Since we can only get the response's ResourceTiming when FetchInstance::OnResponseEnd() is called. This patch introduces a new IPC method to propagate the ResourceTiming information from the parent process main thread to the content process worker thread. PFetchEventOp::Send/RecvPreloadResponseEnd -> PFetchEventOpProxy->Send/RecvPreloadResponseEnd. The tricky of this patch is we must extend the life cycle of FetchEventOp object if preloadResponse is set into FetchEvent. That because ServiceWorker could resolve FetchEvent.respondWith() by using FetchEvent.preloadResponse. In that case, FetchEventOp will get into a finish state and try to call FetchEventOpProxyChild::Senddelete with the operation result. However, the ResponseEnd could not be called at the moment, and we need to wait for the corresponding timing information and its end reason. To extend the life cycle of FetchEventOp, this patch cached the operation result while we get FetchEventOp::Callback is called. Then call FetchEventOpProxyChile::Senddelete() in FetchEventOpProxyChild::RecvPreloadResponseEnd() to close IPC. Or Senddelete() will be called while ActorDestroy() caused by shutdown. Differential Revision: https://phabricator.services.mozilla.com/D145338
2022-05-11 19:40:47 +00:00
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());
Bug 1693074 - Adding telemetry for evaluating the duration of fetch event dispatching and response synthesizing. r=dom-worker-reviewers,asuth This patch tries to record the fetch event dispatching time, the response's synthesizing time, and interception resetting time. Fetch event dispatching time is the time duration between interception starts, which is the time point of InterceptedHttpChannel::AsyncOpenInternal(), and the fetch handler starts. It includes the InterceptedHttpChannel setup time, ServiceWorker launch time, FetchEventOp propagation through IPC, a FetchEvent creation, initialization, and dispatching/scheduling on worker scope. Response synthesizing time is the time duration between the fetch handler finishes, which is the resolving/rejecting promise of respondWith(), to the finish of pumping the synthesized response to InterceptedHttpChannel, which is the time point of calling InterceptedHttpChannel::OnStopRequest(). It includes the response propagation through IPC, response header and body synthesis, and pumping synthesized response to the channel. Interception resetting time is the time duration between the fetch handler finishes and redirecting InterceptedHttpChannel to a normal HTTP channel. Since the fetch handler is executed on the process where the service worker spawned, the timestamps related to the fetch handler need to be get on that process. So this patch adds the FetchHandlerStart and FetchHandlerFinish on IPCFetchEventRespondWithResult related types to propagate the timestamps to the parent process. Depends on D118398 Differential Revision: https://phabricator.services.mozilla.com/D118399
2021-08-22 11:02:18 +00:00
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(
Bug 1693074 - Adding telemetry for evaluating the duration of fetch event dispatching and response synthesizing. r=dom-worker-reviewers,asuth This patch tries to record the fetch event dispatching time, the response's synthesizing time, and interception resetting time. Fetch event dispatching time is the time duration between interception starts, which is the time point of InterceptedHttpChannel::AsyncOpenInternal(), and the fetch handler starts. It includes the InterceptedHttpChannel setup time, ServiceWorker launch time, FetchEventOp propagation through IPC, a FetchEvent creation, initialization, and dispatching/scheduling on worker scope. Response synthesizing time is the time duration between the fetch handler finishes, which is the resolving/rejecting promise of respondWith(), to the finish of pumping the synthesized response to InterceptedHttpChannel, which is the time point of calling InterceptedHttpChannel::OnStopRequest(). It includes the response propagation through IPC, response header and body synthesis, and pumping synthesized response to the channel. Interception resetting time is the time duration between the fetch handler finishes and redirecting InterceptedHttpChannel to a normal HTTP channel. Since the fetch handler is executed on the process where the service worker spawned, the timestamps related to the fetch handler need to be get on that process. So this patch adds the FetchHandlerStart and FetchHandlerFinish on IPCFetchEventRespondWithResult related types to propagate the timestamps to the parent process. Depends on D118398 Differential Revision: https://phabricator.services.mozilla.com/D118399
2021-08-22 11:02:18 +00:00
FetchEventRespondWithResult(MakeTuple(
std::move(ir), mRespondWithClosure.ref(),
Bug 1693074 - Adding telemetry for evaluating the duration of fetch event dispatching and response synthesizing. r=dom-worker-reviewers,asuth This patch tries to record the fetch event dispatching time, the response's synthesizing time, and interception resetting time. Fetch event dispatching time is the time duration between interception starts, which is the time point of InterceptedHttpChannel::AsyncOpenInternal(), and the fetch handler starts. It includes the InterceptedHttpChannel setup time, ServiceWorker launch time, FetchEventOp propagation through IPC, a FetchEvent creation, initialization, and dispatching/scheduling on worker scope. Response synthesizing time is the time duration between the fetch handler finishes, which is the resolving/rejecting promise of respondWith(), to the finish of pumping the synthesized response to InterceptedHttpChannel, which is the time point of calling InterceptedHttpChannel::OnStopRequest(). It includes the response propagation through IPC, response header and body synthesis, and pumping synthesized response to the channel. Interception resetting time is the time duration between the fetch handler finishes and redirecting InterceptedHttpChannel to a normal HTTP channel. Since the fetch handler is executed on the process where the service worker spawned, the timestamps related to the fetch handler need to be get on that process. So this patch adds the FetchHandlerStart and FetchHandlerFinish on IPCFetchEventRespondWithResult related types to propagate the timestamps to the parent process. Depends on D118398 Differential Revision: https://phabricator.services.mozilla.com/D118399
2021-08-22 11:02:18 +00:00
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());
Bug 1693074 - Adding telemetry for evaluating the duration of fetch event dispatching and response synthesizing. r=dom-worker-reviewers,asuth This patch tries to record the fetch event dispatching time, the response's synthesizing time, and interception resetting time. Fetch event dispatching time is the time duration between interception starts, which is the time point of InterceptedHttpChannel::AsyncOpenInternal(), and the fetch handler starts. It includes the InterceptedHttpChannel setup time, ServiceWorker launch time, FetchEventOp propagation through IPC, a FetchEvent creation, initialization, and dispatching/scheduling on worker scope. Response synthesizing time is the time duration between the fetch handler finishes, which is the resolving/rejecting promise of respondWith(), to the finish of pumping the synthesized response to InterceptedHttpChannel, which is the time point of calling InterceptedHttpChannel::OnStopRequest(). It includes the response propagation through IPC, response header and body synthesis, and pumping synthesized response to the channel. Interception resetting time is the time duration between the fetch handler finishes and redirecting InterceptedHttpChannel to a normal HTTP channel. Since the fetch handler is executed on the process where the service worker spawned, the timestamps related to the fetch handler need to be get on that process. So this patch adds the FetchHandlerStart and FetchHandlerFinish on IPCFetchEventRespondWithResult related types to propagate the timestamps to the parent process. Depends on D118398 Differential Revision: https://phabricator.services.mozilla.com/D118399
2021-08-22 11:02:18 +00:00
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(
Bug 1693074 - Adding telemetry for evaluating the duration of fetch event dispatching and response synthesizing. r=dom-worker-reviewers,asuth This patch tries to record the fetch event dispatching time, the response's synthesizing time, and interception resetting time. Fetch event dispatching time is the time duration between interception starts, which is the time point of InterceptedHttpChannel::AsyncOpenInternal(), and the fetch handler starts. It includes the InterceptedHttpChannel setup time, ServiceWorker launch time, FetchEventOp propagation through IPC, a FetchEvent creation, initialization, and dispatching/scheduling on worker scope. Response synthesizing time is the time duration between the fetch handler finishes, which is the resolving/rejecting promise of respondWith(), to the finish of pumping the synthesized response to InterceptedHttpChannel, which is the time point of calling InterceptedHttpChannel::OnStopRequest(). It includes the response propagation through IPC, response header and body synthesis, and pumping synthesized response to the channel. Interception resetting time is the time duration between the fetch handler finishes and redirecting InterceptedHttpChannel to a normal HTTP channel. Since the fetch handler is executed on the process where the service worker spawned, the timestamps related to the fetch handler need to be get on that process. So this patch adds the FetchHandlerStart and FetchHandlerFinish on IPCFetchEventRespondWithResult related types to propagate the timestamps to the parent process. Depends on D118398 Differential Revision: https://phabricator.services.mozilla.com/D118399
2021-08-22 11:02:18 +00:00
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();
Bug 1577346 - P4 IPC implementation for FetchEvent.preloadResponse. r=dom-worker-reviewers,ytausky This patch implements the IPC for propagating the preload response from the parent process' main thread to the content process' worker thread. The following is the complicated propagation path. FetchEventOpChild(Parent process main thread) => FetchEventOpParent(Parent process background thread) => FetchEventOpProxyParent(Parent process background thread) => FetchEventOpProxyChild(content process worker launch thread) => ServiceWorker(content process worker thread) However, since preload response fetching is an asynchronous behavior, the preload response can be ready at any time point during the fetch event dispatching. This patch also handles different situations. Basically, it can be separated into the following stages. 1. Preload response is ready when the fetch event dispatching is in the parent process main thread, which means the fetch event is pending as a ServiceWorkerPrivateImpl::PendingFetchEvent. Details in PendingFetchEvent and FetchEventOpChild 2. Preload response is ready when the fetch event dispatching is in the parent process background thread, which means fetch event is still waiting for worker launching. Details in FetchEventOpParent and FetchEventOpProxyParent. 3. Preload response is ready when constructing the fetch event. Details in FetchEventOpProxyChild and ServiceWorkerOp::FetchEventOp. Depends on D126244 Differential Revision: https://phabricator.services.mozilla.com/D122821
2021-11-09 20:24:27 +00:00
mPreloadResponse = fetchEvent->PreloadResponse();
if (args.common().preloadNavigation()) {
Bug 1765777 - Resolve FetchEvent.preloadResponse when the response is available not the response end. r=dom-worker-reviewers,jesup. In the previous implementation, FetchEvent.preloadResponse is resolved when the response fetching finishes. However, ServiceWorker responding letency could be increased since waiting for preloadResponse finishes. The patch resolves FetchEvent.preloadResponse earlier when the response is available. The basic idea is to resolve the preload response when FetchInstance::OnResponseAvailableInternal() is called. Then propagating the response from the parent process main thread to the content process worker thread. This is achieved by IPC PFetchEventOp::Send/RecvPreloadResponse -> PFetchEventOpProxy::Send/RecvPreloadResponse. Since we can only get the response's ResourceTiming when FetchInstance::OnResponseEnd() is called. This patch introduces a new IPC method to propagate the ResourceTiming information from the parent process main thread to the content process worker thread. PFetchEventOp::Send/RecvPreloadResponseEnd -> PFetchEventOpProxy->Send/RecvPreloadResponseEnd. The tricky of this patch is we must extend the life cycle of FetchEventOp object if preloadResponse is set into FetchEvent. That because ServiceWorker could resolve FetchEvent.respondWith() by using FetchEvent.preloadResponse. In that case, FetchEventOp will get into a finish state and try to call FetchEventOpProxyChild::Senddelete with the operation result. However, the ResponseEnd could not be called at the moment, and we need to wait for the corresponding timing information and its end reason. To extend the life cycle of FetchEventOp, this patch cached the operation result while we get FetchEventOp::Callback is called. Then call FetchEventOpProxyChile::Senddelete() in FetchEventOpProxyChild::RecvPreloadResponseEnd() to close IPC. Or Senddelete() will be called while ActorDestroy() caused by shutdown. Differential Revision: https://phabricator.services.mozilla.com/D145338
2022-05-11 19:40:47 +00:00
RefPtr<FetchEventPreloadResponseAvailablePromise> preloadResponsePromise =
mActor->GetPreloadResponseAvailablePromise();
Bug 1577346 - P4 IPC implementation for FetchEvent.preloadResponse. r=dom-worker-reviewers,ytausky This patch implements the IPC for propagating the preload response from the parent process' main thread to the content process' worker thread. The following is the complicated propagation path. FetchEventOpChild(Parent process main thread) => FetchEventOpParent(Parent process background thread) => FetchEventOpProxyParent(Parent process background thread) => FetchEventOpProxyChild(content process worker launch thread) => ServiceWorker(content process worker thread) However, since preload response fetching is an asynchronous behavior, the preload response can be ready at any time point during the fetch event dispatching. This patch also handles different situations. Basically, it can be separated into the following stages. 1. Preload response is ready when the fetch event dispatching is in the parent process main thread, which means the fetch event is pending as a ServiceWorkerPrivateImpl::PendingFetchEvent. Details in PendingFetchEvent and FetchEventOpChild 2. Preload response is ready when the fetch event dispatching is in the parent process background thread, which means fetch event is still waiting for worker launching. Details in FetchEventOpParent and FetchEventOpProxyParent. 3. Preload response is ready when constructing the fetch event. Details in FetchEventOpProxyChild and ServiceWorkerOp::FetchEventOp. Depends on D126244 Differential Revision: https://phabricator.services.mozilla.com/D122821
2021-11-09 20:24:27 +00:00
MOZ_ASSERT(preloadResponsePromise);
// If preloadResponsePromise has already settled then this callback will get
// run synchronously here.
RefPtr<FetchEventOp> self = this;
Bug 1765777 - Resolve FetchEvent.preloadResponse when the response is available not the response end. r=dom-worker-reviewers,jesup. In the previous implementation, FetchEvent.preloadResponse is resolved when the response fetching finishes. However, ServiceWorker responding letency could be increased since waiting for preloadResponse finishes. The patch resolves FetchEvent.preloadResponse earlier when the response is available. The basic idea is to resolve the preload response when FetchInstance::OnResponseAvailableInternal() is called. Then propagating the response from the parent process main thread to the content process worker thread. This is achieved by IPC PFetchEventOp::Send/RecvPreloadResponse -> PFetchEventOpProxy::Send/RecvPreloadResponse. Since we can only get the response's ResourceTiming when FetchInstance::OnResponseEnd() is called. This patch introduces a new IPC method to propagate the ResourceTiming information from the parent process main thread to the content process worker thread. PFetchEventOp::Send/RecvPreloadResponseEnd -> PFetchEventOpProxy->Send/RecvPreloadResponseEnd. The tricky of this patch is we must extend the life cycle of FetchEventOp object if preloadResponse is set into FetchEvent. That because ServiceWorker could resolve FetchEvent.respondWith() by using FetchEvent.preloadResponse. In that case, FetchEventOp will get into a finish state and try to call FetchEventOpProxyChild::Senddelete with the operation result. However, the ResponseEnd could not be called at the moment, and we need to wait for the corresponding timing information and its end reason. To extend the life cycle of FetchEventOp, this patch cached the operation result while we get FetchEventOp::Callback is called. Then call FetchEventOpProxyChile::Senddelete() in FetchEventOpProxyChild::RecvPreloadResponseEnd() to close IPC. Or Senddelete() will be called while ActorDestroy() caused by shutdown. Differential Revision: https://phabricator.services.mozilla.com/D145338
2022-05-11 19:40:47 +00:00
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();
Bug 1765777 - Resolve FetchEvent.preloadResponse when the response is available not the response end. r=dom-worker-reviewers,jesup. In the previous implementation, FetchEvent.preloadResponse is resolved when the response fetching finishes. However, ServiceWorker responding letency could be increased since waiting for preloadResponse finishes. The patch resolves FetchEvent.preloadResponse earlier when the response is available. The basic idea is to resolve the preload response when FetchInstance::OnResponseAvailableInternal() is called. Then propagating the response from the parent process main thread to the content process worker thread. This is achieved by IPC PFetchEventOp::Send/RecvPreloadResponse -> PFetchEventOpProxy::Send/RecvPreloadResponse. Since we can only get the response's ResourceTiming when FetchInstance::OnResponseEnd() is called. This patch introduces a new IPC method to propagate the ResourceTiming information from the parent process main thread to the content process worker thread. PFetchEventOp::Send/RecvPreloadResponseEnd -> PFetchEventOpProxy->Send/RecvPreloadResponseEnd. The tricky of this patch is we must extend the life cycle of FetchEventOp object if preloadResponse is set into FetchEvent. That because ServiceWorker could resolve FetchEvent.respondWith() by using FetchEvent.preloadResponse. In that case, FetchEventOp will get into a finish state and try to call FetchEventOpProxyChild::Senddelete with the operation result. However, the ResponseEnd could not be called at the moment, and we need to wait for the corresponding timing information and its end reason. To extend the life cycle of FetchEventOp, this patch cached the operation result while we get FetchEventOp::Callback is called. Then call FetchEventOpProxyChile::Senddelete() in FetchEventOpProxyChild::RecvPreloadResponseEnd() to close IPC. Or Senddelete() will be called while ActorDestroy() caused by shutdown. Differential Revision: https://phabricator.services.mozilla.com/D145338
2022-05-11 19:40:47 +00:00
RefPtr<FetchEventPreloadResponseEndPromise> preloadResponseEndPromise =
mActor->GetPreloadResponseEndPromise();
MOZ_ASSERT(preloadResponseEndPromise);
preloadResponseEndPromise
Bug 1577346 - P4 IPC implementation for FetchEvent.preloadResponse. r=dom-worker-reviewers,ytausky This patch implements the IPC for propagating the preload response from the parent process' main thread to the content process' worker thread. The following is the complicated propagation path. FetchEventOpChild(Parent process main thread) => FetchEventOpParent(Parent process background thread) => FetchEventOpProxyParent(Parent process background thread) => FetchEventOpProxyChild(content process worker launch thread) => ServiceWorker(content process worker thread) However, since preload response fetching is an asynchronous behavior, the preload response can be ready at any time point during the fetch event dispatching. This patch also handles different situations. Basically, it can be separated into the following stages. 1. Preload response is ready when the fetch event dispatching is in the parent process main thread, which means the fetch event is pending as a ServiceWorkerPrivateImpl::PendingFetchEvent. Details in PendingFetchEvent and FetchEventOpChild 2. Preload response is ready when the fetch event dispatching is in the parent process background thread, which means fetch event is still waiting for worker launching. Details in FetchEventOpParent and FetchEventOpProxyParent. 3. Preload response is ready when constructing the fetch event. Details in FetchEventOpProxyChild and ServiceWorkerOp::FetchEventOp. Depends on D126244 Differential Revision: https://phabricator.services.mozilla.com/D122821
2021-11-09 20:24:27 +00:00
->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()));
}
Bug 1765777 - Resolve FetchEvent.preloadResponse when the response is available not the response end. r=dom-worker-reviewers,jesup. In the previous implementation, FetchEvent.preloadResponse is resolved when the response fetching finishes. However, ServiceWorker responding letency could be increased since waiting for preloadResponse finishes. The patch resolves FetchEvent.preloadResponse earlier when the response is available. The basic idea is to resolve the preload response when FetchInstance::OnResponseAvailableInternal() is called. Then propagating the response from the parent process main thread to the content process worker thread. This is achieved by IPC PFetchEventOp::Send/RecvPreloadResponse -> PFetchEventOpProxy::Send/RecvPreloadResponse. Since we can only get the response's ResourceTiming when FetchInstance::OnResponseEnd() is called. This patch introduces a new IPC method to propagate the ResourceTiming information from the parent process main thread to the content process worker thread. PFetchEventOp::Send/RecvPreloadResponseEnd -> PFetchEventOpProxy->Send/RecvPreloadResponseEnd. The tricky of this patch is we must extend the life cycle of FetchEventOp object if preloadResponse is set into FetchEvent. That because ServiceWorker could resolve FetchEvent.respondWith() by using FetchEvent.preloadResponse. In that case, FetchEventOp will get into a finish state and try to call FetchEventOpProxyChild::Senddelete with the operation result. However, the ResponseEnd could not be called at the moment, and we need to wait for the corresponding timing information and its end reason. To extend the life cycle of FetchEventOp, this patch cached the operation result while we get FetchEventOp::Callback is called. Then call FetchEventOpProxyChile::Senddelete() in FetchEventOpProxyChild::RecvPreloadResponseEnd() to close IPC. Or Senddelete() will be called while ActorDestroy() caused by shutdown. Differential Revision: https://phabricator.services.mozilla.com/D145338
2022-05-11 19:40:47 +00:00
if (aArgs.endReason() == FetchDriverObserver::eAborted) {
self->mPreloadResponse->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
}
self->mPreloadResponseEndPromiseRequestHolder.Complete();
Bug 1577346 - P4 IPC implementation for FetchEvent.preloadResponse. r=dom-worker-reviewers,ytausky This patch implements the IPC for propagating the preload response from the parent process' main thread to the content process' worker thread. The following is the complicated propagation path. FetchEventOpChild(Parent process main thread) => FetchEventOpParent(Parent process background thread) => FetchEventOpProxyParent(Parent process background thread) => FetchEventOpProxyChild(content process worker launch thread) => ServiceWorker(content process worker thread) However, since preload response fetching is an asynchronous behavior, the preload response can be ready at any time point during the fetch event dispatching. This patch also handles different situations. Basically, it can be separated into the following stages. 1. Preload response is ready when the fetch event dispatching is in the parent process main thread, which means the fetch event is pending as a ServiceWorkerPrivateImpl::PendingFetchEvent. Details in PendingFetchEvent and FetchEventOpChild 2. Preload response is ready when the fetch event dispatching is in the parent process background thread, which means fetch event is still waiting for worker launching. Details in FetchEventOpParent and FetchEventOpProxyParent. 3. Preload response is ready when constructing the fetch event. Details in FetchEventOpProxyChild and ServiceWorkerOp::FetchEventOp. Depends on D126244 Differential Revision: https://phabricator.services.mozilla.com/D122821
2021-11-09 20:24:27 +00:00
},
[self](int) {
Bug 1765777 - Resolve FetchEvent.preloadResponse when the response is available not the response end. r=dom-worker-reviewers,jesup. In the previous implementation, FetchEvent.preloadResponse is resolved when the response fetching finishes. However, ServiceWorker responding letency could be increased since waiting for preloadResponse finishes. The patch resolves FetchEvent.preloadResponse earlier when the response is available. The basic idea is to resolve the preload response when FetchInstance::OnResponseAvailableInternal() is called. Then propagating the response from the parent process main thread to the content process worker thread. This is achieved by IPC PFetchEventOp::Send/RecvPreloadResponse -> PFetchEventOpProxy::Send/RecvPreloadResponse. Since we can only get the response's ResourceTiming when FetchInstance::OnResponseEnd() is called. This patch introduces a new IPC method to propagate the ResourceTiming information from the parent process main thread to the content process worker thread. PFetchEventOp::Send/RecvPreloadResponseEnd -> PFetchEventOpProxy->Send/RecvPreloadResponseEnd. The tricky of this patch is we must extend the life cycle of FetchEventOp object if preloadResponse is set into FetchEvent. That because ServiceWorker could resolve FetchEvent.respondWith() by using FetchEvent.preloadResponse. In that case, FetchEventOp will get into a finish state and try to call FetchEventOpProxyChild::Senddelete with the operation result. However, the ResponseEnd could not be called at the moment, and we need to wait for the corresponding timing information and its end reason. To extend the life cycle of FetchEventOp, this patch cached the operation result while we get FetchEventOp::Callback is called. Then call FetchEventOpProxyChile::Senddelete() in FetchEventOpProxyChild::RecvPreloadResponseEnd() to close IPC. Or Senddelete() will be called while ActorDestroy() caused by shutdown. Differential Revision: https://phabricator.services.mozilla.com/D145338
2022-05-11 19:40:47 +00:00
self->mPreloadResponseEndPromiseRequestHolder.Complete();
Bug 1577346 - P4 IPC implementation for FetchEvent.preloadResponse. r=dom-worker-reviewers,ytausky This patch implements the IPC for propagating the preload response from the parent process' main thread to the content process' worker thread. The following is the complicated propagation path. FetchEventOpChild(Parent process main thread) => FetchEventOpParent(Parent process background thread) => FetchEventOpProxyParent(Parent process background thread) => FetchEventOpProxyChild(content process worker launch thread) => ServiceWorker(content process worker thread) However, since preload response fetching is an asynchronous behavior, the preload response can be ready at any time point during the fetch event dispatching. This patch also handles different situations. Basically, it can be separated into the following stages. 1. Preload response is ready when the fetch event dispatching is in the parent process main thread, which means the fetch event is pending as a ServiceWorkerPrivateImpl::PendingFetchEvent. Details in PendingFetchEvent and FetchEventOpChild 2. Preload response is ready when the fetch event dispatching is in the parent process background thread, which means fetch event is still waiting for worker launching. Details in FetchEventOpParent and FetchEventOpProxyParent. 3. Preload response is ready when constructing the fetch event. Details in FetchEventOpProxyChild and ServiceWorkerOp::FetchEventOp. Depends on D126244 Differential Revision: https://phabricator.services.mozilla.com/D122821
2021-11-09 20:24:27 +00:00
})
Bug 1765777 - Resolve FetchEvent.preloadResponse when the response is available not the response end. r=dom-worker-reviewers,jesup. In the previous implementation, FetchEvent.preloadResponse is resolved when the response fetching finishes. However, ServiceWorker responding letency could be increased since waiting for preloadResponse finishes. The patch resolves FetchEvent.preloadResponse earlier when the response is available. The basic idea is to resolve the preload response when FetchInstance::OnResponseAvailableInternal() is called. Then propagating the response from the parent process main thread to the content process worker thread. This is achieved by IPC PFetchEventOp::Send/RecvPreloadResponse -> PFetchEventOpProxy::Send/RecvPreloadResponse. Since we can only get the response's ResourceTiming when FetchInstance::OnResponseEnd() is called. This patch introduces a new IPC method to propagate the ResourceTiming information from the parent process main thread to the content process worker thread. PFetchEventOp::Send/RecvPreloadResponseEnd -> PFetchEventOpProxy->Send/RecvPreloadResponseEnd. The tricky of this patch is we must extend the life cycle of FetchEventOp object if preloadResponse is set into FetchEvent. That because ServiceWorker could resolve FetchEvent.respondWith() by using FetchEvent.preloadResponse. In that case, FetchEventOp will get into a finish state and try to call FetchEventOpProxyChild::Senddelete with the operation result. However, the ResponseEnd could not be called at the moment, and we need to wait for the corresponding timing information and its end reason. To extend the life cycle of FetchEventOp, this patch cached the operation result while we get FetchEventOp::Callback is called. Then call FetchEventOpProxyChile::Senddelete() in FetchEventOpProxyChild::RecvPreloadResponseEnd() to close IPC. Or Senddelete() will be called while ActorDestroy() caused by shutdown. Differential Revision: https://phabricator.services.mozilla.com/D145338
2022-05-11 19:40:47 +00:00
->Track(mPreloadResponseEndPromiseRequestHolder);
Bug 1577346 - P4 IPC implementation for FetchEvent.preloadResponse. r=dom-worker-reviewers,ytausky This patch implements the IPC for propagating the preload response from the parent process' main thread to the content process' worker thread. The following is the complicated propagation path. FetchEventOpChild(Parent process main thread) => FetchEventOpParent(Parent process background thread) => FetchEventOpProxyParent(Parent process background thread) => FetchEventOpProxyChild(content process worker launch thread) => ServiceWorker(content process worker thread) However, since preload response fetching is an asynchronous behavior, the preload response can be ready at any time point during the fetch event dispatching. This patch also handles different situations. Basically, it can be separated into the following stages. 1. Preload response is ready when the fetch event dispatching is in the parent process main thread, which means the fetch event is pending as a ServiceWorkerPrivateImpl::PendingFetchEvent. Details in PendingFetchEvent and FetchEventOpChild 2. Preload response is ready when the fetch event dispatching is in the parent process background thread, which means fetch event is still waiting for worker launching. Details in FetchEventOpParent and FetchEventOpProxyParent. 3. Preload response is ready when constructing the fetch event. Details in FetchEventOpProxyChild and ServiceWorkerOp::FetchEventOp. Depends on D126244 Differential Revision: https://phabricator.services.mozilla.com/D122821
2021-11-09 20:24:27 +00:00
} else {
// preload navigation is disabled, resolved preload response promise with
// undefined as default behavior.
mPreloadResponse->MaybeResolveWithUndefined();
}
Bug 1693074 - Adding telemetry for evaluating the duration of fetch event dispatching and response synthesizing. r=dom-worker-reviewers,asuth This patch tries to record the fetch event dispatching time, the response's synthesizing time, and interception resetting time. Fetch event dispatching time is the time duration between interception starts, which is the time point of InterceptedHttpChannel::AsyncOpenInternal(), and the fetch handler starts. It includes the InterceptedHttpChannel setup time, ServiceWorker launch time, FetchEventOp propagation through IPC, a FetchEvent creation, initialization, and dispatching/scheduling on worker scope. Response synthesizing time is the time duration between the fetch handler finishes, which is the resolving/rejecting promise of respondWith(), to the finish of pumping the synthesized response to InterceptedHttpChannel, which is the time point of calling InterceptedHttpChannel::OnStopRequest(). It includes the response propagation through IPC, response header and body synthesis, and pumping synthesized response to the channel. Interception resetting time is the time duration between the fetch handler finishes and redirecting InterceptedHttpChannel to a normal HTTP channel. Since the fetch handler is executed on the process where the service worker spawned, the timestamps related to the fetch handler need to be get on that process. So this patch adds the FetchHandlerStart and FetchHandlerFinish on IPCFetchEventRespondWithResult related types to propagate the timestamps to the parent process. Depends on D118398 Differential Revision: https://phabricator.services.mozilla.com/D118399
2021-08-22 11:02:18 +00:00
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;
Bug 1577346 - P4 IPC implementation for FetchEvent.preloadResponse. r=dom-worker-reviewers,ytausky This patch implements the IPC for propagating the preload response from the parent process' main thread to the content process' worker thread. The following is the complicated propagation path. FetchEventOpChild(Parent process main thread) => FetchEventOpParent(Parent process background thread) => FetchEventOpProxyParent(Parent process background thread) => FetchEventOpProxyChild(content process worker launch thread) => ServiceWorker(content process worker thread) However, since preload response fetching is an asynchronous behavior, the preload response can be ready at any time point during the fetch event dispatching. This patch also handles different situations. Basically, it can be separated into the following stages. 1. Preload response is ready when the fetch event dispatching is in the parent process main thread, which means the fetch event is pending as a ServiceWorkerPrivateImpl::PendingFetchEvent. Details in PendingFetchEvent and FetchEventOpChild 2. Preload response is ready when the fetch event dispatching is in the parent process background thread, which means fetch event is still waiting for worker launching. Details in FetchEventOpParent and FetchEventOpProxyParent. 3. Preload response is ready when constructing the fetch event. Details in FetchEventOpProxyChild and ServiceWorkerOp::FetchEventOp. Depends on D126244 Differential Revision: https://phabricator.services.mozilla.com/D122821
2021-11-09 20:24:27 +00:00
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");
Bug 1693074 - Adding telemetry for evaluating the duration of fetch event dispatching and response synthesizing. r=dom-worker-reviewers,asuth This patch tries to record the fetch event dispatching time, the response's synthesizing time, and interception resetting time. Fetch event dispatching time is the time duration between interception starts, which is the time point of InterceptedHttpChannel::AsyncOpenInternal(), and the fetch handler starts. It includes the InterceptedHttpChannel setup time, ServiceWorker launch time, FetchEventOp propagation through IPC, a FetchEvent creation, initialization, and dispatching/scheduling on worker scope. Response synthesizing time is the time duration between the fetch handler finishes, which is the resolving/rejecting promise of respondWith(), to the finish of pumping the synthesized response to InterceptedHttpChannel, which is the time point of calling InterceptedHttpChannel::OnStopRequest(). It includes the response propagation through IPC, response header and body synthesis, and pumping synthesized response to the channel. Interception resetting time is the time duration between the fetch handler finishes and redirecting InterceptedHttpChannel to a normal HTTP channel. Since the fetch handler is executed on the process where the service worker spawned, the timestamps related to the fetch handler need to be get on that process. So this patch adds the FetchHandlerStart and FetchHandlerFinish on IPCFetchEventRespondWithResult related types to propagate the timestamps to the parent process. Depends on D118398 Differential Revision: https://phabricator.services.mozilla.com/D118399
2021-08-22 11:02:18 +00:00
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(
Bug 1693074 - Adding telemetry for evaluating the duration of fetch event dispatching and response synthesizing. r=dom-worker-reviewers,asuth This patch tries to record the fetch event dispatching time, the response's synthesizing time, and interception resetting time. Fetch event dispatching time is the time duration between interception starts, which is the time point of InterceptedHttpChannel::AsyncOpenInternal(), and the fetch handler starts. It includes the InterceptedHttpChannel setup time, ServiceWorker launch time, FetchEventOp propagation through IPC, a FetchEvent creation, initialization, and dispatching/scheduling on worker scope. Response synthesizing time is the time duration between the fetch handler finishes, which is the resolving/rejecting promise of respondWith(), to the finish of pumping the synthesized response to InterceptedHttpChannel, which is the time point of calling InterceptedHttpChannel::OnStopRequest(). It includes the response propagation through IPC, response header and body synthesis, and pumping synthesized response to the channel. Interception resetting time is the time duration between the fetch handler finishes and redirecting InterceptedHttpChannel to a normal HTTP channel. Since the fetch handler is executed on the process where the service worker spawned, the timestamps related to the fetch handler need to be get on that process. So this patch adds the FetchHandlerStart and FetchHandlerFinish on IPCFetchEventRespondWithResult related types to propagate the timestamps to the parent process. Depends on D118398 Differential Revision: https://phabricator.services.mozilla.com/D118399
2021-08-22 11:02:18 +00:00
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(
Bug 1693074 - Adding telemetry for evaluating the duration of fetch event dispatching and response synthesizing. r=dom-worker-reviewers,asuth This patch tries to record the fetch event dispatching time, the response's synthesizing time, and interception resetting time. Fetch event dispatching time is the time duration between interception starts, which is the time point of InterceptedHttpChannel::AsyncOpenInternal(), and the fetch handler starts. It includes the InterceptedHttpChannel setup time, ServiceWorker launch time, FetchEventOp propagation through IPC, a FetchEvent creation, initialization, and dispatching/scheduling on worker scope. Response synthesizing time is the time duration between the fetch handler finishes, which is the resolving/rejecting promise of respondWith(), to the finish of pumping the synthesized response to InterceptedHttpChannel, which is the time point of calling InterceptedHttpChannel::OnStopRequest(). It includes the response propagation through IPC, response header and body synthesis, and pumping synthesized response to the channel. Interception resetting time is the time duration between the fetch handler finishes and redirecting InterceptedHttpChannel to a normal HTTP channel. Since the fetch handler is executed on the process where the service worker spawned, the timestamps related to the fetch handler need to be get on that process. So this patch adds the FetchHandlerStart and FetchHandlerFinish on IPCFetchEventRespondWithResult related types to propagate the timestamps to the parent process. Depends on D118398 Differential Revision: https://phabricator.services.mozilla.com/D118399
2021-08-22 11:02:18 +00:00
FetchEventRespondWithResult(ResetInterceptionArgs(
FetchEventTimeStamps(mFetchHandlerStart, mFetchHandlerFinish))),
__func__);
}
} else {
MOZ_ASSERT(mRespondWithClosure);
}
mPostDispatchChecksDone = true;
MaybeFinished();
return NS_OK;
}
Bug 1728327 - Introduce a new nsIServiceWorkerManager.wakeForExtensionAPIEvent method. r=asuth This patch includes a set of changes to the ServiceWorker internals to introduce a new nsIServiceWorkerManager.wakeForExtensionAPIEvent method, to be used by the WebExtensions internals to request an active background service worker to be spawned (if it is not yet) in response to a WebExtension API event. The new method gets as parameters: - the scope URL for the extension background service worker to spawn - WebExtensions API namespace and API event name which we are going to spawn an active worker for and return a promise which would be: - rejected if the worker could not be spawned - resolved to a boolean if the worker was spawned successfully (or already running) The value of the boolean value resolved is meant to represent if the worker did actually have any listener subscribed for the given WebExtensions API event listener (which the WebExtensions internals may then use to decide if it is worth to send that event to be handled by the worker script or not). In this patch the ExtensionBrowser::HasWakeupEventListener used to determine if an WebExtensions API event was subscribed syncronously when the worker was being loaded is not implemented yet and it is always returning false (a proposed implementation for that method is going to be added in a separate patch part of this same bugzilla issue). A unit test for the new proposed nsIServiceWorkerManager method is also part of a separate patch (attached to this bugzilla issue as the child revision for this one). Differential Revision: https://phabricator.services.mozilla.com/D130756
2021-12-15 18:29:36 +00:00
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;
Bug 1728327 - Introduce a new nsIServiceWorkerManager.wakeForExtensionAPIEvent method. r=asuth This patch includes a set of changes to the ServiceWorker internals to introduce a new nsIServiceWorkerManager.wakeForExtensionAPIEvent method, to be used by the WebExtensions internals to request an active background service worker to be spawned (if it is not yet) in response to a WebExtension API event. The new method gets as parameters: - the scope URL for the extension background service worker to spawn - WebExtensions API namespace and API event name which we are going to spawn an active worker for and return a promise which would be: - rejected if the worker could not be spawned - resolved to a boolean if the worker was spawned successfully (or already running) The value of the boolean value resolved is meant to represent if the worker did actually have any listener subscribed for the given WebExtensions API event listener (which the WebExtensions internals may then use to decide if it is worth to send that event to be handled by the worker script or not). In this patch the ExtensionBrowser::HasWakeupEventListener used to determine if an WebExtensions API event was subscribed syncronously when the worker was being loaded is not implemented yet and it is always returning false (a proposed implementation for that method is going to be added in a separate patch part of this same bugzilla issue). A unit test for the new proposed nsIServiceWorkerManager method is also part of a separate patch (attached to this bugzilla issue as the child revision for this one). Differential Revision: https://phabricator.services.mozilla.com/D130756
2021-12-15 18:29:36 +00:00
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