Bug 1564359 - Add in-tree documentation on how to port from the message manager to JSWindowActor messaging to be Fission-compatible. r=jdai
authorMike Conley <mconley@mozilla.com>
Wed, 14 Aug 2019 17:16:04 +0000
changeset 487990 9ae108bb86e2c93d9d74f43cfa8f39a9e84cc8b2
parent 487989 27209bcee14fe4bdbfb87c1f128035b3d1cb7f96
child 487991 fc5ca772497d74b3201cdd8fa0a2c09a83f51d51
push id36434
push usercbrindusan@mozilla.com
push dateThu, 15 Aug 2019 09:44:30 +0000
treeherdermozilla-central@144fbfb409b7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdai
bugs1564359
milestone70.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1564359 - Add in-tree documentation on how to port from the message manager to JSWindowActor messaging to be Fission-compatible. r=jdai Differential Revision: https://phabricator.services.mozilla.com/D40475
dom/docs/Fission.rst
--- a/dom/docs/Fission.rst
+++ b/dom/docs/Fission.rst
@@ -63,23 +63,27 @@ Other notable differences between JSWind
 #. Each JSWindowActor pair is associated with a particular frame. For example, given the following DOM hierarchy::
 
      <browser src="https://www.example.com">
        <iframe src="https://www.a.com" />
        <iframe src="https://www.b.com" />
 
    A ``JSWindowActorParent / ``JSWindowActorChild`` pair instantiated for either of the ``iframe``'s would only be sending messages to and from that ``iframe``.
 
+#. There's only one pair per actor type, per frame.
+
+   For example, suppose we have a ``ContextMenu`` actor. The parent process can have up to N instances of the ``ContextMenuParent`` actor, where N is the number of frames that are currently loaded. For any individual frame though, there's only ever one `ContextMenuChild` associated with that frame.
+
 #. We can no longer assume full, synchronous access to the frame tree, even in content processes.
 
    This is a natural consequence of splitting frames to run out-of-process.
 
 #. ``JSWindowActorChild``'s live as long as the ``BrowsingContext`` they're associated with.
 
-  If in the previously mentioned DOM hierarchy, one of the ``<iframe>``s unload, any associated JSWindowActor pairs will be torn down.
+  If in the previously mentioned DOM hierarchy, one of the ``<iframe>``'s unload, any associated JSWindowActor pairs will be torn down.
 
 .. hint::
    JS Window Actors are "managed" by the WindowGlobal IPC Actors, and are implemented as JS classes (subclasses of ``JSWindowActorParent`` and ``JSWindowActorChild``) instantiated when requested for any particular window. Like the Frame Message Manager, they are ultimately using IPC Actors to communicate under the hood.
 
 .. figure:: Fission-actors-diagram.png
    :width: 233px
    :height: 240px
 
@@ -93,76 +97,104 @@ The ``JSWindowActorParent`` and ``JSWind
 
 ``sendAsyncMessage``
 ````````````````````
 
 This has a similar signature as the ``sendAsyncMessage`` method for Message Managers::
 
     sendAsyncMessage("SomeMessage", { key: "value" }, { transferredObject });
 
