w3c / ServiceWorker

Service Workers
https://w3c.github.io/ServiceWorker/
Other
3.63k stars 314 forks source link

dynamically registered event listeners for extension service worker #1698

Open hanguokai opened 6 months ago

hanguokai commented 6 months ago

Note that I'm talking about browser extension service worker, not Web service worker. But I would like to seek advice from Web service worker experts here.

Unlike the web environment, browser extensions usually provide users with some functional options in the extension settings interface. Ideally, the extension only registers relevant events in extension service worker when the user turns on a feature. So some developers are proposing to dynamically register events at WECG.

Here is my proposal:

At present, an extension Event object has these method:

addListener()
removeListener()
hasListener()
hasListeners()

addRules()
getRules()
removeRules()

For dynamically registered events for service worker, I proposal these new methods instead of changing existing methods:

subscribe(function-name: String)
unsubscribe(function-name: String)
hasSubscribed(function-name: String)

For example (there are lots of different events in browser extensions):

browser.webNavigation.onCommitted.subscribe("myFunctionName");
browser.webNavigation.onCommitted.unsubscribe("myFunctionName");

"FunctionName" is a global function name that declared in service worker, rather than a function object, and doesn't need to be registered in the first event loop when service worker wakes up. This allows the browser to remember both the event and this specific function independently of service worker's lifecycle, not just remember the event like addListener() does.

These new methods can be called in both service-worker context and non-service-worker context (e.g. a user setting page) dynamically, but only trigger events in service worker context, not in other contexts.

After calling some_event.subscribe(function-name), the browser triggers related events in service worker. If the service worker is inactive, wake up it first. Then looks for the function (by name) to execute.

I'd love to hear from the browser implementation perspective, such as whether it's possible or what problems it will encounter.

tomayac commented 6 months ago

Looping in @oliverdunk and @patrickkettner who may have opinions on this.

oliverdunk commented 6 months ago

Thanks for opening this Jackie. As we discussed in the last WECG meeting, I think improving extension events is something everyone is supportive of longer term and very curious to see if any web folk have thoughts on how we can make things more ergonomic. That said, I'm not sure if this proposal is the right one:

I'd prefer to look at ways of using self.addEventListener in extension service workers for extension events to align with the web. And then once we've got there, we can find potential improvements to registration.

hanguokai commented 6 months ago

To understand this proposal, we must first understand the current way of working.

The current way of working:

  1. Developers register event handlers at top level (i.e. the first event loop) in service worker. And the browser remembers what event types this service worker want to handle. (When service worker is terminated, the browser still remembers what event types this service worker want to handle).
  2. when new events are fired, browser wakes up service worker if it is not active.
  3. after the first event loop of service worker, browser dispatch events to event handlers that are registered in the first event loop.

This proposal is

  1. Developers can register event handlers for service worker at anywhere and anytime: including first event loop and later(e.g. in a function that runs later) in service worker, and even can be registered at outside of service worker at anytime (e.g in other extension pages).
  2. Here you will ask if event handers are not registered at top level, where does the browser find them after the first event loop of service worker?
  3. Here is the key point. When subscribe() an event, in addition to remember the event type, the browser also needs to remember the function name, so that the brwoser can find them.

Anyway, in order to be able to dynamically add or remove, enable or disable event handlers, the browser must remember some additional information beyond service worker lifecycle.

asutherland commented 6 months ago

I think specifying the function name dynamically via a string is a non-starter. Although there is precedent in HTML for inline JS that could be said to resemble this, it's bad precedent and is a major source of XSS issues.

For the particular use-case of WebExtensions that seems to be motivating this request, my general impression from discussion with our (Mozilla's) add-on teams is that one of our greatest concerns is dynamism in WebExtensions that enables bad actors to create extensions that seem benign but later are dynamically reconfigured to violate the user's privacy and/or security. An API surface like this would intentionally create problems for static analyses.

If the high-level goal is for WebExtensions to be able to reduce their performance impact on the browser until the user activates specific functionality, I would suggest that the correct approach for this is to have the enabling/disabling located on the WebExtension API surface that would be responsible for generating the event. Individual WebExtension logic can then add event listeners using addEventListener in the usual way. This a more appropriate layering for efficiency and devtools integration.

NavigationPreloadManager is not a perfect example of this, but is a pattern that WECG could follow if there somehow is not already an existing idiom for this.