User:Asqueella/JEP 107

< User:Asqueella
Revision as of 12:11, 30 May 2010 by Asqueella (talk | contribs) (→‎API Methods: replace scriptMod.enable/disable with global add/remove, make add() explicit [8:a204bab6d64d])

This is an alternative version of Labs/Jetpack/Reboot/JEP/107 (page mods).

Here's in progress implementation (currently script mods).


JEP 107 - Page Mods

  • Champion: Nickolay Ponomarev <asqueella@gmail.com>
  • Status: ?
  • Bug Ticket: bug 546739
  • Type: API

Proposal

Introduce an API allowing jetpacks to run script whenever a content page the jetpack is interested in loads.

This way of enhancing functionality of web sites was popularized by the Greasemonkey extension. Unlike Greasemonkey scripts, in this proposal the pagemod's scripts all share the same Jetpack context and have the same privileges as the jetpack itself (but see #Discussion - e10s below).

Use Cases

  • Porting Greasemonkey-style scripts to Jetpack (see Bugzilla tweaks for Jetpack prototype for example)
  • Implementing scripts that enhance certain sites, while also having access to higher-privileged APIs.
  • Adding methods and properties to the global window. For example, window.geolocation could have been implemented as a jetpack, and we could implement window.camera or window.microphone as a jetpack in the future. (via bsmedberg)

Non-Use Cases

  • Porting Stylish-like CSS-based modifications to Jetpack (this could be added in a later version of the API)

Dependencies & Requirements

  • onWindowCreate and current implementation of the module in general requires bug 549539's fix, which (as of 2010-05-26) has only landed on mozilla-central (Firefox versions after 3.6.x). The plans are to land it on 3.6.x too, though.
  • Making this API work in Electrolysis/Jetpack requires additional effort from the e10s team (see details on that page)

API Methods

Here's an example of how the ScriptMod API can be used in a jetpack:

var ScriptMod = require("page-mod").ScriptMod;
var myMod = new ScriptMod({
  include: ["*.example.com",
            "http://example.org/a/specific/url",
            "http://example.info/*"],
  onWindowCreate: function(wrappedWindow) {
    // this runs each time a new content document starts loading, but
    // before the page starts loading, so we can't interact with the
    // page's DOM here yet.
    wrappedWindow.wrappedJSObject.newExposedProperty = 1;
  },
  onDOMReady: function(wrappedWindow) {
    // at this point we can work with the DOM
    wrappedWindow.document.body.innerHTML = "<h1>Jetpack Page Mods</h1>";
  }
});

In this proposal, a script mod specifies the pages it might modify, and the scripts to run on these pages.

The onWindowCreate callback function gets called at the earliest possible moment (before the page even started loading, which is made possible by the notifications added in bug 549539).

The onDOMReady callback is called as soon as the page's DOM is ready (on the DOMContentLoaded event)

ScriptMod constructor

The ScriptMod constructor takes a single options parameter which is an object that may define the following properties:

  • include: a required parameter specifying the pages the scripts in this script mod should run on.
    • Providing a string value str is equivalent to providing a single-item array [str].
    • The mod's scripts run on pages, matching any of include rules.
    • Each include rule is a string using one of the following formats (see discussion below):
      1. * (a single asterisk) - any page
      2. *.domain.name - pages from the specified domain and all its subdomains, regardless of their scheme.
      3. http://example.com/* - any URLs with the specified prefix.
      4. http://example.com/test - the single specified URL
  • onWindowCreate, onDOMReady: optional parameters specifying the code to run on the matched pages.
    • No code is run if these parameters are not specified.
    • Providing a single function func is equivalent to providing a single-item array [func]
    • When the provided value is an array, its items are expected to be functions. Non-function values are ignored.
    • The specified functions are called in order:
      • for onWindowCreate - when a page matching the include rules starts to load (but before any content is loaded in the page -- i.e. when the content-document-global-created notification implemented in bug 549539 is issued)
      • for onDOMReady - when a DOMContentLoaded event fires for the matching page.
    • An exception thrown from one of the functions does not stop the rest of functions from executing.
    • The specified callbacks are called with a single wrappedWindow parameter -- the content's window object wrapped in an XPCNativeWrapper. The callback's this is the page mod object (TBD not currently implemented). It goes without saying that with this syntax the callbacks are run in the calling module's scope, not in the content page's scope.

Creating a ScriptMod instance does not automatically add (activate) it.

add()

require("page-mod").add(scriptMod)
  • add() makes the specified script mod take effect on any matching pages that start to load after the call. Adding a script mod does not apply it to existing matching pages.
  • scriptMod must be a ScriptMod instance.
  • Trying to add the same script mod twice throws an exception.
  • This method does not have a return value.

remove()

require("page-mod").remove(scriptMod)
  • Call remove() to stop a script mod from running on further pages. This does not undo the mod's effects on already loaded pages.
  • scriptMod must be a ScriptMod instance, added earlier.
  • Trying to remove a script mod, that has not been added, throws.
  • This method does not have a return value.

