/* -*- 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 #include "ServiceWorkerOpPromise.h" #include "js/Exception.h" // JS::ExceptionStack, JS::StealPendingExceptionStack #include "jsapi.h" #include "nsCOMPtr.h" #include "nsContentUtils.h" #include "nsDebug.h" #include "nsError.h" #include "nsINamed.h" #include "nsIPushErrorReporter.h" #include "nsISupportsImpl.h" #include "nsITimer.h" #include "nsProxyRelease.h" #include "nsServiceManagerUtils.h" #include "nsTArray.h" #include "nsThreadUtils.h" #include "ServiceWorkerCloneData.h" #include "ServiceWorkerShutdownState.h" #include "mozilla/Assertions.h" #include "mozilla/CycleCollectedJSContext.h" #include "mozilla/DebugOnly.h" #include "mozilla/ErrorResult.h" #include "mozilla/OwningNonNull.h" #include "mozilla/SchedulerGroup.h" #include "mozilla/ScopeExit.h" #include "mozilla/Telemetry.h" #include "mozilla/Unused.h" #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/Client.h" #include "mozilla/dom/ExtendableMessageEventBinding.h" #include "mozilla/dom/FetchEventBinding.h" #include "mozilla/dom/FetchEventOpProxyChild.h" #include "mozilla/dom/InternalHeaders.h" #include "mozilla/dom/InternalRequest.h" #include "mozilla/dom/InternalResponse.h" #include "mozilla/dom/Notification.h" #include "mozilla/dom/NotificationEvent.h" #include "mozilla/dom/NotificationEventBinding.h" #include "mozilla/dom/PerformanceTiming.h" #include "mozilla/dom/PerformanceStorage.h" #include "mozilla/dom/PushEventBinding.h" #include "mozilla/dom/RemoteWorkerChild.h" #include "mozilla/dom/RemoteWorkerService.h" #include "mozilla/dom/Request.h" #include "mozilla/dom/Response.h" #include "mozilla/dom/RootedDictionary.h" #include "mozilla/dom/SafeRefPtr.h" #include "mozilla/dom/ServiceWorkerBinding.h" #include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h" #include "mozilla/dom/WorkerCommon.h" #include "mozilla/dom/WorkerRef.h" #include "mozilla/dom/WorkerScope.h" #include "mozilla/extensions/ExtensionBrowser.h" #include "mozilla/ipc/IPCStreamUtils.h" #include "mozilla/net/MozURL.h" namespace mozilla::dom { namespace { class ExtendableEventKeepAliveHandler final : public ExtendableEvent::ExtensionsHandler, public PromiseNativeHandler { public: NS_DECL_ISUPPORTS static RefPtr Create( RefPtr aCallback) { MOZ_ASSERT(IsCurrentThreadRunningWorker()); RefPtr 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 aValue, ErrorResult& aRv) override { RemovePromise(Resolved); } void RejectedCallback(JSContext* aCx, JS::Handle 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 aHandler) : mHandler(std::move(aHandler)) {} void Run(AutoSlowOperation& /* unused */) override { mHandler->MaybeDone(); } private: RefPtr mHandler; }; explicit ExtendableEventKeepAliveHandler( RefPtr 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 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 mSelfRef; RefPtr mWorkerRef; RefPtr mCallback; uint32_t mPendingPromisesCount = 0; bool mRejected = false; bool mAcceptingPromises = true; }; NS_IMPL_ISUPPORTS0(ExtendableEventKeepAliveHandler) nsresult DispatchExtendableEventOnWorkerScope( JSContext* aCx, WorkerGlobalScope* aWorkerScope, ExtendableEvent* aEvent, RefPtr aCallback) { MOZ_ASSERT(aCx); MOZ_ASSERT(aWorkerScope); MOZ_ASSERT(aEvent); nsCOMPtr globalObject = aWorkerScope; WidgetEvent* internalEvent = aEvent->WidgetEventPtr(); RefPtr 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 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 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() && !IsTerminationOp()) { return false; } if (NS_WARN_IF(aState.is()) || NS_WARN_IF(aState.is())) { RejectAll(NS_ERROR_DOM_INVALID_STATE_ERR); mStarted = true; return true; } MOZ_ASSERT(aState.is() || IsTerminationOp()); RefPtr 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 owner = aOwner; nsCOMPtr 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() && !self->IsTerminationOp())) { self->RejectAll(NS_ERROR_DOM_INVALID_STATE_ERR); return; } if (self->IsTerminationOp()) { owner->CloseWorkerOnMainThread(state); } else { MOZ_ASSERT(state.is()); RefPtr workerRunnable = self->GetRunnable(state.as().mWorkerPrivate); if (NS_WARN_IF(!workerRunnable->Dispatch())) { self->RejectAll(NS_ERROR_FAILURE); } } nsCOMPtr 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&& aCallback) : mArgs(std::move(aArgs)) { MOZ_ASSERT(RemoteWorkerService::Thread()->IsOnCurrentThread()); RefPtr 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 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 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 mOwner; }; ~UpdateServiceWorkerStateOp() = default; RefPtr 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 event; RefPtr 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(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 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::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 r = NS_NewRunnableFunction( __func__, [messageId = std::move(messageId), error = aError] { nsCOMPtr 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 target = aWorkerPrivate->GlobalScope(); ExtendableEventInit init; init.mBubbles = false; init.mCancelable = false; RefPtr 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 timer = NS_NewTimer(aWorkerPrivate->ControlEventTarget()); if (NS_WARN_IF(!timer)) { return; } MOZ_ASSERT(!mWorkerRef); RefPtr 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 target = aWorkerPrivate->GlobalScope(); ServiceWorkerNotificationEventOpArgs& args = mArgs.get_ServiceWorkerNotificationEventOpArgs(); ErrorResult result; RefPtr 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::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 mTimer; RefPtr 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&& 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 messageData(aCx); nsCOMPtr 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> ports; if (!mData->TakeTransferredPortsAsSequence(ports)) { RejectAll(NS_ERROR_FAILURE); rv.SuppressException(); return false; } RootedDictionary 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 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 target = aWorkerPrivate->GlobalScope(); RefPtr 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 mData; }; /** * Used for ScopeExit-style network request cancelation in * `ResolvedCallback()` (e.g. if `FetchEvent::RespondWith()` is resolved with * a non-JS object). */ class MOZ_STACK_CLASS FetchEventOp::AutoCancel { public: explicit AutoCancel(FetchEventOp* aOwner) : mOwner(aOwner), mLine(0), mColumn(0), mMessageName("InterceptionFailedWithURL"_ns) { MOZ_ASSERT(IsCurrentThreadRunningWorker()); MOZ_ASSERT(mOwner); nsAutoString requestURL; mOwner->GetRequestURL(requestURL); mParams.AppendElement(requestURL); } ~AutoCancel() { if (mOwner) { if (mSourceSpec.IsEmpty()) { mOwner->AsyncLog(mMessageName, std::move(mParams)); } else { mOwner->AsyncLog(mSourceSpec, mLine, mColumn, mMessageName, std::move(mParams)); } MOZ_ASSERT(!mOwner->mRespondWithPromiseHolder.IsEmpty()); mOwner->mHandled->MaybeRejectWithNetworkError("AutoCancel"_ns); mOwner->mRespondWithPromiseHolder.Reject( CancelInterceptionArgs( NS_ERROR_INTERCEPTION_FAILED, FetchEventTimeStamps(mOwner->mFetchHandlerStart, mOwner->mFetchHandlerFinish)), __func__); } } // This function steals the error message from a ErrorResult. void SetCancelErrorResult(JSContext* aCx, ErrorResult& aRv) { MOZ_DIAGNOSTIC_ASSERT(aRv.Failed()); MOZ_DIAGNOSTIC_ASSERT(!JS_IsExceptionPending(aCx)); // Storing the error as exception in the JSContext. if (!aRv.MaybeSetPendingException(aCx)) { return; } MOZ_ASSERT(!aRv.Failed()); // Let's take the pending exception. JS::ExceptionStack exnStack(aCx); if (!JS::StealPendingExceptionStack(aCx, &exnStack)) { return; } // Converting the exception in a JS::ErrorReportBuilder. JS::ErrorReportBuilder report(aCx); if (!report.init(aCx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) { JS_ClearPendingException(aCx); return; } MOZ_ASSERT(mOwner); MOZ_ASSERT(mMessageName.EqualsLiteral("InterceptionFailedWithURL")); MOZ_ASSERT(mParams.Length() == 1); // Let's store the error message here. mMessageName.Assign(report.toStringResult().c_str()); mParams.Clear(); } template 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(aParams)...); } template 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(aParams)...); } void Reset() { mOwner = nullptr; } private: FetchEventOp* MOZ_NON_OWNING_REF mOwner; nsCString mSourceSpec; uint32_t mLine; uint32_t mColumn; nsCString mMessageName; nsTArray mParams; }; NS_IMPL_ISUPPORTS0(FetchEventOp) void FetchEventOp::SetActor(RefPtr 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 FetchEventOp::GetRespondWithPromise() { MOZ_ASSERT(RemoteWorkerService::Thread()->IsOnCurrentThread()); MOZ_ASSERT(!Started()); MOZ_ASSERT(mRespondWithPromiseHolder.IsEmpty()); return mRespondWithPromiseHolder.Ensure(__func__); } void FetchEventOp::RespondWithCalledAt(const nsCString& aRespondWithScriptSpec, uint32_t aRespondWithLineNumber, uint32_t aRespondWithColumnNumber) { MOZ_ASSERT(IsCurrentThreadRunningWorker()); MOZ_ASSERT(!mRespondWithClosure); mRespondWithClosure.emplace(aRespondWithScriptSpec, aRespondWithLineNumber, aRespondWithColumnNumber); } void FetchEventOp::ReportCanceled(const nsCString& aPreventDefaultScriptSpec, uint32_t aPreventDefaultLineNumber, uint32_t aPreventDefaultColumnNumber) { MOZ_ASSERT(IsCurrentThreadRunningWorker()); MOZ_ASSERT(mActor); MOZ_ASSERT(!mPromiseHolder.IsEmpty()); nsString requestURL; GetRequestURL(requestURL); AsyncLog(aPreventDefaultScriptSpec, aPreventDefaultLineNumber, aPreventDefaultColumnNumber, "InterceptionCanceledWithURL"_ns, {std::move(requestURL)}); } FetchEventOp::~FetchEventOp() { mRespondWithPromiseHolder.RejectIfExists( CancelInterceptionArgs( NS_ERROR_DOM_ABORT_ERR, FetchEventTimeStamps(mFetchHandlerStart, mFetchHandlerFinish)), __func__); if (mActor) { NS_ProxyRelease("FetchEventOp::mActor", RemoteWorkerService::Thread(), mActor.forget()); } } void FetchEventOp::RejectAll(nsresult aStatus) { MOZ_ASSERT(!mRespondWithPromiseHolder.IsEmpty()); MOZ_ASSERT(!mPromiseHolder.IsEmpty()); if (mFetchHandlerStart.IsNull()) { mFetchHandlerStart = TimeStamp::Now(); } if (mFetchHandlerFinish.IsNull()) { mFetchHandlerFinish = TimeStamp::Now(); } mRespondWithPromiseHolder.Reject( CancelInterceptionArgs( aStatus, FetchEventTimeStamps(mFetchHandlerStart, mFetchHandlerFinish)), __func__); mPromiseHolder.Reject(aStatus, __func__); } void FetchEventOp::FinishedWithResult(ExtendableEventResult aResult) { MOZ_ASSERT(IsCurrentThreadRunningWorker()); MOZ_ASSERT(!mPromiseHolder.IsEmpty()); MOZ_ASSERT(!mResult); mResult.emplace(aResult); /** * This should only return early if neither waitUntil() nor respondWith() * are called. The early return is so that mRespondWithPromiseHolder has a * chance to settle before mPromiseHolder does. */ if (!mPostDispatchChecksDone) { return; } MaybeFinished(); } void FetchEventOp::MaybeFinished() { MOZ_ASSERT(IsCurrentThreadRunningWorker()); MOZ_ASSERT(!mPromiseHolder.IsEmpty()); if (mResult) { // It's possible that mRespondWithPromiseHolder wasn't settled. That happens // if the worker was terminated before the respondWith promise settled. mHandled = nullptr; mPreloadResponse = nullptr; mPreloadResponseAvailablePromiseRequestHolder.DisconnectIfExists(); mPreloadResponseEndPromiseRequestHolder.DisconnectIfExists(); ServiceWorkerFetchEventOpResult result( mResult.value() == Resolved ? NS_OK : NS_ERROR_FAILURE); mPromiseHolder.Resolve(result, __func__); } } bool FetchEventOp::Exec(JSContext* aCx, WorkerPrivate* aWorkerPrivate) { aWorkerPrivate->AssertIsOnWorkerThread(); MOZ_ASSERT(aWorkerPrivate->IsServiceWorker()); MOZ_ASSERT(!mRespondWithPromiseHolder.IsEmpty()); MOZ_ASSERT(!mPromiseHolder.IsEmpty()); nsresult rv = DispatchFetchEvent(aCx, aWorkerPrivate); if (NS_WARN_IF(NS_FAILED(rv))) { RejectAll(rv); } return NS_SUCCEEDED(rv); } void FetchEventOp::AsyncLog(const nsCString& aMessageName, nsTArray 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 aParams) { MOZ_ASSERT(mActor); MOZ_ASSERT(!mPromiseHolder.IsEmpty()); // Capture `this` because FetchEventOpProxyChild (mActor) is not thread // safe, so an AddRef from RefPtr's constructor will // assert. RefPtr self = this; nsCOMPtr 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& urls = mArgs.get_ParentToChildServiceWorkerFetchEventOpArgs() .common() .internalRequest() .urlList(); MOZ_ASSERT(!urls.IsEmpty()); CopyUTF8toUTF16(urls.LastElement(), aOutRequestURL); } void FetchEventOp::ResolvedCallback(JSContext* aCx, JS::Handle aValue, ErrorResult& aRv) { MOZ_ASSERT(IsCurrentThreadRunningWorker()); MOZ_ASSERT(mRespondWithClosure); MOZ_ASSERT(!mRespondWithPromiseHolder.IsEmpty()); MOZ_ASSERT(!mPromiseHolder.IsEmpty()); mFetchHandlerFinish = TimeStamp::Now(); nsAutoString requestURL; GetRequestURL(requestURL); AutoCancel autoCancel(this); if (!aValue.isObject()) { NS_WARNING( "FetchEvent::RespondWith was passed a promise resolved to a " "non-Object " "value"); nsCString sourceSpec; uint32_t line = 0; uint32_t column = 0; nsString valueString; nsContentUtils::ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column, valueString); autoCancel.SetCancelMessageAndLocation(sourceSpec, line, column, "InterceptedNonResponseWithURL"_ns, requestURL, valueString); return; } RefPtr response; 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 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 body; ir->GetUnfilteredBody(getter_AddRefs(body)); // Errors and redirects may not have a body. if (body) { ErrorResult error; response->SetBodyUsed(aCx, error); error.WouldReportJSException(); if (NS_WARN_IF(error.Failed())) { autoCancel.SetCancelErrorResult(aCx, error); return; } } if (!ir->GetChannelInfo().IsInitialized()) { // This is a synthetic response (I think and hope so). ir->InitChannelInfo(worker->GetChannelInfo()); } autoCancel.Reset(); // https://w3c.github.io/ServiceWorker/#on-fetch-request-algorithm Step 26: If // eventHandled is not null, then resolve eventHandled. // // mRespondWithPromiseHolder will resolve a MozPromise that will resolve on // the worker owner's thread, so it's fine to resolve the mHandled promise now // because content will not interfere with respondWith getting the Response to // where it's going. mHandled->MaybeResolveWithUndefined(); mRespondWithPromiseHolder.Resolve( FetchEventRespondWithResult(MakeTuple( std::move(ir), mRespondWithClosure.ref(), FetchEventTimeStamps(mFetchHandlerStart, mFetchHandlerFinish))), __func__); } void FetchEventOp::RejectedCallback(JSContext* aCx, JS::Handle aValue, ErrorResult& aRv) { MOZ_ASSERT(IsCurrentThreadRunningWorker()); MOZ_ASSERT(mRespondWithClosure); MOZ_ASSERT(!mRespondWithPromiseHolder.IsEmpty()); MOZ_ASSERT(!mPromiseHolder.IsEmpty()); mFetchHandlerFinish = TimeStamp::Now(); FetchEventRespondWithClosure& closure = mRespondWithClosure.ref(); nsCString sourceSpec = closure.respondWithScriptSpec(); uint32_t line = closure.respondWithLineNumber(); uint32_t column = closure.respondWithColumnNumber(); nsString valueString; nsContentUtils::ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column, valueString); nsString requestURL; GetRequestURL(requestURL); AsyncLog(sourceSpec, line, column, "InterceptionRejectedResponseWithURL"_ns, {std::move(requestURL), valueString}); // https://w3c.github.io/ServiceWorker/#on-fetch-request-algorithm Step 25.1: // If eventHandled is not null, then reject eventHandled with a "NetworkError" // DOMException in workerRealm. mHandled->MaybeRejectWithNetworkError( "FetchEvent.respondWith() Promise rejected"_ns); mRespondWithPromiseHolder.Resolve( FetchEventRespondWithResult(CancelInterceptionArgs( NS_ERROR_INTERCEPTION_FAILED, FetchEventTimeStamps(mFetchHandlerStart, mFetchHandlerFinish))), __func__); } nsresult FetchEventOp::DispatchFetchEvent(JSContext* aCx, WorkerPrivate* aWorkerPrivate) { MOZ_ASSERT(aCx); MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); MOZ_ASSERT(aWorkerPrivate->IsServiceWorker()); ParentToChildServiceWorkerFetchEventOpArgs& args = mArgs.get_ParentToChildServiceWorkerFetchEventOpArgs(); /** * Testing: Failure injection. * * There are a number of different ways that this fetch event could have * failed that would result in cancellation. This injection point helps * simulate them without worrying about shifting implementation details with * full fidelity reproductions of current scenarios. * * Broadly speaking, we expect fetch event scenarios to fail because of: * - Script load failure, which results in the CompileScriptRunnable closing * the worker and thereby cancelling all pending operations, including this * fetch. The `ServiceWorkerOp::Cancel` impl just calls * RejectAll(NS_ERROR_DOM_ABORT_ERR) which we are able to approximate by * returning the same nsresult here, as our caller also calls RejectAll. * (And timing-wise, this rejection will happen in the correct sequence.) * - An exception gets thrown in the processing of the promise that was passed * to respondWith and it ends up rejecting. The rejection will be converted * by `FetchEventOp::RejectedCallback` into a cancellation with * NS_ERROR_INTERCEPTION_FAILED, and by returning that here we approximate * that failure mode. */ if (NS_FAILED(args.common().testingInjectCancellation())) { return args.common().testingInjectCancellation(); } /** * Step 1: get the InternalRequest. The InternalRequest can't be constructed * here from mArgs because the IPCStream has to be deserialized on the * thread receiving the ServiceWorkerFetchEventOpArgs. * FetchEventOpProxyChild will have already deserialized the stream on the * correct thread before creating this op, so we can take its saved * InternalRequest. */ SafeRefPtr internalRequest = mActor->ExtractInternalRequest(); /** * Step 2: get the worker's global object */ GlobalObject globalObject(aCx, aWorkerPrivate->GlobalScope()->GetWrapper()); nsCOMPtr 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 = new Request(globalObjectAsSupports, internalRequest.clonePtr(), nullptr); MOZ_ASSERT_IF(internalRequest->IsNavigationRequest(), request->Redirect() == RequestRedirect::Manual); /** * Step 4a: create the FetchEventInit */ RootedDictionary 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 request’s * destination is not "report", initialize e’s resultingClientId attribute * to reservedClient’s [resultingClient's] id, and to the empty string * otherwise." (Step 18.8) */ if (!args.common().resultingClientId().IsEmpty() && args.common().isNonSubresourceRequest() && internalRequest->Destination() != RequestDestination::Report) { fetchEventInit.mResultingClientId = args.common().resultingClientId(); } /** * Step 4b: create the FetchEvent */ RefPtr fetchEvent = FetchEvent::Constructor(globalObject, u"fetch"_ns, fetchEventInit); fetchEvent->SetTrusted(true); fetchEvent->PostInit(args.common().workerScriptSpec(), this); mHandled = fetchEvent->Handled(); mPreloadResponse = fetchEvent->PreloadResponse(); if (args.common().preloadNavigation()) { RefPtr preloadResponsePromise = mActor->GetPreloadResponseAvailablePromise(); MOZ_ASSERT(preloadResponsePromise); // If preloadResponsePromise has already settled then this callback will get // run synchronously here. RefPtr self = this; preloadResponsePromise ->Then( GetCurrentSerialEventTarget(), __func__, [self, globalObjectAsSupports]( SafeRefPtr&& aPreloadResponse) { self->mPreloadResponse->MaybeResolve( MakeRefPtr(globalObjectAsSupports, std::move(aPreloadResponse), nullptr)); self->mPreloadResponseAvailablePromiseRequestHolder.Complete(); }, [self](int) { self->mPreloadResponseAvailablePromiseRequestHolder.Complete(); }) ->Track(mPreloadResponseAvailablePromiseRequestHolder); RefPtr performanceStorage = aWorkerPrivate->GetPerformanceStorage(); RefPtr preloadResponseEndPromise = mActor->GetPreloadResponseEndPromise(); MOZ_ASSERT(preloadResponseEndPromise); preloadResponseEndPromise ->Then( GetCurrentSerialEventTarget(), __func__, [self, performanceStorage, globalObjectAsSupports](ResponseEndArgs&& aArgs) { if (aArgs.timing().isSome() && performanceStorage) { performanceStorage->AddEntry( aArgs.timing().ref().entryName(), aArgs.timing().ref().initiatorType(), MakeUnique( aArgs.timing().ref().timingData())); } if (aArgs.endReason() == FetchDriverObserver::eAborted) { self->mPreloadResponse->MaybeReject(NS_ERROR_DOM_ABORT_ERR); } self->mPreloadResponseEndPromiseRequestHolder.Complete(); }, [self](int) { self->mPreloadResponseEndPromiseRequestHolder.Complete(); }) ->Track(mPreloadResponseEndPromiseRequestHolder); } else { // preload navigation is disabled, resolved preload response promise with // undefined as default behavior. mPreloadResponse->MaybeResolveWithUndefined(); } mFetchHandlerStart = TimeStamp::Now(); /** * Step 5: Dispatch the FetchEvent to the worker's global object */ nsresult rv = DispatchExtendableEventOnWorkerScope( aCx, aWorkerPrivate->GlobalScope(), fetchEvent, this); bool dispatchFailed = NS_FAILED(rv) && rv != NS_ERROR_XPC_JS_THREW_EXCEPTION; if (NS_WARN_IF(dispatchFailed)) { mHandled = nullptr; mPreloadResponse = nullptr; return rv; } /** * At this point, there are 4 (legal) scenarios: * * 1) If neither waitUntil() nor respondWith() are called, * DispatchExtendableEventOnWorkerScope() will have already called * FinishedWithResult(), but this call will have recorded the result * (mResult) and returned early so that mRespondWithPromiseHolder can be * settled first. mRespondWithPromiseHolder will be settled below, followed * by a call to MaybeFinished() which settles mPromiseHolder. * * 2) If waitUntil() is called at least once, and respondWith() is not * called, DispatchExtendableEventOnWorkerScope() will NOT have called * FinishedWithResult(). We'll settle mRespondWithPromiseHolder first, and * at some point in the future when the last waitUntil() promise settles, * FinishedWithResult() will be called, settling mPromiseHolder. * * 3) If waitUntil() is not called, and respondWith() is called, * DispatchExtendableEventOnWorkerScope() will NOT have called * FinishedWithResult(). We can also guarantee that * mRespondWithPromiseHolder will be settled before mPromiseHolder, due to * the Promise::AppendNativeHandler() call ordering in * FetchEvent::RespondWith(). * * 4) If waitUntil() is called at least once, and respondWith() is also * called, the effect is similar to scenario 3), with the most imporant * property being mRespondWithPromiseHolder settling before mPromiseHolder. * * Note that if mPromiseHolder is settled before mRespondWithPromiseHolder, * FetchEventOpChild will cancel the interception. */ if (!fetchEvent->WaitToRespond()) { MOZ_ASSERT(!mRespondWithPromiseHolder.IsEmpty()); MOZ_ASSERT(!aWorkerPrivate->UsesSystemPrincipal(), "We don't support system-principal serviceworkers"); mFetchHandlerFinish = TimeStamp::Now(); if (fetchEvent->DefaultPrevented(CallerType::NonSystem)) { // https://w3c.github.io/ServiceWorker/#on-fetch-request-algorithm // Step 24.1.1: If eventHandled is not null, then reject eventHandled with // a "NetworkError" DOMException in workerRealm. mHandled->MaybeRejectWithNetworkError( "FetchEvent.preventDefault() called"_ns); mRespondWithPromiseHolder.Resolve( FetchEventRespondWithResult(CancelInterceptionArgs( NS_ERROR_INTERCEPTION_FAILED, FetchEventTimeStamps(mFetchHandlerStart, mFetchHandlerFinish))), __func__); } else { // https://w3c.github.io/ServiceWorker/#on-fetch-request-algorithm // Step 24.2: If eventHandled is not null, then resolve eventHandled. mHandled->MaybeResolveWithUndefined(); mRespondWithPromiseHolder.Resolve( FetchEventRespondWithResult(ResetInterceptionArgs( FetchEventTimeStamps(mFetchHandlerStart, mFetchHandlerFinish))), __func__); } } else { MOZ_ASSERT(mRespondWithClosure); } mPostDispatchChecksDone = true; MaybeFinished(); return NS_OK; } class ExtensionAPIEventOp final : public ServiceWorkerOp { using ServiceWorkerOp::ServiceWorkerOp; public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ExtensionAPIEventOp, override) private: ~ExtensionAPIEventOp() = default; bool Exec(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); MOZ_ASSERT(aWorkerPrivate->IsServiceWorker()); MOZ_ASSERT(aWorkerPrivate->ExtensionAPIAllowed()); MOZ_ASSERT(!mPromiseHolder.IsEmpty()); ServiceWorkerExtensionAPIEventOpArgs& args = mArgs.get_ServiceWorkerExtensionAPIEventOpArgs(); ServiceWorkerExtensionAPIEventOpResult result; result.extensionAPIEventListenerWasAdded() = false; if (aWorkerPrivate->WorkerScriptExecutedSuccessfully()) { GlobalObject globalObj(aCx, aWorkerPrivate->GlobalScope()->GetWrapper()); RefPtr scope; UNWRAP_OBJECT(ServiceWorkerGlobalScope, globalObj.Get(), scope); SafeRefPtr 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::Create( ServiceWorkerOpArgs&& aArgs, std::function&& aCallback) { MOZ_ASSERT(RemoteWorkerService::Thread()->IsOnCurrentThread()); RefPtr op; switch (aArgs.type()) { case ServiceWorkerOpArgs::TServiceWorkerCheckScriptEvaluationOpArgs: op = MakeRefPtr(std::move(aArgs), std::move(aCallback)); break; case ServiceWorkerOpArgs::TServiceWorkerUpdateStateOpArgs: op = MakeRefPtr(std::move(aArgs), std::move(aCallback)); break; case ServiceWorkerOpArgs::TServiceWorkerTerminateWorkerOpArgs: op = MakeRefPtr(std::move(aArgs), std::move(aCallback)); break; case ServiceWorkerOpArgs::TServiceWorkerLifeCycleEventOpArgs: op = MakeRefPtr(std::move(aArgs), std::move(aCallback)); break; case ServiceWorkerOpArgs::TServiceWorkerPushEventOpArgs: op = MakeRefPtr(std::move(aArgs), std::move(aCallback)); break; case ServiceWorkerOpArgs::TServiceWorkerPushSubscriptionChangeEventOpArgs: op = MakeRefPtr(std::move(aArgs), std::move(aCallback)); break; case ServiceWorkerOpArgs::TServiceWorkerNotificationEventOpArgs: op = MakeRefPtr(std::move(aArgs), std::move(aCallback)); break; case ServiceWorkerOpArgs::TServiceWorkerMessageEventOpArgs: op = MakeRefPtr(std::move(aArgs), std::move(aCallback)); break; case ServiceWorkerOpArgs::TParentToChildServiceWorkerFetchEventOpArgs: op = MakeRefPtr(std::move(aArgs), std::move(aCallback)); break; case ServiceWorkerOpArgs::TServiceWorkerExtensionAPIEventOpArgs: op = MakeRefPtr(std::move(aArgs), std::move(aCallback)); break; default: MOZ_CRASH("Unknown Service Worker operation!"); return nullptr; } return op.forget(); } } // namespace mozilla::dom