diff options
Diffstat (limited to 'files/fr/mozilla/firefox/multiprocessus_firefox')
3 files changed, 285 insertions, 0 deletions
diff --git a/files/fr/mozilla/firefox/multiprocessus_firefox/index.html b/files/fr/mozilla/firefox/multiprocessus_firefox/index.html new file mode 100644 index 0000000000..9a09652da6 --- /dev/null +++ b/files/fr/mozilla/firefox/multiprocessus_firefox/index.html @@ -0,0 +1,77 @@ +--- +title: Firefox multiprocessus +slug: Mozilla/Firefox/Multiprocessus_Firefox +tags: + - multiprocessus +translation_of: Mozilla/Firefox/Multiprocess_Firefox +--- +<div>{{FirefoxSidebar}}</div><p>Dans les anciennes versions de Firefox pour bureau, le navigateur ne s'exécutait que dans un seul processus du système d'exploitation. En particulier, le JavaScript qui exécutait l'interface du navigateur (également connu sous le nom « code chrome ») s'exécutait habituellement dans le même processus que celui présent dans les pages web (<span class="spellmodupdated" title="">appelé</span> aussi « contenu » ou « contenu web »).</p> + +<p>Les dernières versions de Firefox e<span class="spellmod" title="">xécutent</span> l'interface du navigateur dans un processus différent de celui des pages w<span class="spellmod" title="">eb.</span> Dans la première itération de cette architecture, tous les onglets tournent dans le même processus et l'interface du navigateur dans un processus différent. Dans les itérations suivantes, nous espérons avoir plus d'un processus pour le contenu. Le projet qui consiste à apporter le <span class="spellmod" title="">multi</span>processus dans Firefox est <span class="spellmod" title="">appelé</span> <em>Electrolysis</em>, qui correspond à l'abréviation e10s.</p> + +<p>Les pages web classiques ne sont pas affectées par l'apparition du multiprocessus dans Firefox. Les développeurs travaillant sur Firefox lui-même ou les extensions du navigateur seront affectés si leur code <span id="result_box" lang="fr"><span class="hps">repose sur la possibilité</span> <span class="hps">d'accéder directement à</span> du contenu web.</span></p> + +<p><span lang="fr"><span class="hps">Au lieu d'accéder au contenu directement, le JavaScript présent dans le code chrome devra utiliser </span></span>le <a href="/Firefox/Multiprocess_Firefox/Message_manager">gestionnaire de messages</a> pour accéder au contenu. Pour faciliter la transition, nous avons mis en place des objets (les <a href="/Firefox/Multiprocess_Firefox/Cross_Process_Object_Wrappers">Cross Process Object Wrappers</a>) et vous pouvez vous référer à cette <a href="https://developer.mozilla.org/Firefox/Multiprocess_Firefox/Limitations_of_chrome_scripts#Compatibility_shims">page</a> pour découvrir les limitations des scripts chrome. Si vous développez des extensions, vous pouvez lire le <a href="https://developer.mozilla.org/Mozilla/Add-ons/Working_with_multiprocess_Firefox">guide pour travailler avec Firefox multiprocessus</a>.</p> + +<hr> +<div class="column-container"> +<div class="column-half"> +<dl> + <dt><a href="/Firefox/Multiprocess_Firefox/Technical_overview">Présentation technique</a></dt> + <dd>Une vision très haut niveau de comment le multiprocessus pour Firefox est mis en œuvre.</dd> + <dt><a href="/en-US/Firefox/Multiprocess_Firefox/Web_content_compatibility">Guide de compatibilité du contenu web</a></dt> + <dd>Conseils et détails sur de potentiels problèmes de compatibilité des sites web qui peuvent arriver à cause de la transition. Remarque : il n'y en a pas beaucoup !</dd> + <dt><a href="/Firefox/Multiprocess_Firefox/Glossary">Glossaire</a></dt> + <dd>La définition du jargon utilisé dans le projet <em>Electrolysis</em>.</dd> + <dt><a href="/Firefox/Multiprocess_Firefox/Message_Manager">Gestionnaire de messages</a></dt> + <dd><span id="result_box" lang="fr"><span class="hps">Guide complet sur</span> <span class="hps">les</span> <span class="hps">objets utilisés</span> <span class="hps">pour communiquer entre</span> <span class="hps">le code chrome</span> <span class="hps">et le contenu.</span></span></dd> + <dt><a href="/Add-ons/SDK/Guides/Multiprocess_Firefox_and_the_SDK">Extensions basées sur le SDK</a></dt> + <dd>Comment migrer vos extensions en utilisant le SDK d'extensions.</dd> + <dt><a href="/Firefox/Multiprocess_Firefox/Which_URIs_load_where">Quelle URI se charge où</a></dt> + <dd>Un guide rapide sur quelles URI - chrome:, about:, file:, resource: - sont chargés dans chaque processus.</dd> +</dl> +</div> + +<div class="column-half"> +<dl> + <dt><a href="/Firefox/Multiprocess_Firefox/Motivation">Motivation</a></dt> + <dd>Pourquoi nous implémentons le multiprocessus dans Firefox : performance, sécurité et stabilité.</dd> + <dt><a href="/Add-ons/Working_with_multiprocess_Firefox">Guide de migration des extensions</a></dt> + <dd>Si vous êtes un développeur d'extension, apprenez si votre extension est affectée et comment mettre à jour votre code.</dd> + <dt><a href="/Firefox/Multiprocess_Firefox/Cross_Process_Object_Wrappers">Objets CPOW</a></dt> + <dd>Les objets appelés <em>Cross Process Object Wrappers</em> sont une aide à la migration, permettant au code chrome d'accéder de manière synchrone au contenu.</dd> + <dt><a href="/Firefox/Multiprocess_Firefox/Debugging_frame_scripts">Deboggage des processus du contenu</a></dt> + <dd>Comment déboguer du code fonctionnant dans le processus contenu.</dd> + <dt><a href="/docs/Mozilla/Firefox/Multiprocess_Firefox/Tab_selection_in_multiprocess_Firefox">Sélection d'onglet dans Firefox multiprocessus</a></dt> + <dd>Comment le passage entre les onglets marche dans Firefox multiprocessus.</dd> +</dl> +</div> +</div> + +<hr> +<div class="column-container"> +<div class="column-half"> +<dl> + <dt><a href="/Firefox/Multiprocess_Firefox/Limitations_of_chrome_scripts">Limitations des scripts chrome</a></dt> + <dd>Les pratiques ne fonctionnant plus dans le code chrome et comment y remédier.</dd> +</dl> +</div> + +<div class="column-half"> +<dl> + <dt><a href="/Firefox/Multiprocess_Firefox/Limitations_of_frame_scripts">Limitations des scripts avec privilèges</a></dt> + <dd>Les pratiques ne fonctionnant plus dans le code des scripts de cadre et comment y remédier.</dd> +</dl> +</div> +</div> + +<hr> +<h2 id="Contactez-nous">Contactez-nous</h2> + +<p>Pour plus d'informations sur le projet, vous impliquer ou poser des questions :</p> + +<ul> + <li><strong>Page du projet Electrolysis</strong>: <a href="https://wiki.mozilla.org/Electrolysis">https://wiki.mozilla.org/Electrolysis</a></li> + <li><strong>IRC</strong>: #e10s sur <a href="https://wiki.mozilla.org/IRC">irc.mozilla.org</a></li> + <li><strong>Mailing list</strong>: <a href="https://groups.google.com/forum/#!forum/mozilla.dev.tech.electrolysis">dev.tech.electrolysis</a></li> +</ul> diff --git a/files/fr/mozilla/firefox/multiprocessus_firefox/motivation/index.html b/files/fr/mozilla/firefox/multiprocessus_firefox/motivation/index.html new file mode 100644 index 0000000000..ed2b4828b4 --- /dev/null +++ b/files/fr/mozilla/firefox/multiprocessus_firefox/motivation/index.html @@ -0,0 +1,44 @@ +--- +title: Motivation +slug: Mozilla/Firefox/Multiprocessus_Firefox/Motivation +translation_of: Mozilla/Firefox/Multiprocess_Firefox/Motivation +--- +<div>{{FirefoxSidebar}}</div><p>Il y a trois raisons qui nous poussent à faire tourner le contenu de Firefox dans un processus séparé : performance, sécurité, stabilité.</p> + +<h2 id="Performance">Performance</h2> + +<p>La plupart des travaux de Mozilla ces deux dernières années ont mis l'accent sur la réactivité du navigateur. Le but est de réduire les lenteurs de l'interface (<a href="/en-US/docs/Glossary/Jank">jank</a>) - c'est-à-dire quand le navigateur a l'air de se figer lors du chargement d'une grosse page, au remplissage d'un formulaire ou lors du défilement de la page. La réactivité devient plus importante que le débit sur le web aujourd'hui. Un gros du travail a été réalisé comme une partie du projet <a href="https://wiki.mozilla.org/Performance/Snappy">Snappy</a>. En voici les principaux axes :</p> + +<ul> + <li>Mettre les actions longues à réaliser dans un thread séparé pour que le thread principal puisse continuer à répondre.</li> + <li>Rendre les Entrées/Sorties asynchrones ou dans des threads séparés pour que le thread principal ne soit pas bloqué par le disque dur.</li> + <li>Réduire les gros codes en plusieurs petits morceaux et en exécutant la boucle d'évènement entre ces morceaux.</li> +</ul> + +<p>Une grande partie de ces travaux a déjà été réalisée. Les points restants sont difficiles à résoudre. Par exemple, l'exécution de JavaScript se déroule dans le thread principal ce qui bloque la boucle d'évènements. Exécuter ces composants dans un thread séparé est difficile parce qu'ils accèdent à des données comme le DOM qui ne sont pas sécurisées dans le cas d'accès par différents threads. Comme alternative, nous avons envisagé de pouvoir exécuter la boucle d'évènements au milieu de l'exécution de JavaScript, mais cela briserait beaucoup d'hypothèses de différentes parties de Firefox (comme les extensions).</p> + +<p>Exécuter le contenu web dans un processus séparé est une alternative viable. Comme l'approche avec différents threads, Firefox est capable d'exécuter la boucle d'évènements pendant que le JavaScript et l'agencement s'exécutent dans un processus contenu. Mais, le code d'Interface Utilisateur n'a pas accès au contenu du DOM ou d'autres structures de données du contenu, il y a donc un besoin de verrouillage et de protection d'accès sur cette partie. L'inconvénient est que tout code présent dans le processus interface utilisateur de Firefox qui a besoin d'accéder au contenu doit le faire explicitement via un passage de messages.</p> + +<p>Nous pensons que ce compromis est logique pour plusieurs raisons :</p> + +<ul> + <li>L'accès au DOM par le code de Firefox n'est pas commun.</li> + <li>Le code partagé avec Firefox OS utilise déjà le passage de messages.</li> + <li>Dans le modèle multi-processus, <span id="result_box" lang="fr"><span>le code</span> <span class="hps">de Firefox qui</span> <span class="hps">ne parvient pas à</span> <span class="hps">utiliser</span> <span class="hps">le passage de messages</span> afin d'accéder <span class="hps">au contenu</span> <span class="hps">va échouer</span> <span class="hps">de</span> <span class="hps">manière </span><span class="hps">évidente. Dans un modèle</span> multi-threads, le <span class="hps">code qui accède à</span> <span class="hps">du contenu sans</span> <span class="hps">blocage</span> <span class="hps">correct</span> <span class="hps">échouera</span> <span class="hps">de façon subtile</span>, et sera bien plus difficile <span class="hps">à déboguer.</span></span></li> +</ul> + +<h2 id="Sécurité">Sécurité</h2> + +<p>Aujourd'hui, si quelqu'un découvre un bug exploitable dans Firefox, il est capable de prendre le contrôle des ordinateurs des utilisateurs. Il existe des solutions pour parer ce problème, la plus connue est la technique du <a href="http://fr.wikipedia.org/wiki/Sandbox_%28s%C3%A9curit%C3%A9_informatique%29">bac à sable</a>. Cette méthode ne nécessite pas une architecture multi-processus. Cependant, un bac à sable fonctionnant avec Firefox en processus unique ne serait pas très efficace. Les bacs à sable permettent seulement d'empêcher à un processus d'effectuer des actions qu'il ne devrait pas réaliser. Malheureusement, Firefox nécessite l'accès à bien plus de choses que le réseau et le système de fichiers (surtout lorsque des extensions sont installées). Ainsi, un bac à sable pour Firefox en processus unique ne pourrait pas bloquer grand-chose.</p> + +<p>Avec Firefox multi-processus, les processus contenus seront mis dans un bac à sable. Ainsi, un processus ne pourra pas accéder directement au système de fichiers, mais devra demander au processus principal. À ce moment, le processus principal pourra vérifier que la requête est sécurisée et logique. Par conséquent, le bac à sable peut être très restrictif. On espère que cette méthode rende plus difficile l'exploitation de trous de sécurité dans Firefox.</p> + +<h2 id="Stabilité">Stabilité</h2> + +<p>Actuellement, un plantage dans un code s'exécutant dans une page va faire tomber le navigateur en entier. Avec Firefox multi-processus, seul le processus du contenu qui s'est planté sera détruit.</p> + +<div class="note"> +<p>Cette page contient pas mal de contenu provenant de l'article de blog de Bill McCloskey's sur Firefox multi-processus : <a href="http://billmccloskey.wordpress.com/2013/12/05/multiprocess-firefox/">http://billmccloskey.wordpress.com/2013/12/05/multiprocess-firefox/</a> (en)</p> +</div> + +<p> </p> diff --git a/files/fr/mozilla/firefox/multiprocessus_firefox/technical_overview/index.html b/files/fr/mozilla/firefox/multiprocessus_firefox/technical_overview/index.html new file mode 100644 index 0000000000..92839d64e2 --- /dev/null +++ b/files/fr/mozilla/firefox/multiprocessus_firefox/technical_overview/index.html @@ -0,0 +1,164 @@ +--- +title: Vue d'ensemble technique +slug: Mozilla/Firefox/Multiprocessus_Firefox/Technical_overview +translation_of: Mozilla/Firefox/Multiprocess_Firefox/Technical_overview +--- +<div>{{FirefoxSidebar}}</div><div class="note"> +<p>Cette page est une traduction d'un extrait de l'article du blog Bill McCloskey sur Firefox et le multiprocessus: <a class="external external-icon" href="http://billmccloskey.wordpress.com/2013/12/05/multiprocess-firefox/">http://billmccloskey.wordpress.com/2013/12/05/multiprocess-firefox/</a></p> +</div> + +<p>À un haut niveau, le Firefox en multiprocessus fonctionne comme il suit. Le processus qui est démarré quand Firefox est lancé est appelé le processus parent. Initialement, ce processus fonctionne de la même manière que Firefox lorsqu'il fonctionne avec un seul processus: une fenêtre est ouverte affichant <a href="http://mxr.mozilla.org/mozilla-central/source/browser/base/content/browser.xul"><code>browser.xul</code></a>, qui contient les éléments principaux de l'UI pour Firefox. Firefox a un toolkit pour les GUI appelé XUL qui permet aux éléments de l'interface d'être déclaré et posés de façon déclarative, de la même manière que pour du contenu web. Tout comme le contenu web, il y a un objet <code>window</code>, qui a une propriété <code>document</code>, et celui-ci contient tout les éléments XML déclarés dans <code>browser.xul</code>. Tous les menus, barres d'outils, sidebars, et onglets dans Firefox sont des éléments XML dans ce document. Chaque onglet contient un élément <code><browser></code> pour afficher le contenu web.</p> + +<p>Le premier endroit où Firefox multiprocessus diverge de sa version en monoprocessus est que chaque élément <code><browser></code> a un attribut <code>remote="true"</code>. Quand un tel élément est ajouté au document, un nouveau processus, appelé <em>child process</em>, pour le contenu est lancé. Un canal IPC est créé qui relie processus parent et enfant. Initialement l'enfant affiche <code>about:blank</code>, mais le parent peut envoyer des commandes à l'enfant pour naviguer autre part.</p> + +<h2 id="Rendu"><strong id="drawing">Rendu</strong></h2> + +<p>D'une certaine manière, le contenu Web affiché doit passer du processus enfant au parent, puis à l'écran. Le multiprocessus de Firefox dépend d'une nouvelle fonctionnalité appelée <a href="http://benoitgirard.wordpress.com/2012/05/15/off-main-thread-compositing-omtc-and-why-it-matters/"><em>off main thread compositing</em></a> (OMTC). En bref chaque fenêtre de Firefox est divisée en séries de couches, en quelque sorte similaire au calque de Photoshop. Chaque fois que Firefox effectue un rendue de la page Web, ces couches sont soumises à un thread de composition qui traduit les couches et les associent en semble pour former la page qui est ensuite dessinée.</p> + +<p>Les calques sont structurés comme un arbre. La couche racine de l'arbre est responsable de l'ensemble de la fenêtre de Firefox. Cette couche contient d'autres couches, dont certaines sont responsables de l'élaboration des menus et des onglets. Une sous-couche affiche tout le contenu Web. Le contenu Web lui-même peut être divisé en plusieurs couches, mais ils sont tous enracinés dans une seule couche "content".</p> + +<p>Dans le multiprocessus de Firefox, la sous-couche de la couche de contenu est en fait une cale. La plupart du temps, il contient un noeud de substitution qui conserve simplement une référence à la liaison IPC avec le processus enfant. Le processus de contenu conserve l'arborescence de couches réelle pour le contenu Web. Il construit et dessine dans cet arbre de couche. Lorsqu'il a terminé, il envoie la structure de son arbre de couche au processus parent via IPC. Les tampons de sauvegarde sont partagés avec le parent soit par la mémoire partagée, soit par la mémoire GPU. Les références à cette mémoire sont envoyées dans une parti de l'arborescence de l'arbre. Lorsque le parent reçoit l'arborescence de la couche, il supprime son nœud de substitution et le remplace par l'arbre réel du contenu. Ensuite, il compose et dessine comme d'habitude. Lorsqu'il a terminé, il remet sont nœud de substitution.</p> + +<p>L'architecture de base de la façon dont OMTC fonctionne avec plusieurs processus existe depuis un certain temps, car il est nécessaire pour Firefox OS. Cependant, Matt Woodrow et David Anderson travail beaucoup pour que tout fonctionne correctement sur Windows, Mac et Linux. L'un des grands défis pour le multiprocessus de Firefox serait d'utiliser OMTC sur toutes les plates-formes. À l'heure actuelle, seuls la plate-forme Mac l'utilisent par défaut.</p> + +<h2 id="User_input"><strong id="input">User input</strong></h2> + +<p>Events in Firefox work the same way as they do on the web. Namely, there is a DOM tree for the entire window, and events are threaded through this tree in capture and bubbling phases. Imagine that the user clicks on a button on a web page. In single-process Firefox, the root DOM node of the Firefox window gets the first chance to process the event. Then, nodes lower down in the DOM tree get a chance. The event handling proceeds down through to the XUL <code><browser></code> element. At this point, nodes in the web page’s DOM tree are given a chance to handle the event, all the way down to the button. The bubble phase follows, running in the opposite order, all the way back up to the root node of the Firefox window.</p> + +<p>With multiple processes, event handling works the same way until the <code><browser></code> element is hit. At that point, if the event hasn’t been handled yet, it gets sent to the child process by IPC, where handling starts at the root of the content DOM tree. The parent process then waits to run its bubbling phase until the content process has finished handling the event.</p> + +<h2 id="Inter-process_communication"><strong id="ipc">Inter-process communication</strong></h2> + +<p>All IPC happens using the Chromium IPC libraries. Each child process has its own separate IPC link with the parent. Children cannot communicate directly with each other. To prevent deadlocks and to ensure responsiveness, the parent process is not allowed to sit around waiting for messages from the child. However, the child is allowed to block on messages from the parent.</p> + +<p>Rather than directly sending packets of data over IPC as one might expect, we use code generation to make the process much nicer. The IPC protocol is defined in <a href="https://wiki.mozilla.org/IPDL">IPDL</a>, which sort of stands for “inter-* protocol definition language”. A typical IPDL file is <code><a href="http://mxr.mozilla.org/mozilla-central/source/netwerk/ipc/PNecko.ipdl">PNecko.ipdl</a></code>. It defines a set messages and their parameters. Parameters are serialized and included in the message. To send a message <code>M</code>, C++ code just needs to call the method <code>SendM</code>. To receive the message, it implements the method <code>RecvM</code>.</p> + +<p>IPDL is used in all the low-level C++ parts of Gecko where IPC is required. In many cases, IPC is just used to forward actions from the child to the parent. This is a common pattern in Gecko:</p> + +<pre class="brush: cpp">void AddHistoryEntry(param) { + if (XRE_GetProcessType() == GeckoProcessType_Content) { + // If we're in the child, ask the parent to do this for us. + SendAddHistoryEntry(param); + return; + } + + // Actually add the history entry... +} + +bool RecvAddHistoryEntry(param) { + // Got a message from the child. Do the work for it. + AddHistoryEntry(param); + return true; +} +</pre> + +<p>When <code>AddHistoryEntry</code> is called in the child, we detect that we’re inside the child process and send an IPC message to the parent. When the parent receives that message, it calls <code>AddHistoryEntry</code> on its side.</p> + +<p>For a more realistic illustration, consider the Places database, which stores visited URLs for populating the awesome bar. Whenever the user visits a URL in the content process, we call <a href="http://mxr.mozilla.org/mozilla-central/source/toolkit/components/places/History.cpp?rev=8b9687f6c602#2326">this code</a>. Notice the content process check followed by the <code>SendVisitURI</code> call and an immediate return. The message is received <a href="http://mxr.mozilla.org/mozilla-central/source/dom/ipc/ContentParent.cpp?rev=fecda5f4a0df#2666">here</a>; this code just calls <code>VisitURI</code> in the parent.</p> + +<p>The code for IndexedDB, the places database, and HTTP connections all runs in the parent process, and they all use roughly the same proxying mechanism in the child.</p> + +<h2 id="Frame_scripts"><strong id="contentscripts">Frame scripts</strong></h2> + +<p>IPDL takes care of passing messages in C++, but much of Firefox is actually written in JavaScript. Instead of using IPDL directly, JavaScript code relies on <a href="/en-US/Firefox/Multiprocess_Firefox/The_message_manager">the message manager</a> to communicate between processes. To use the message manager in JS, you need to get hold of a message manager object. There is a global message manager, message managers for each Firefox window, and message managers for each <code><browser></code> element. A message manager can be used to load JS code into the child process and to exchange messages with it.</p> + +<p>As a simple example, imagine that we want to be informed every time a <code>load</code> event triggers in web content. We’re not interested in any particular browser or window, so we use the global message manager. The basic process is as follows:</p> + +<pre class="brush: js">// Get the global message manager. +let mm = Cc["@<span class="skimlinks-unlinked">mozilla.org/globalmessagemanager;1</span>"]. + getService(Ci.nsIMessageListenerManager); + +// Wait for load event. +mm.addMessageListener("GotLoadEvent", function (msg) { + dump("Received load event: " + <span class="skimlinks-unlinked">msg.data.url</span> + "\n"); +}); + +// Load code into the child process to listen for the event. +mm.loadFrameScript("chrome://content/<span class="skimlinks-unlinked">content-script.js</span>", true); +</pre> + +<p>For this to work, we also need to have a file <code>content-script.js</code>:</p> + +<pre class="brush: js">// Listen for the load event. +addEventListener("load", function (e) { + // Inform the parent process. + let docURL = content.document.documentURI; + sendAsyncMessage("GotLoadEvent", {url: docURL}); +}, false); +</pre> + +<p>This file is called a <em>frame script</em>. When the <code>loadFrameScript</code> function call runs, the code for the script is run once for each <code><browser></code> element. This includes both remote browsers and regular ones. If we had used a per-window message manager, the code would only be run for the browser elements in that window. Any time a new browser element is added, the script is run automatically (this is the purpose of the <code>true</code> parameter to <code>loadFrameScript</code>). Since the script is run once per browser, it can access the browser’s window object and docshell via the <code>content</code> and <code>docShell</code> globals.</p> + +<p>The great thing about frame scripts is that they work in both single-process and multiprocess Firefox. To learn more about the message manager, see the <a href="/en-US/Firefox/Multiprocess_Firefox/The_message_manager">message manager guide</a>.</p> + +<h2 id="Cross-process_APIs"><strong id="shims">Cross-process APIs</strong></h2> + +<p>There are a lot of APIs in Firefox that cross between the parent and child processes. An example is the <code>webNavigation</code> property of XUL <code><browser></code> elements. The <code>webNavigation</code> property is an object that provides methods like <code>loadURI</code>, <code>goBack</code>, and <code>goForward</code>. These methods are called in the parent process, but the actions need to happen in the child. First I’ll cover how these methods work in single-process Firefox, and then I’ll describe how we adapted them for multiple processes.</p> + +<p>The <code>webNavigation</code> property is defined using the XML Binding Language (XBL). XBL is a declarative language for customizing how XML elements work. Its syntax is a combination of XML and JavaScript. Firefox uses XBL extensively to customize XUL elements like <code><browser></code> and <code><tabbrowser></code>. The <code><browser></code> customizations reside in <code><a href="http://mxr.mozilla.org/mozilla-central/source/toolkit/content/widgets/browser.xml?rev=754cf7fc84cd">browser.xml</a></code>. <a href="http://mxr.mozilla.org/mozilla-central/source/toolkit/content/widgets/browser.xml?rev=754cf7fc84cd#262">Here</a> is how <code>browser.webNavigation</code> is defined:</p> + +<pre class="brush: xml"><field name="_webNavigation">null</field> + +<property name="webNavigation" readonly="true"> + <getter> + <![CDATA[ + if (!this._webNavigation) + this._webNavigation = this.docShell.QueryInterface(Components.interfaces.nsIWebNavigation); + return this._webNavigation; + ]]> + </getter> +</property> +</pre> + +<p>This code is invoked whenever JavaScript code in Firefox accesses <code>browser.webNavigation</code>, where <code>browser</code> is some <code><browser></code> element. It checks if the result has already been cached in the <code>browser._webNavigation</code> field. If it hasn’t been cached, then it fetches the navigation object based off the browser’s <em>docshell</em>. The docshell is a Firefox-specific object that encapsulates a lot of functionality for loading new pages, navigating back and forth, and saving page history. In multiprocess Firefox, the docshell lives in the child process. Since the <code>webNavigation</code> accessor runs in the parent process, <code>this.docShell</code> above will just return null. As a consequence, this code will fail completely.</p> + +<p>One way to fix this problem would be to create a fake docshell in C++ that could be returned. It would operate by sending IPDL messages to the real docshell in the child to get work done. We may eventually take this route in the future. We decided to do the message passing in JavaScript instead, since it’s easier and faster to prototype things there. Rather than change every docshell-using accessor to test if we’re using multiprocess browsing, we decided to create a new XBL binding that applies only to remote <code><browser></code> elements. It is called <a href="http://mxr.mozilla.org/mozilla-central/source/toolkit/content/widgets/remote-browser.xml?rev=9583bd3099ae"><code>remote-browser.xml</code></a>, and it extends the existing <code>browser.xml</code> binding.</p> + +<p>The <code>remote-browser.xml</code> binding returns a JavaScript <em>shim object</em> whenever anyone uses <code>browser.webNavigation</code> or other similar objects. The shim object is implemented <a href="http://mxr.mozilla.org/mozilla-central/source/toolkit/modules/RemoteWebNavigation.jsm">in its own JavaScript module</a>. It uses the message manager to send messages like <code>"WebNavigation:LoadURI"</code> to <a href="http://mxr.mozilla.org/mozilla-central/source/toolkit/content/browser-child.js?rev=9583bd3099ae#107">a content script loaded by <code>remote-browser.xml</code></a>. The content script performs the actual action.</p> + +<p>The shims we provide emulate their real counterparts imperfectly. They offer enough functionality to make Firefox work, but add-ons that use them may find them insufficient. I’ll discuss strategies for making add-ons work in more detail later.</p> + +<h2 id="Cross-process_object_wrappers"><strong id="cpows">Cross-process object wrappers</strong></h2> + +<p>The message manager API does not allow the parent process to call <code>sendSyncMessage</code>; that is, the parent is not allowed to wait for a response from the child. It’s detrimental for the parent to wait on the child, since we don’t want the browser UI to be unresponsive because of slow content. However, converting Firefox code to be asynchronous (i.e., to use <code>sendAsyncMessage</code> instead) can sometimes be onerous. As an expedient, we’ve introduced a new primitive that allows code in the parent process to access objects in the child process synchronously.</p> + +<p>These objects are called cross-process object wrappers, frequently abbreviated to CPOWs. They’re created using the message manager. Consider this example content script:</p> + +<pre class="brush: js">addEventListener("load", function (e) { + let doc = content.document; + sendAsyncMessage("GotLoadEvent", <strong>{}, {document: doc}</strong>); +}, false); +</pre> + +<p>In this code, we want to be able to send a reference to the document to the parent process. We can’t use the second parameter to <code>sendAsyncMessage</code> to do this: that argument is converted to JSON before it is sent up. The optional third parameter allows us to send object references. Each property of this argument becomes accessible in the parent process as a CPOW. Here’s what the parent code might look like:</p> + +<pre class="brush: js">let mm = Cc["@<span class="skimlinks-unlinked">mozilla.org/globalmessagemanager;1</span>"]. + getService(Ci.nsIMessageListenerManager); + +mm.addMessageListener("GotLoadEvent", function (msg) { + let uri = <strong>msg.objects.document.documentURI</strong>; + dump("Received load event: " + uri + "\n"); +}); +mm.loadFrameScript("chrome://content/<span class="skimlinks-unlinked">content-script.js</span>", true); +</pre> + +<p>It’s important to realize that we’re send object <em>references</em>. The <code>msg.objects.document</code> object is only a wrapper. The access to its <code>documentURI</code> property sends a synchronous message down to the child asking for the value. The dump statement only happens after a reply has come back from the child.</p> + +<p>Because every property access sends a message, CPOWs can be slow to use. There is no caching, so 1,000 accesses to the same property will send 1,000 messages.</p> + +<p>Another problem with CPOWs is that they violate some assumptions people might have about message ordering. Consider this code:</p> + +<pre class="brush: js">mm.addMessageListener("GotLoadEvent", function (msg) { + mm.sendAsyncMessage("ChangeDocumentURI", {newURI: "<span class="skimlinks-unlinked">hello.com</span>"}); + let uri = <strong>msg.objects.document.documentURI</strong>; + dump("Received load event: " + uri + "\n"); +}); +</pre> + +<p>This code sends a message asking the child to change the current document URI. Then it accesses the current document URI via a CPOW. You might expect the value of <code>uri</code> to come back as <code>"hello.com"</code>. But it might not. In order to avoid deadlocks, CPOW messages can bypass normal messages and be processed first. It’s possible that the request for the <code>documentURI</code> property will be processed before the <code>"ChangeDocumentURI"</code> message, in which case <code>uri</code> will have some other value.</p> + +<p>For this reason, it’s best not to mix CPOWs with normal message manager messages. It’s also a bad idea to use CPOWs for anything security-related, since you may not get results that are consistent with surrounding code that might use the message manager.</p> + +<p>Despite these problems, we’ve found CPOWs to be useful for converting certain parts of Firefox to be multiprocess-compatible. It’s best to use them in cases where users are less likely to notice poor responsiveness. As an example, we use CPOWs to implement the context menu that pops up when users right-click on content elements. Whether this code is asynchronous or synchronous, the menu cannot be displayed until content has responded with data about the element that has been clicked. The user is unlikely to notice if, for example, tab animations don’t run while waiting for the menu to pop up. Their only concern is for the menu to come up as quickly as possible, which is entirely gated on the response time of the content process. For this reason, we chose to use CPOWs, since they’re easier than converting the code to be asynchronous.</p> + +<p>It’s possible that CPOWs will be phased out in the future. Asynchronous messaging using the message manager gives a user experience that is at least as good as, and often strictly better than, CPOWs. We strongly recommend that people use the message manager over CPOWs when possible. Nevertheless, CPOWs are sometimes useful.</p> |