Discussion

Extracted from this thread.

Discussion - format for include

First, a short survey of existing formats:

  • Greasemonkey scripts specify include and exclude URLs, each may contain wildcards ("*") in any location and may use a special ".tld" domain. These rules get compiled to a regular expression (see convert2RegExp), which is then matched against every URL loaded in the browser.
  • Match patterns for Google Chrome's content scripts are similar to Greasemonkey's, but force to specify domain (either fully, any domain, or *.domain) and don't have the magic tld domain.
  • When specifying CSS styling Using the Stylesheet Service, which is an easy and robust way to apply CSS to all content and is also what Stylish uses, you have to describe the filters using CSS, i.e. @-moz-document rule. It allows to specify domain, exact URL, or the URL prefix.

Myk's thoughts, "I need to take a closer look at this, but at first glance it looks like Chrome's format balances these goals best."

Discussion - e10s

Context: in the 0.5 timeframe it is planned to move jetpacks to their own processes, as described on the Electrolysis/Jetpack page. In the long term, content tabs will run in their own processes as well ("out-of-process tabs").

Communication between different processes is not entirely transparent [3]: while the jetpack process will be able to call content functions, reference content objects and pass primitive values to content, content won't be able to hold references to jetpack objects. This means it won't be possible to pass a jetpack-defined callback to content functions (with a few possible exceptions).

From the discussion referenced above, there are different possible models of pagemods execution (implying different implementation requirements):

A. run a script in the web page context, and let it communicate with the
jetpack via postMessage-style APIs. [...]

B. run a script in the jetpack context and pass it the window/document for a
page being loaded. This requires CPOW wrappers, which have some limitations[...]

C. Run page mods in the content processes (to avoid getting involved with CPOWs
and their limitations), but in a separate context from the page (to make it
possible to write page mods that can do things that we don't want to expose
to regular pages). My understanding is that it is similar to what Google
Chrome does and similar to what Greasemonkey does.

This proposal currently specifies (B). Comments collected from the discussion:

  • on A:
    • [Benjamin] This is trivially straightforward to do in a multi-process world[...]. But there are issues with polluting the content script namespace (e.g. if the jetpack needs to define functions).
    • [Nickolay] this means we can't give it [the jetpack script in content] any additional privileges (e.g. by listening for postMessage'd requests asking to do something that requires chrome permissions or by providing additional APIs like GM_* in Greasemonkey). It's fine for simple scripts, but not in general, I think.
  • on B:
    • [Myk] Despite the limitations imposed by the requirement for CPOW wrappers, its developer ergonomics appeal to me. It's not yet clear what the relative security implications are, however.
    • [Nickolay] thinks that inability to register a callback is a major flaw for those who need it (cited the case of using Gmail's Greasemonkey API to get notified of changes in the web app)
  • on C:
    • [Nickolay] suggested this as an optional addition to (B) for scripts that need transparent interaction with content.
    • [Benjamin] That's attractive in some ways, but it breaks the normal jetpack behavior of being a single script that does everything. I'm not sure it's worth breaking that programming model.
    • Requires additional code in the single-process case, additions to the platform in the e10s case.

Discsussion - comparison to the original JEP

This JEP has three main differences from the original JEP 107:

  • CSS-based mods were deferred to a later version of the API.
  • This JEP doesn't promise enabling/disabling page mods "instantly", since I don't see a way to implement it.
  • Scripts in the original JEP run in the context of the page, while in this JEP they run in the jetpack context. Although it's an important feature, I think it can be implemented separately, since it requires substantially more effort and additional coordination for e10s.
  • add/remove/empty methods on the page mod object were not included, since there's no clear use case for them, especially if the changes are not applied instantly, as in this proposal.

Other issues

(Not discussed in the google groups thread)

  • There's no easy way to clean up objects referenced from the web page (event listeners, exported APIs) when a jetpack using script mods is unloaded.
  • Should provide an example of using jQuery in a script mod. It's likely possible, but not obvious, given that the script mod runs in a hosting module's scope.

TODO

  • For SDK 0.5:
    1. Finalize the format for "include" rules and implement the necessary changes.
    2. Identify changes required for Electrolysis/Jetpack and implement them.
    3. Fix the remaining XXX:
      • minor tweaks
      • disable test on not supported Firefox versions (e.g. 3.6.3) -- is it needed or will jetpack drop support for 3.6.x with e10s anyway?
      • (maybe) figure out leak report in tests if the test tab is not closed before stopping tests.
  • Post-0.5:
    1. Possible API enhancements:
      • implement helper functions for common actions (insert <style>s, <script>s, etc.)
      • filter function instead of (or in addition) to exclude.
      • CSS-based mods
    2. Provide a way to run scripts in separate context for each page (i.e. in the content process for out-of-process tabs)