-The second argument is serialized and sent down to the receiver (this can include ``nsIPrincipal``s), and the third object sends `Transferrables`_ to the receiver. Note that CPOWs cannot be sent.
+Like messages sent via the Message Manager, anything that can be serialized using the structured clone algorithm can be sent down through the second argument. Additionally, ``nsIPrincipal``'s can be sent without manually serializing and deserializing them.
+
+The third argument sends `Transferables`_ to the receiver, for example an ``ArrayBuffer``.
+
+.. note::
+    Cross Process Object Wrappers (CPOWs) cannot be sent over JSWindowActors.
 
 .. note::
     Notably absent is ``sendSyncMessage`` or ``sendRPCMessage``. Sync IPC is not supported on JSWindowActors, and code which needs to send sync messages should be modified to use async messages, or must send them over the per-process message manager.
 
 ``sendQuery``
 `````````````
 
 ``sendQuery`` improves upon ``sendAsyncMessage`` by returning a ``Promise``. The receiver of the message must then return a ``Promise`` that can eventually resolve into a value - at which time the ``sendQuery`` ``Promise`` resolves with that value.
 
+The ``sendQuery`` method arguments follow the same conventions as ``sendAsyncMessage``, with the second argument being a structured clone, and the third being for `Transferables`_.
+
 ``receiveMessage``
 ``````````````````
 
 This is identical to the Message Manager implementation of ``receiveMessage``. The method receives a single argument, which is the de-serialized arguments that were sent via either ``sendAsyncMessage`` or ``sendQuery``. Note that upon handling a ``sendQuery`` message, the ``receiveMessage`` handler must return a ``Promise`` for that message.
 
 .. hint::
-    Using ``sendQuery``, and the ``receiveMessage`` is able to return a value right away? Try using ``Promise.resolve(value);`` to return ``value``.
+    Using ``sendQuery``, and the ``receiveMessage`` is able to return a value right away? Try using ``Promise.resolve(value);`` to return ``value``, or you could also make your ``receiveMessage`` method an async function, presuming none of the other messages it handles need to get a non-Promise return value.
+
+Other JSWindowActor methods that can be overridden
+--------------------------------------------------
+
+``constructor()``
+
+If there's something you need to do as soon as the JSWindowActor is instantiated, the ``constructor`` function is a great place to do that.
 
-JSWindowActor destroy methods
------------------------------
+``observe(subject, topic, data)``
+`````````````````````````````````
+
+If you register your Actor to listen for ``nsIObserver`` notifications, implement an ``observe`` method with the above signature to handle the notification.
+
+``handleEvent(event)``
+``````````````````````
+
+If you register your Actor to listen for content events, implement a ``handleEvent`` method with the above signature to handle the event.
 
 ``willDestroy``
 ```````````````
 
 This method will be called when we know that the JSWindowActor pair is going to be destroyed because the associated BrowsingContext is going away. You should override this method if you have any cleanup you need to do before going away.
 
 You can also use ``willDestroy`` as a last opportunity to send messages to the other side, as the communications channel at this point is still running.
 
+.. note::
+    This method cannot be async.
+
 ``didDestroy``
 ``````````````
 
 This is another point to clean-up an Actor before it is destroyed, but at this point, no communication is possible with the other side.
 
+.. note::
+    This method cannot be async.
 
-Other things are exposed on a JSWindowActorParent
--------------------------------------------------
 
-``BrowsingContext``
-```````````````````
+Other things exposed on a JSWindowActorParent
+---------------------------------------------
+
+``CanonicalBrowsingContext``
+````````````````````````````
 
 TODO
 
 ``WindowGlobalParent``
 ``````````````````````
 
 TODO
 
-Other things are exposed on a JSWindowActorChild
--------------------------------------------------
+Other things exposed on a JSWindowActorChild
+--------------------------------------------
 
 ``BrowsingContext``
 ```````````````````
 
 TODO
 
 ``WindowGlobalChild``
 `````````````````````
 
 TODO
 
+
 Helpful getters
 ```````````````
 
 A number of helpful getters exist on a ``JSWindowActorChild``, including:
 
 ``this.document``
 ^^^^^^^^^^^^^^^^^
 
@@ -173,25 +205,210 @@ The currently loaded document in the fra
 
 The outer window for the frame associated with this ``JSWindowActorChild``.
 
 ``this.docShell``
 ^^^^^^^^^^^^^^^^^
 
 The ``nsIDocShell`` for the frame associated with this ``JSWindowActorChild``.
 
-See `JSWindowActor.webidl <https://searchfox.org/mozilla-central/source/dom/chrome-webidl/JSWindowActor.webidl>`_ for more detail on exactly what is exposed on both ``JSWindowActorParent`` and ``JSWindowActorChild`` implementations.
+See `JSWindowActor.webidl`_ for more detail on exactly what is exposed on both ``JSWindowActorParent`` and ``JSWindowActorChild`` implementations.
 
 How to port from message manager and framescripts to JS Window Actors
 ---------------------------------------------------------------------
 
-TBD
+.. _fission.message-manager-actors:
+
+Message Manager Actors
+``````````````````````
+
+While the JSWindowActor mechanism was being designed and developed, large sections of our framescripts were converted to an "actor style" pattern to make eventual porting to JSWindowActors easier. These Actors use the Message Manager under the hood, but made it much easier to shrink our framescripts, and also allowed us to gain significant memory savings by having the actors be lazily instantiated.
+
+You can find the list of Message Manager Actors (or "Legacy Actors") in `BrowserGlue.jsm <https://searchfox.org/mozilla-central/source/browser/components/BrowserGlue.jsm>`_ and `ActorManagerParent.jsm <https://searchfox.org/mozilla-central/source/toolkit/modules/ActorManagerParent.jsm>`_, in the ``LEGACY_ACTORS`` lists.
+
+.. note::
+  The split in Message Manager Actors defined between ``BrowserGlue`` and ``ActorManagerParent`` is mainly to keep Firefox Desktop specific Actors separate from Actors that can (in theory) be instantiated for non-Desktop browsers (like Fennec and GeckoView-based browsers). Firefox Desktop-specific Actors should be registered in ``BrowserGlue``. Shared "toolkit" Actors should go into ``ActorManagerParent``.
+
+"Porting" these Actors often means doing what is necessary in order to move their registration entries from ``LEGACY_ACTORS`` to the ``ACTORS`` list.
+
+Figuring out the lifetime of a new Actor pair
+`````````````````````````````````````````````
+
+In the old model, framescript were loaded and executed as soon as possible by the top-level frame. In the JSWindowActor model, the Actors are much lazier, and only instantiate when:
+
+1. They're instantiated explicitly by calling ``getActor`` on a ``WindowGlobal``, and passing in the name of the Actor.
+2. A message is sent to them.
+3. A pre-defined ``nsIObserver`` observer notification fires
+4. A pre-defined content Event fires
+
+Making the Actors lazy like this saves on processing time to get a frame ready to load web pages, as well as the overhead of loading the Actor into memory.
+
+When porting a framescript to JSWindowActors, often the first question to ask is: what's the entrypoint? At what point should the Actors instantiate and become active?
+
+For example, when porting the content area context menu for Firefox, it was noted that the ``contextmenu`` event firing in content was a natural event to wait for to instantiate the Actor pair. Once the ``ContextMenuChild`` instantiated, the ``handleEvent`` method was used to inspect the event and prepare a message to be sent to the ``ContextMenuParent``. This example can be found by looking at the patch for the `Context Menu Fission Port`_.
+
+.. _fission.registering-a-new-jswindowactor:
+
+Registering a new JSWindowActor
+```````````````````````````````
+
+``ChromeUtils`` exposes an API for registering window actors, but both ``BrowserGlue`` and ``ActorManagerParent`` are the main entry points where the registration occurs. If you want to register an actor, you should put them in one of the ``ACTORS`` lists in one of those two files. See :ref:`fission.message-manager-actors` for details.
+
+The ``ACTORS`` lists expect a key-value pair, where the key is the name of the actor pair (example: ``ContextMenu``), and the value is an ``Object`` of registration parameters.
+
+The full list of registration parameters can be found in the `JSWindowActor.webidl`_ file as ``WindowActorOptions``, ``WindowActorSidedOptions`` and ``WindowActorChildOptions``.
+
+Here's an example JSWindowActor registration pulled from ``BrowserGlue.jsm``:
+
+.. code-block:: javascript
+
+   Plugin: {
+      parent: {
+        moduleURI: "resource:///actors/PluginParent.jsm",
+      },
+      child: {
+        moduleURI: "resource:///actors/PluginChild.jsm",
+        events: {
+          PluginBindingAttached: { capture: true, wantUntrusted: true },
+          PluginCrashed: { capture: true },
+          PluginOutdated: { capture: true },
+          PluginInstantiated: { capture: true },
+          PluginRemoved: { capture: true },
+          HiddenPlugin: { capture: true },
+        },
+
+        observers: ["decoder-doctor-notification"],
+      },
+
+      allFrames: true,
+    },
+
+This example is for the JSWindowActor implementation of click-to-play for Flash.
+
+Let's examine the first chunk:
+
+.. code-block:: javascript
+
+      parent: {
+        moduleURI: "resource:///actors/PluginParent.jsm",
+      },
+
+Here, we're declaring that the ``PluginParent`` subclassing ``JSWindowActorParent`` will be defined and exported inside the ``PluginParent.jsm`` file. That's all we have to say for the parent (main process) side of things.
+
+.. note::
+    It's not sufficient to just add a new .jsm file to the actors subdirectories. You also need to update the ``moz.build`` files in the same directory to get the ``resource://`` linkages set up correctly.
+
+Let's look at the second chunk:
+
+.. code-block:: javascript
 
