In the Fission world, JS Window Actors will be the replacement for framescripts. Framescripts were how we structured code to be aware of the parent (UI) and child (content) separation, including establishing the communication channel between the two (via the Frame Message Manager).
In the Fission world, Actors will be the replacement for framescripts. Framescripts were how we structured code to be aware of the parent (UI) and child (content) separation, including establishing the communication channel between the two (via the message manager).
However, the framescripts had no way to establish further process separation downwards (that is, for out-of-process iframes). JS Window Actors will be the replacement.
However, the framescripts had no way to establish further process separation downwards (that is, for out-of-process iframes). Actors will be the replacement.
How are they structured?
------------------------
A review of the current Message Manager mechanism
`````````````````````````````````````````````````
Currently, in the post-e10s Firefox codebase, we have code living in the parent process (UI) that is in plain JS (.js files) or in JS modules (.jsm). In the child process (hosting the content), we use framescripts (.js) and also JS modules. The framescripts are instantiated once per top-level frame (or, in simpler terms, once per tab). This code has access to all of the DOM from the web content, including iframes on it.
.. note::
There are actually several types of Message Managers: Frame Message Managers, Window Message Managers, Group Message Managers and Process Message Managers. For the purposes of this documentation, it'ssimplesttorefertoallofthesemechanismsaltogetherasthe"Message Manager mechanism".MostoftheexamplesinthisdocumentwillbeoperatingontheassumptionthattheMessageManagerisaFrameMessageManager,whichisthemostcommonlyusedone.
2.Thesubscriberisresponsibleforunsubscribingwhenthere's no longer interest in the notifications (``removeMessageListener`` for the Frame Message Manager, ``removeEventListener`` for Events).
3. Any number of subscribers can be attached at any one time.
The two processes communicate between them through the message manager (mm) using the sendAsyncMessage API, and any code in the parent can communicate with any code in the child (and vice versa), by just listening to the messsages of interest.
.. figure:: Fission-framescripts.png
:width: 320px
:height: 200px
How JS Window Actors differ from the Frame Message Manager
For Fission, the JS Window Actors replacing framescripts will be structured in pairs. A pair of JS Window Actors will be instantiated lazily: one in the parent and one in the child process, and a direct channel of communication between the two will be established. The JS Window Actor in the parent must extend the global ``JSWindowActorParent`` class, and the JS Window Actor in the child must extend the global ``JSWindowActorChild`` class.
The JS Window Actor mechanism is similar to how `IPC Actors`_ work in the native layer of Firefox:
#. Every Actor has one counterpart in another process that they can communicate directly with.
#. Every Actor inherits a common communications API from a parent class.
#. Every Actor has a name that ends in either ``Parent`` or ``Child``.
#. There is no built-in mechanism for subscribing to messages. When one JS Window Actor sends a message, the counterpart JS Window Actor on the other side will receive it without needing to explicitly listen for it.
Other notable differences between JSWindowActor'sandMessageManager/framescripts:
#. Each JSWindowActor pair is associated with a particular frame. For example, given the following DOM hierarchy::
<browsersrc="https://www.example.com">
<iframesrc="https://www.a.com"/>
<iframesrc="https://www.b.com"/>
A``JSWindowActorParent/``JSWindowActorChild``pairinstantiatedforeitherofthe``iframe``'s would only be sending messages to and from that ``iframe``.
#. There'sonlyonepairperactortype,perframe.
Forexample,supposewehavea``ContextMenu``actor.TheparentprocesscanhaveuptoNinstancesofthe``ContextMenuParent``actor,whereNisthenumberofframesthatarecurrentlyloaded.Foranyindividualframethough,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.
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.
For fission, the actors replacing FrameScript will be structured in pairs. A pair of actors will be instantiated lazily: one in the parent and one in the child process, and a direct channel of communication between the two will be established.
These actors are "managed" by the WindowGlobal actors, and are implemented as JS classes instantiated when requested for any particular window.
.. figure:: Fission-actors-diagram.png
:width: 233px
:height: 240px
Cross-process communication with JS Window Actors
-------------------------------------------------
.. note::
Like the Message Manager, JSWindowActors are implemented for both in-process and out-of-process frame communication. This means that porting to JSWindowActors can be done immediately without waiting for out-of-process iframes to be enabled.
Communicating between actors
----------------------------
The ``JSWindowActorParent`` and ``JSWindowActorChild`` base classes expose two methods for sending messages:
``sendAsyncMessage``
````````````````````
This has a similar signature as the ``sendAsyncMessage`` method for Message Managers::
The second argument is serialized and sent down to the receiver (this can include ``nsIPrincipal``s), and the third object sends `Transferables`_ to the receiver. Note that CPOWs cannot be sent.
The actor will only communicate between each other. To communicate, the actor supports ``sendAsyncMessage`` and ``sendQuery`` which acts like send async message, but also returns a promise, and it will be present for both in-process and out-of-process windows.
.. 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.
``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``.
Other JSWindowActor methods that can be overridden
If there'ssomethingyouneedtodoassoonastheJSWindowActorisinstantiated,the``constructor``functionisagreatplacetodothat.
``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.
Sync IPC is not supported on JSWindowActors, and code which needs to send sync messages should be modified, or must send them over the per-process message manager.
Things are exposed on a JS Window Actor
---------------------------------------
OtherthingsareexposedonaJSWindowActorParent
-------------------------------------------------
See `JSWindowActor.webidl <https://searchfox.org/mozilla-central/source/dom/chrome-webidl/JSWindowActor.webidl>`_ for more detail.
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 `ContextMenuFissionPort`_.
This example is for the JSWindowActor implementation of click-to-play for Flash.
Let'sexaminethefirstchunk:
..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'sallwehavetosayfortheparent(mainprocess)sideofthings.
..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.
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'talreadyexist,andthenhave``handleEvent``calledonthe``PluginChild``instance.Foreacheventname,anObjectofeventlisteneroptionscanbepassed.Youcanusethesameeventlisteneroptionsasacceptedby``addEventListener``.
Finally,wedeclarethat``PluginChild``shouldobservethe``decoder-doctor-notification````nsIObserver``notification.Whenthatobservernotificationfires,the``PluginChild``willbeinstantiatedforall``BrowsingContext``'s, and the ``observe`` method on the ``PluginChild`` implementation will be called.
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'softenhelpfultofindandnotealloftheplaceswhere``sendAsyncMessage``isbeingusedtosendmessagesthroughtheoldmessagemanagerinterfaceforthecomponentyou're porting, and where any messages listeners are defined.
Let'slookatahypotheticalexample.Supposewe'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.
However, if any of the frames on the page are running in their own process, they'renotgoingtoreceivethat``PageInfo:getInfoFromAllFrames``message.Instead,inthiscase,weshouldwalkthe``BrowsingContext``tree,andinstantiatea``PageInfo``actorforeachglobal,andsendonemessageeachtogetinformationforeachframe.Perhapssomethinglikethis: