aboutsummaryrefslogtreecommitdiff
path: root/files/de/mozilla/firefox/multiprocess_firefox/technical_overview/index.html
blob: f0de7c545775e747020ee5c0aef3c53926c097fc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
---
title: Technical overview
slug: Mozilla/Firefox/Multiprocess_Firefox/Technical_overview
translation_of: Mozilla/Firefox/Multiprocess_Firefox/Technical_overview
---
<div>{{FirefoxSidebar}}</div><div class="note">
<p>Diese Seite ist ein bearbeiteter Auszug aus Bill McCloskey's Blogbeitrag Multiprozess Firefox:(engl) <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><span id="result_box" lang="de"><span class="hps">Auf einer sehr hohen</span> <span class="hps">Ebene arbeitet</span> <span class="hps">Multiprozess</span> <span class="hps">Firefox</span> <span class="hps">wie folgt.</span> <span class="hps">Der</span> <span class="alt-edited hps">Prozess, der</span> <span class="hps">beginnt, wenn</span> <span class="hps">Firefox</span> <span class="hps">startet</span><span class="alt-edited"> wird </span><span class="alt-edited hps">übergeordneter Prozess</span><span> genannt.</span> <span class="hps">Zunächst</span> <span class="hps">arbeitet</span> <span class="hps">dieses Verfahren</span> <span class="hps">ähnlich wie</span> <span class="alt-edited hps">Einzelprozess.</span> <span class="alt-edited hps">Es</span> <span class="alt-edited hps">wird ein Firefox Fenster geöffnet</span><span class="alt-edited">, das</span> <span class="hps">alle wichtigen</span> <span class="hps">Elemente der Benutzeroberfläche für</span> <span class="hps">Firefox</span> <span class="hps">enthält</span> (</span> <span id="result_box" lang="de"><span class="hps">browser.xul</span></span><span lang="de">)<span class="alt-edited hps">.</span> <span class="hps">Firefox</span> <span class="hps">hat eine flexibles</span> <span class="hps">GUI</span>-<span class="hps">Toolkit namens</span> <span class="hps">XUL</span><span>, das </span><span class="hps">GUI-Elemente</span> <span class="hps">deklarativ</span> <span class="hps">beschreibt</span><span>, ähnlich wie bei</span> <span class="hps">Web-Content</span><span>.</span> <span class="hps">Genau wie</span> <span class="hps">Web-Inhalte</span><span>, hat das</span> <span class="hps">Firefox</span><span>-UI</span> <span class="hps">ein Fenster</span><span>-Objekt, das</span> <span class="hps">eine Dokumenteigenschaft</span> <span class="hps">hat</span><span>,</span> <span class="hps">und dieses Dokument</span> <span class="hps">enthält alle</span> <span class="hps">XML-Elemente</span> <span class="hps">aus</span> der <span class="hps">browser.xul</span><span>.</span> <span class="hps">Alle</span> <span class="hps">Firefox</span> <span class="hps">Menüs, Symbolleisten</span><span>, Seitenleisten</span> <span class="hps">und</span> <span class="hps">Tabs sind</span> <span class="hps">XML-Elemente</span> <span class="hps">in diesem Dokument.</span> <span class="hps">Jedes Register</span> <span class="hps">enthält ein</span> <span class="hps">&lt;browser</span><span>&gt; -Element</span> um<span class="hps"> Web-Inhalte</span> <span class="hps">anzuzeigen.</span><br>
 <br>
 <span class="hps">Die erste Stelle</span><span class="hps">, an der</span> <span class="hps">Multiprozess</span> <span class="hps">Firefox</span> <span class="alt-edited hps">von</span> <span class="atn hps">Single-</span><span class="alt-edited">Prozess</span> <span class="hps">Firefox</span> <span class="alt-edited hps">abweicht ist, dass jedes</span> <span class="hps">&lt;browser</span><span>&gt;</span> <span class="alt-edited hps">Element ein</span> <span class="alt-edited hps">remote</span> <span class="hps">= "true"</span> <span class="alt-edited hps">-Attribut besitzt.</span> <span class="alt-edited hps">Wenn ein solches</span> <span class="hps">Browser</span> <span class="hps">Element</span> <span class="hps">in das Dokument eingefügt</span> <span class="alt-edited hps">wird, wird ein</span> <span class="alt-edited hps">neuer Inhaltsprozess</span> <span class="hps">gestartet</span><span>.</span> <span class="hps">Dieser Prozess wird als</span> <span class="alt-edited hps">ein untergeordneter Prozess bezeichnet</span><span>.</span> <span class="hps">Ein</span> <span class="hps">IPC-Kanal</span> <span class="alt-edited hps">erstellt, der die</span> <span class="atn hps">Eltern-Kind-</span><span class="alt-edited">Prozesse</span> <span class="hps">verknüpft</span><span>.</span> <span class="hps">Anfangs zeigt</span> <span class="hps">das Kind</span> <span class="hps">about: blank</span><span>, aber die</span> <span class="hps">Eltern können</span> <span class="hps">dem Kind einen</span> <span class="hps">Befehl</span> <span class="hps">senden, um</span> <span class="hps">an anderer Stelle</span> <span class="hps">zu navigieren.</span></span></p>

