Skip to content

Commit

Permalink
Bug 1728327 - Introduce a new nsIServiceWorkerManager.wakeForExtensio…
Browse files Browse the repository at this point in the history
…nAPIEvent 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
  • Loading branch information
rpl committed Dec 15, 2021
1 parent ada312e commit fe9a6a9
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 2 deletions.
19 changes: 17 additions & 2 deletions dom/interfaces/base/nsIServiceWorkerManager.idl
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -78,7 +77,6 @@ interface nsIServiceWorkerInfo : nsISupports
// the cancellation control flow path will be triggered.
attribute nsresult testingInjectCancellation;


void attachDebugger();

void detachDebugger();
Expand Down Expand Up @@ -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.
Expand Down
74 changes: 74 additions & 0 deletions dom/serviceworkers/ServiceWorkerManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Promise> 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<nsIURI> scopeURI;
nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aExtensionBaseURL);
if (NS_FAILED(rv)) {
outer->MaybeReject(rv);
outer.forget(aPromise);
return NS_OK;
}

nsCOMPtr<nsIPrincipal> 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<ServiceWorkerPrivate::PromiseExtensionWorkerHasListener> 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,
Expand Down
54 changes: 54 additions & 0 deletions dom/serviceworkers/ServiceWorkerOp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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<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) {
Expand Down Expand Up @@ -1811,6 +1861,10 @@ nsresult FetchEventOp::DispatchFetchEvent(JSContext* aCx,
case ServiceWorkerOpArgs::TParentToChildServiceWorkerFetchEventOpArgs:
op = MakeRefPtr<FetchEventOp>(std::move(aArgs), std::move(aCallback));
break;
case ServiceWorkerOpArgs::TServiceWorkerExtensionAPIEventOpArgs:
op = MakeRefPtr<ExtensionAPIEventOp>(std::move(aArgs),
std::move(aCallback));
break;
default:
MOZ_CRASH("Unknown Service Worker operation!");
return nullptr;
Expand Down
15 changes: 15 additions & 0 deletions dom/serviceworkers/ServiceWorkerOpArgs.ipdlh
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -101,6 +110,7 @@ union ServiceWorkerOpArgs {
ServiceWorkerPushSubscriptionChangeEventOpArgs;
ServiceWorkerNotificationEventOpArgs;
ServiceWorkerMessageEventOpArgs;
ServiceWorkerExtensionAPIEventOpArgs;
ParentToChildServiceWorkerFetchEventOpArgs;
};

Expand Down Expand Up @@ -163,10 +173,15 @@ struct ServiceWorkerFetchEventOpResult {
nsresult rv;
};

struct ServiceWorkerExtensionAPIEventOpResult {
bool extensionAPIEventListenerWasAdded;
};

union ServiceWorkerOpResult {
nsresult;
ServiceWorkerCheckScriptEvaluationOpResult;
ServiceWorkerFetchEventOpResult;
ServiceWorkerExtensionAPIEventOpResult;
};

} // namespace dom
Expand Down
18 changes: 18 additions & 0 deletions dom/serviceworkers/ServiceWorkerPrivate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1589,6 +1589,24 @@ nsresult ServiceWorkerPrivate::SendFetchEvent(
return NS_OK;
}

Result<RefPtr<ServiceWorkerPrivate::PromiseExtensionWorkerHasListener>,
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) {
Expand Down
9 changes: 9 additions & 0 deletions dom/serviceworkers/ServiceWorkerPrivate.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ class ServiceWorkerPrivate final {
NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(ServiceWorkerPrivate)

using HasThreadSafeRefCnt = std::false_type;
using PromiseExtensionWorkerHasListener = MozPromise<bool, nsresult, false>;

protected:
nsCycleCollectingAutoRefCnt mRefCnt;
Expand Down Expand Up @@ -140,6 +141,10 @@ class ServiceWorkerPrivate final {
nsCOMPtr<nsIInterceptedChannel> aChannel, const nsAString& aClientId,
const nsAString& aResultingClientId) = 0;

virtual RefPtr<PromiseExtensionWorkerHasListener> WakeForExtensionAPIEvent(
const nsAString& aExtensionAPINamespace,
const nsAString& aExtensionAPIEventName) = 0;

virtual nsresult SpawnWorkerIfNeeded() = 0;

virtual void TerminateWorker() = 0;
Expand Down Expand Up @@ -181,6 +186,10 @@ class ServiceWorkerPrivate final {
nsILoadGroup* aLoadGroup, const nsAString& aClientId,
const nsAString& aResultingClientId);

Result<RefPtr<PromiseExtensionWorkerHasListener>, nsresult>
WakeForExtensionAPIEvent(const nsAString& aExtensionAPINamespace,
const nsAString& aEXtensionAPIEventName);

bool MaybeStoreISupports(nsISupports* aSupports);

void RemoveISupports(nsISupports* aSupports);
Expand Down
33 changes: 33 additions & 0 deletions dom/serviceworkers/ServiceWorkerPrivateImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1008,6 +1008,39 @@ nsresult ServiceWorkerPrivateImpl::SendFetchEventInternal(
return NS_OK;
}

RefPtr<ServiceWorkerPrivate::PromiseExtensionWorkerHasListener>
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<PromiseExtensionWorkerHasListener::Private>(__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<PromiseExtensionWorkerHasListener> outPromise(promise);
return outPromise.forget();
}

void ServiceWorkerPrivateImpl::TerminateWorker() {
AssertIsOnMainThread();
MOZ_ASSERT(mOuter);
Expand Down
7 changes: 7 additions & 0 deletions dom/serviceworkers/ServiceWorkerPrivateImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -101,6 +104,10 @@ class ServiceWorkerPrivateImpl final : public ServiceWorkerPrivate::Inner,
const nsAString& aClientId,
const nsAString& aResultingClientId) override;

RefPtr<PromiseExtensionWorkerHasListener> WakeForExtensionAPIEvent(
const nsAString& aExtensionAPINamespace,
const nsAString& aExtensionAPIEventName) override;

nsresult SpawnWorkerIfNeeded() override;

void TerminateWorker() override;
Expand Down
5 changes: 5 additions & 0 deletions toolkit/components/extensions/webidl-api/ExtensionBrowser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,5 +218,10 @@ ExtensionTest* ExtensionBrowser::GetExtensionTest() {
return mExtensionTest;
}

bool ExtensionBrowser::HasWakeupEventListener(const nsString& aAPINamespace,
const nsString& aAPIName) {
return false;
}

} // namespace extensions
} // namespace mozilla
5 changes: 5 additions & 0 deletions toolkit/components/extensions/webidl-api/ExtensionBrowser.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit fe9a6a9

Please sign in to comment.