From fe9a6a9fa6e4f54539b91db8e37c5415730a0e52 Mon Sep 17 00:00:00 2001 From: Luca Greco Date: Wed, 15 Dec 2021 18:29:36 +0000 Subject: [PATCH] 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 --- .../base/nsIServiceWorkerManager.idl | 19 ++++- dom/serviceworkers/ServiceWorkerManager.cpp | 74 +++++++++++++++++++ dom/serviceworkers/ServiceWorkerOp.cpp | 54 ++++++++++++++ dom/serviceworkers/ServiceWorkerOpArgs.ipdlh | 15 ++++ dom/serviceworkers/ServiceWorkerPrivate.cpp | 18 +++++ dom/serviceworkers/ServiceWorkerPrivate.h | 9 +++ .../ServiceWorkerPrivateImpl.cpp | 33 +++++++++ dom/serviceworkers/ServiceWorkerPrivateImpl.h | 7 ++ .../webidl-api/ExtensionBrowser.cpp | 5 ++ .../extensions/webidl-api/ExtensionBrowser.h | 5 ++ 10 files changed, 237 insertions(+), 2 deletions(-) diff --git a/dom/interfaces/base/nsIServiceWorkerManager.idl b/dom/interfaces/base/nsIServiceWorkerManager.idl index a5fee9c4cd2a1..0b6b455386329 100644 --- a/dom/interfaces/base/nsIServiceWorkerManager.idl +++ b/dom/interfaces/base/nsIServiceWorkerManager.idl @@ -67,7 +67,6 @@ interface nsIServiceWorkerInfo : nsISupports readonly attribute PRTime activatedTime; readonly attribute PRTime redundantTime; - // Total number of navigation faults experienced by this ServiceWorker since // it was loaded from disk at startup or was installed. readonly attribute unsigned long navigationFaultCount; @@ -78,7 +77,6 @@ interface nsIServiceWorkerInfo : nsISupports // the cancellation control flow path will be triggered. attribute nsresult testingInjectCancellation; - void attachDebugger(); void detachDebugger(); @@ -224,6 +222,23 @@ interface nsIServiceWorkerManager : nsISupports void getRegistrationForAddonPrincipal(in nsIPrincipal aPrincipal, [optional, retval] out nsIServiceWorkerRegistrationInfo regInfo); + /** + * Wake up the extension background service worker given its extension base url, + * for an API event identified by the namespace and event name strings. + * + * Returns a Promise which is resolved to true if a listener has been subscribed + * during the synchronous worker script execution for the expected WebExtensions + * API event. + * + * NOTE: ExtensionBrowser and ExtensionEventManager interfaces are keeping track + * of these listeners. These are WebExtensions API event listeners and they do not + * involve any functional events at all. + */ + [implicit_jscontext] + Promise wakeForExtensionAPIEvent(in AString aExtensionBaseURL, + in AString aAPINamespace, + in AString aAPIEventName); + /** * Unregister an existing ServiceWorker registration for `aScope`. * It keeps aCallback alive until the operation is concluded. diff --git a/dom/serviceworkers/ServiceWorkerManager.cpp b/dom/serviceworkers/ServiceWorkerManager.cpp index 56981925b1c74..e7463f7ae0c05 100644 --- a/dom/serviceworkers/ServiceWorkerManager.cpp +++ b/dom/serviceworkers/ServiceWorkerManager.cpp @@ -2840,6 +2840,80 @@ ServiceWorkerManager::GetRegistrationForAddonPrincipal( return NS_OK; } +NS_IMETHODIMP +ServiceWorkerManager::WakeForExtensionAPIEvent( + const nsAString& aExtensionBaseURL, const nsAString& aAPINamespace, + const nsAString& aAPIEventName, JSContext* aCx, dom::Promise** aPromise) { + nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx); + if (NS_WARN_IF(!global)) { + return NS_ERROR_FAILURE; + } + + ErrorResult erv; + RefPtr outer = Promise::Create(global, erv); + if (NS_WARN_IF(erv.Failed())) { + return erv.StealNSResult(); + } + + auto enabled = + StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup(); + if (!enabled) { + outer->MaybeRejectWithNotAllowedError( + "Disabled. extensions.backgroundServiceWorker.enabled is false"); + outer.forget(aPromise); + return NS_OK; + } + + nsCOMPtr scopeURI; + nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aExtensionBaseURL); + if (NS_FAILED(rv)) { + outer->MaybeReject(rv); + outer.forget(aPromise); + return NS_OK; + } + + nsCOMPtr principal; + MOZ_TRY_VAR(principal, ScopeToPrincipal(scopeURI, {})); + + auto* addonPolicy = BasePrincipal::Cast(principal)->AddonPolicy(); + if (NS_WARN_IF(!addonPolicy)) { + outer->MaybeRejectWithNotAllowedError( + "Not an extension principal or extension disabled"); + outer.forget(aPromise); + return NS_OK; + } + + OriginAttributes attrs; + ServiceWorkerInfo* info = GetActiveWorkerInfoForScope( + attrs, NS_ConvertUTF16toUTF8(aExtensionBaseURL)); + if (NS_WARN_IF(!info)) { + outer->MaybeRejectWithInvalidStateError( + "No active worker for the extension background service worker"); + outer.forget(aPromise); + return NS_OK; + } + + ServiceWorkerPrivate* workerPrivate = info->WorkerPrivate(); + auto result = + workerPrivate->WakeForExtensionAPIEvent(aAPINamespace, aAPIEventName); + if (result.isErr()) { + outer->MaybeReject(result.propagateErr()); + outer.forget(aPromise); + return NS_OK; + } + + RefPtr innerPromise = + result.unwrap(); + + innerPromise->Then( + GetMainThreadSerialEventTarget(), __func__, + [outer](bool aSubscribedEvent) { outer->MaybeResolve(aSubscribedEvent); }, + [outer](nsresult aErrorResult) { outer->MaybeReject(aErrorResult); }); + + outer.forget(aPromise); + return NS_OK; +} + NS_IMETHODIMP ServiceWorkerManager::GetRegistrationByPrincipal( nsIPrincipal* aPrincipal, const nsAString& aScope, diff --git a/dom/serviceworkers/ServiceWorkerOp.cpp b/dom/serviceworkers/ServiceWorkerOp.cpp index c3e7cfc4b3fbe..84850cead4b81 100644 --- a/dom/serviceworkers/ServiceWorkerOp.cpp +++ b/dom/serviceworkers/ServiceWorkerOp.cpp @@ -55,10 +55,12 @@ #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/WorkerPrivate.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" @@ -1771,6 +1773,54 @@ nsresult FetchEventOp::DispatchFetchEvent(JSContext* aCx, 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) { @@ -1811,6 +1861,10 @@ nsresult FetchEventOp::DispatchFetchEvent(JSContext* aCx, 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; diff --git a/dom/serviceworkers/ServiceWorkerOpArgs.ipdlh b/dom/serviceworkers/ServiceWorkerOpArgs.ipdlh index 42cde86e21778..67f23101598b2 100644 --- a/dom/serviceworkers/ServiceWorkerOpArgs.ipdlh +++ b/dom/serviceworkers/ServiceWorkerOpArgs.ipdlh @@ -60,6 +60,15 @@ struct ServiceWorkerNotificationEventOpArgs { uint32_t disableOpenClickDelay; }; +struct ServiceWorkerExtensionAPIEventOpArgs { + // WebExtensions API namespace and event names, for a list of the API namespaces + // and related API event names refer to the API JSONSchema files in-tree: + // + // https://searchfox.org/mozilla-central/search?q=&path=extensions%2Fschemas%2F*.json + nsString apiNamespace; + nsString apiEventName; +}; + struct ServiceWorkerMessageEventOpArgs { ClientInfoAndState clientInfoAndState; ClonedOrErrorMessageData clonedData; @@ -101,6 +110,7 @@ union ServiceWorkerOpArgs { ServiceWorkerPushSubscriptionChangeEventOpArgs; ServiceWorkerNotificationEventOpArgs; ServiceWorkerMessageEventOpArgs; + ServiceWorkerExtensionAPIEventOpArgs; ParentToChildServiceWorkerFetchEventOpArgs; }; @@ -163,10 +173,15 @@ struct ServiceWorkerFetchEventOpResult { nsresult rv; }; +struct ServiceWorkerExtensionAPIEventOpResult { + bool extensionAPIEventListenerWasAdded; +}; + union ServiceWorkerOpResult { nsresult; ServiceWorkerCheckScriptEvaluationOpResult; ServiceWorkerFetchEventOpResult; + ServiceWorkerExtensionAPIEventOpResult; }; } // namespace dom diff --git a/dom/serviceworkers/ServiceWorkerPrivate.cpp b/dom/serviceworkers/ServiceWorkerPrivate.cpp index c42a6fe511afd..2ff185bea9828 100644 --- a/dom/serviceworkers/ServiceWorkerPrivate.cpp +++ b/dom/serviceworkers/ServiceWorkerPrivate.cpp @@ -1589,6 +1589,24 @@ nsresult ServiceWorkerPrivate::SendFetchEvent( return NS_OK; } +Result, + nsresult> +ServiceWorkerPrivate::WakeForExtensionAPIEvent( + const nsAString& aExtensionAPINamespace, + const nsAString& aExtensionAPIEventName) { + MOZ_ASSERT(NS_IsMainThread()); + + if (mInner) { + return mInner->WakeForExtensionAPIEvent(aExtensionAPINamespace, + aExtensionAPIEventName); + } + + MOZ_ASSERT_UNREACHABLE( + "WakeForExtensionAPIEvent unsupported in parent intercept mode."); + + return Err(NS_ERROR_NOT_IMPLEMENTED); +} + nsresult ServiceWorkerPrivate::SpawnWorkerIfNeeded(WakeUpReason aWhy, bool* aNewWorkerCreated, nsILoadGroup* aLoadGroup) { diff --git a/dom/serviceworkers/ServiceWorkerPrivate.h b/dom/serviceworkers/ServiceWorkerPrivate.h index 528baad17d10f..3202c22a97b0e 100644 --- a/dom/serviceworkers/ServiceWorkerPrivate.h +++ b/dom/serviceworkers/ServiceWorkerPrivate.h @@ -99,6 +99,7 @@ class ServiceWorkerPrivate final { NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(ServiceWorkerPrivate) using HasThreadSafeRefCnt = std::false_type; + using PromiseExtensionWorkerHasListener = MozPromise; protected: nsCycleCollectingAutoRefCnt mRefCnt; @@ -140,6 +141,10 @@ class ServiceWorkerPrivate final { nsCOMPtr aChannel, const nsAString& aClientId, const nsAString& aResultingClientId) = 0; + virtual RefPtr WakeForExtensionAPIEvent( + const nsAString& aExtensionAPINamespace, + const nsAString& aExtensionAPIEventName) = 0; + virtual nsresult SpawnWorkerIfNeeded() = 0; virtual void TerminateWorker() = 0; @@ -181,6 +186,10 @@ class ServiceWorkerPrivate final { nsILoadGroup* aLoadGroup, const nsAString& aClientId, const nsAString& aResultingClientId); + Result, nsresult> + WakeForExtensionAPIEvent(const nsAString& aExtensionAPINamespace, + const nsAString& aEXtensionAPIEventName); + bool MaybeStoreISupports(nsISupports* aSupports); void RemoveISupports(nsISupports* aSupports); diff --git a/dom/serviceworkers/ServiceWorkerPrivateImpl.cpp b/dom/serviceworkers/ServiceWorkerPrivateImpl.cpp index 732a97cfab7b2..6eba352cb95a6 100644 --- a/dom/serviceworkers/ServiceWorkerPrivateImpl.cpp +++ b/dom/serviceworkers/ServiceWorkerPrivateImpl.cpp @@ -1008,6 +1008,39 @@ nsresult ServiceWorkerPrivateImpl::SendFetchEventInternal( return NS_OK; } +RefPtr +ServiceWorkerPrivateImpl::WakeForExtensionAPIEvent( + const nsAString& aExtensionAPINamespace, + const nsAString& aExtensionAPIEventName) { + AssertIsOnMainThread(); + MOZ_ASSERT(mOuter); + + ServiceWorkerExtensionAPIEventOpArgs args; + args.apiNamespace() = nsString(aExtensionAPINamespace); + args.apiEventName() = nsString(aExtensionAPIEventName); + + auto promise = + MakeRefPtr(__func__); + + nsresult rv = ExecServiceWorkerOp( + std::move(args), + [promise](ServiceWorkerOpResult&& aResult) { + MOZ_ASSERT( + aResult.type() == + ServiceWorkerOpResult::TServiceWorkerExtensionAPIEventOpResult); + auto& result = aResult.get_ServiceWorkerExtensionAPIEventOpResult(); + promise->Resolve(result.extensionAPIEventListenerWasAdded(), __func__); + }, + [promise]() { promise->Reject(NS_ERROR_FAILURE, __func__); }); + + if (NS_FAILED(rv)) { + promise->Reject(rv, __func__); + } + + RefPtr outPromise(promise); + return outPromise.forget(); +} + void ServiceWorkerPrivateImpl::TerminateWorker() { AssertIsOnMainThread(); MOZ_ASSERT(mOuter); diff --git a/dom/serviceworkers/ServiceWorkerPrivateImpl.h b/dom/serviceworkers/ServiceWorkerPrivateImpl.h index 52c917d3cfe99..2e1574cc90cf2 100644 --- a/dom/serviceworkers/ServiceWorkerPrivateImpl.h +++ b/dom/serviceworkers/ServiceWorkerPrivateImpl.h @@ -41,6 +41,9 @@ class ServiceWorkerRegistrationInfo; class ServiceWorkerPrivateImpl final : public ServiceWorkerPrivate::Inner, public RemoteWorkerObserver { + using PromiseExtensionWorkerHasListener = + ServiceWorkerPrivate::PromiseExtensionWorkerHasListener; + public: NS_INLINE_DECL_REFCOUNTING(ServiceWorkerPrivateImpl, override); @@ -101,6 +104,10 @@ class ServiceWorkerPrivateImpl final : public ServiceWorkerPrivate::Inner, const nsAString& aClientId, const nsAString& aResultingClientId) override; + RefPtr WakeForExtensionAPIEvent( + const nsAString& aExtensionAPINamespace, + const nsAString& aExtensionAPIEventName) override; + nsresult SpawnWorkerIfNeeded() override; void TerminateWorker() override; diff --git a/toolkit/components/extensions/webidl-api/ExtensionBrowser.cpp b/toolkit/components/extensions/webidl-api/ExtensionBrowser.cpp index a9dbf6bd84ad4..240d41a8fd8ac 100644 --- a/toolkit/components/extensions/webidl-api/ExtensionBrowser.cpp +++ b/toolkit/components/extensions/webidl-api/ExtensionBrowser.cpp @@ -218,5 +218,10 @@ ExtensionTest* ExtensionBrowser::GetExtensionTest() { return mExtensionTest; } +bool ExtensionBrowser::HasWakeupEventListener(const nsString& aAPINamespace, + const nsString& aAPIName) { + return false; +} + } // namespace extensions } // namespace mozilla diff --git a/toolkit/components/extensions/webidl-api/ExtensionBrowser.h b/toolkit/components/extensions/webidl-api/ExtensionBrowser.h index 716fb610b777f..4786b03f36260 100644 --- a/toolkit/components/extensions/webidl-api/ExtensionBrowser.h +++ b/toolkit/components/extensions/webidl-api/ExtensionBrowser.h @@ -62,6 +62,11 @@ class ExtensionBrowser final : public nsISupports, public nsWrapperCache { // should be reported to the console bool ClearLastError(); + // Helpers used to keep track of the event listeners added during the + // initial sync worker script execution. + bool HasWakeupEventListener(const nsString& aAPINamespace, + const nsString& aAPIName); + // Helpers used for the ExtensionPort. // Get an ExtensionPort instance given its port descriptor (returns an