<h2 id="Zeichnen"><span class="short_text" id="result_box" lang="de"><span class="alt-edited hps">Zeichnen</span></span></h2>

<p>Somehow, displayed web content needs to get from the child process to the parent and then to the screen. Multiprocess Firefox depends on a new Firefox feature called <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). In brief, each Firefox window is broken into a series of <em>layers</em>, somewhat similar to layers in Photoshop. Each time Firefox draws, these layers are submitted to a compositor thread that clips and translates the layers and combines them together into a single image that is then drawn.</p>

<p>Layers are structured as a tree. The root layer of the tree is responsible for the entire Firefox window. This layer contains other layers, some of which are responsible for drawing the menus and tabs. One subtree displays all the web content. Web content itself may be broken into multiple layers, but they’re all rooted at a single “content” layer.</p>

<p>In multiprocess Firefox, the content layer subtree is actually a shim. Most of the time, it contains a placeholder node that simply keeps a reference to the IPC link with the child process. The content process retains the actual layer tree for web content. It builds and draws to this layer tree. When it’s done, it sends the structure of its layer tree to the parent process via IPC. Backing buffers are shared with the parent either through shared memory or GPU memory. References to this memory are sent as part of the layer tree. When the parent receives the layer tree, it removes its placeholder content node and replaces it with the actual tree from content. Then it composites and draws as normal. When it’s done, it puts the placeholder back.</p>

<p>The basic architecture of how OMTC works with multiple processes has existed for some time, since it is needed for Firefox OS. However, Matt Woodrow and David Anderson have done a lot of work to get everything working properly on Windows, Mac, and Linux. One of the big challenges for multiprocess Firefox will be getting OMTC enabled on all platforms. Right now, only Macs use it by default.</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>&lt;browser&gt;</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>&lt;browser&gt;</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>&lt;browser&gt;</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>&lt;browser&gt;</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>&lt;browser&gt;</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>&lt;browser&gt;</code> and <code>&lt;tabbrowser&gt;</code>. The <code>&lt;browser&gt;</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">&lt;field name="_webNavigation"&gt;null&lt;/field&gt;

&lt;property name="webNavigation" readonly="true"&gt;
   &lt;getter&gt;
   &lt;![CDATA[
     if (!this._webNavigation)
       this._webNavigation = this.docShell.QueryInterface(Components.interfaces.nsIWebNavigation);
     return this._webNavigation;
   ]]&gt;
   &lt;/getter&gt;
&lt;/property&gt;
</pre>

<p>This code is invoked whenever JavaScript code in Firefox accesses <code>browser.webNavigation</code>, where <code>browser</code> is some <code>&lt;browser&gt;</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>&lt;browser&gt;</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>