-Example
--------
+      child: {
+        moduleURI: "resource:///actors/PluginChild.jsm",
+        events: {
+          PluginBindingAttached: { capture: true, wantUntrusted: true },
+          PluginCrashed: { capture: true },
+          PluginOutdated: { capture: true },
+          PluginInstantiated: { capture: true },
+          PluginRemoved: { capture: true },
+          HiddenPlugin: { capture: true },
+        },
+
+        observers: ["decoder-doctor-notification"],
+      },
+
+      allFrames: true,
+    },
+
+We're similarly declaring where the ``PluginChild`` subclassing ``JSWindowActorChild`` can be found.
+
+Next, we declare the content events, if fired in a BrowsingContext, will cause the JSWindowActor pair to instantiate if it doesn't already exist, and then have ``handleEvent`` called on the ``PluginChild`` instance. For each event name, an Object of event listener options can be passed. You can use the same event listener options as accepted by ``addEventListener``.
+
+Next, we declare that ``PluginChild`` should observe the ``decoder-doctor-notification`` ``nsIObserver`` notification. When that observer notification fires, the ``PluginChild`` will be instantiated for all ``BrowsingContext``'s, and the ``observe`` method on the ``PluginChild`` implementation will be called.
+
+Finally, we say that the ``PluginChild`` actor should apply to ``allFrames``. This means that the ``PluginChild`` is allowed to be loaded in any subframe. If ``allFrames`` is set to false, the actor will only ever load in the top-level frame.
+
+Using ContentDOMReference instead of CPOWs
+``````````````````````````````````````````
+
+Despite being outlawed as a way of synchronously accessing the properties of objects in other processes, CPOWs ended up being useful as a way of passing handles for DOM elements between processes.
+
+CPOW messages, however, cannot be sent over the JSWindowActor communications pipe, so this handy mechanism will no longer work.
+
+Instead, a new module called `ContentDOMReference.jsm`_ has been created which supplies the same capability. See that file for documentation.
+
+How to start porting parent-process browser code to use JSWindowActors
+``````````````````````````````````````````````````````````````````````
+
+The :ref:`fission.message-manager-actors` work made it much easier to migrate away from framescripts towards something that is similar to ``JSWindowActors``. It did not, however, substantially change how the parent process interacted with those framescripts.
+
+So when porting code to work with ``JSWindowActors``, we find that this is often where the time goes - refactoring the parent process browser code to accomodate the new ``JSWindowActor`` model.
+
+Usually, the first thing to do is to find a reasonable name for your actor pair, and get them registered (see :ref:`fission.registering-a-new-jswindowactor`), even if the actors implementations themselves are nothing but unmodified subclasses of ``JSWindowActorParent`` and ``JSWindowActorChild``.
+
+Next, it's often helpful to find and note all of the places where ``sendAsyncMessage`` is being used to send messages through the old message manager interface for the component you're porting, and where any messages listeners are defined.
+
+Let's look at a hypothetical example. Suppose we're porting part of the Page Info dialog, which scans each frame for useful information to display in the dialog. Given a chunk of code like this:
+
+.. code-block:: javascript
+
+    // This is some hypothetical Page Info dialog code.
+
+    let mm = browser.messageManager;
+    mm.sendAsyncMessage("PageInfo:getInfoFromAllFrames", { someArgument: 123 });
+
+    // ... and then later on
+
+    mm.addMessageListener("PageInfo:info", async function onmessage(message) {
+      // ...
+    });
+
+If a ``PageInfo`` pair of ``JSWindowActor``'s is registered, it might be tempting to simply replace the first part with:
+
+.. code-block:: javascript
+
+    let actor = browser.browsingContext.currentWindowGlobal.getActor("PageInfo");
+    actor.sendAsyncMessage("PageInfo:getInfoFromAllFrames", { someArgument: 123 });
+
+However, if any of the frames on the page are running in their own process, they're not going to receive that ``PageInfo:getInfoFromAllFrames`` message. Instead, in this case, we should walk the ``BrowsingContext`` tree, and instantiate a ``PageInfo`` actor for each global, and send one message each to get information for each frame. Perhaps something like this:
+
+.. code-block:: javascript
+
+    let contextsToVisit = [browser.browsingContext];
+    while (contextsToVisit.length) {
+      let currentContext = contextsToVisit.pop();
+      let global = currentContext.currentWindowGlobal;
+
+      if (!global) {
+        continue;
+      }
+
+      let actor = global.getActor("PageInfo");
+      actor.sendAsyncMessage("PageInfo:getInfoForFrame", { someArgument: 123 });
+
+      contextsToVisit.push(...currentContext.getChildren());
+    }
+
+The original ``"PageInfo:info"`` message listener will need to be updated, too. Any responses from the ``PageInfoChild`` actor will end up being passed to the ``receiveMessage`` method of the ``PageInfoParent`` actor. It will be necessary to pass that information along to the interested party (in this case, the dialog code which is showing the table of interesting Page Info).
+
+It might be necessary to refactor or rearchitect the original senders and consumers of message manager messages in order to accommodate the ``JSWindowActor`` model. Sometimes it's also helpful to have a singleton management object that manages all ``JSWindowActorParent`` instances and does something with their results. See ``PermitUnloader`` inside the implementation of `BrowserElementParent.jsm`_ for example.
+
+Where to store state
+````````````````````
+
+It's not a good idea to store any state within a ``JSWindowActorChild`` that you want to last beyond the lifetime of its ``BrowsingContext``. An out-of-process ``<iframe>`` can be closed at any time, and if it's the only one for a particular content process, that content process will soon be shut down, and any state you may have stored there will go away.
+
+Your best bet for storing state is in the parent process.
+
+.. hint::
+    If each individual frame needs state, consider using a ``WeakMap`` in the parent process, mapping ``CanonicalBrowsingContext``'s with that state. That way, if the associates frames ever go away, you don't have to do any cleaning up yourself.
+
+If you have state that you want multiple ``JSWindowActorParent``'s to have access to, consider having a "manager" of those ``JSWindowActorParent``'s inside of the same .jsm file to hold that state. See ``PermitUnloader`` inside the implementation of `BrowserElementParent.jsm`_ for example.
+
+Minimal Example Actors
+----------------------
 
 **Define an Actor**
 
 .. code-block:: javascript
 
   // resource://testing-common/TestParent.jsm
   var EXPORTED_SYMBOLS = ["TestParent"];
   class TestParent extends JSWindowActorParent {
@@ -208,45 +425,25 @@ Example
   class TestChild extends JSWindowActorChild {
     constructor() {
       super();
     }
     ...
   }
 
 
-**Registering an Actor**
-
-.. code-block:: javascript
-
-    ChromeUtils.registerWindowActor("Test", {
-      parent: {
-        moduleURI: "resource://testing-common/TestParent.jsm",
-      },
-      child: {
-        moduleURI: "resource://testing-common/TestChild.jsm",
-
-        events: {
-          "mozshowdropdown": {},
-        },
-
-        observers: [
-          "test-js-window-actor-child-observer",
-        ],
-      },
-
-      allFrames: true,
-    });
-
-
 **Get a JS window actor for a specific window**
 
 .. code-block:: javascript
 
   // get parent side actor
   let parentActor = this.browser.browsingContext.currentWindowGlobal.getActor("Test");
 
   // get child side actor
   let childActor = content.window.getWindowGlobalChild().getActor("Test");
 
 .. _Electrolysis Project: https://wiki.mozilla.org/Electrolysis
 .. _IPC Actors: https://developer.mozilla.org/en-US/docs/Mozilla/IPDL/Tutorial
-.. _Transferrables https://developer.mozilla.org/en-US/docs/Web/API/Transferable
\ No newline at end of file
+.. _Transferables: https://developer.mozilla.org/en-US/docs/Web/API/Transferable
+.. _Context Menu Fission Port: https://hg.mozilla.org/mozilla-central/rev/adc60720b7b8
+.. _ContentDOMReference.jsm: https://searchfox.org/mozilla-central/source/toolkit/modules/ContentDOMReference.jsm
+.. _JSWindowActor.webidl: https://searchfox.org/mozilla-central/source/dom/chrome-webidl/JSWindowActor.webidl
+.. _BrowserElementParent.jsm: https://searchfox.org/mozilla-central/rev/ec806131cb7bcd1c26c254d25cd5ab8a61b2aeb6/toolkit/actors/BrowserElementParent.jsm