Multiprocess Firefox

Since this January, David Anderson and I have been working on making Firefox use multiple processes. Tom Schuster (evilpie on IRC) joined us as an intern this summer, and Felipe Gomes, Mark Hammond and other have made great contributions. Now we’re interested to hear what people think of the work so far.

Firefox has always used a single process. When Chrome was released, it used one UI process and separate “content” processes to host web content. Other browsers have adopted similar strategies since then. (Correction: Apparently IE 8 used multiple processes 6 months before Chrome was released.) Around that time, Mozilla launched a far-reaching effort, called Electrolysis, to rewrite Firefox and the Gecko engine to use multiple processes. Much of this work bore fruit. Firefox OS relies heavily on the multiprocessing and IPC code introduced during Electrolysis. However, the main effort of porting the desktop Firefox browser to use multiple processes was put on hold in November 2011 so that shorter-term work to increase responsiveness could proceed more quickly. A good summary of that decision is in this thread.

This blog entry covers some of the technical issues involved in multiprocess Firefox. More information is available on the project wiki page, which includes links to tracking bugs and mailing lists. Tom Schuster’s internship talk gives a great overview of his work.

Why are we doing this?

There are a couple reasons for using multiple processes.

Performance. Most performance work at Mozilla over the last two years has focused on responsiveness of the browser. The goal is to reduce “jank”—those times when the browser seems to briefly freeze when loading a big page, typing in a form, or scrolling. Responsiveness tends to matter a lot more than throughput on the web today. Much of this work has been done as part of the Snappy project. The main focuses have been:

  • Moving long-running actions to a separate thread so that the main thread can continue to respond to the user.
  • Doing I/O asynchronously or on other threads so that the main thread isn’t blocked waiting for the disk.
  • Breaking long-running code into shorter pieces and running the event loop in between. Incremental garbage collection is an example of this.

Much of the low-hanging fruit in these areas has already been picked. The remaining issues are difficult to fix. For example, JavaScript execution and layout happen on the main thread, and they block the event loop. Running these components on a separate thread is difficult because they access data, like the DOM, that are not thread-safe. As an alternative, we’ve considered allowing the event loop to run in the middle of JavaScript execution, but doing so would break a lot of assumptions made by other parts of Firefox (not to mention add-ons).

Running web content in a separate process is a nice alternative to these approaches. Like the threaded approach, Firefox is able to run its event loop while JavaScript and layout are running in a content process. But unlike threading, the UI code has no access to content DOM or or other content data structures, so there is no need for locking or thread-safety. The downside, of course, is that any code in the Firefox UI process that needs to access content data must do so explicitly through message passing.

We feel this tradeoff makes sense for a few reasons:

  • It’s not all that common for Firefox code to access content DOM.
  • Code that is shared with Firefox OS already uses message passing.
  • In the multiprocess model, Firefox code that fails to use message passing to access content will fail in an obvious, consistent way. In the threaded model, code that accesses content without proper locking will fail in subtle ways that are difficult to debug.

The last point is, in my opinion, the most compelling. None of the approaches for improving responsiveness of JavaScript code are easy to implement. The multiprocess approach at least has the advantage that errors are reproducible.

Security. Right now, if someone discovers an exploitable bug in Firefox, they’re able to take over users’ computers. There are a lot of techniques to mitigate this problem, but one of the most powerful is sandboxing. Technically, sandboxing doesn’t require multiple processes. However, a sandbox that covered the current (single) Firefox process wouldn’t be very useful. Sandboxes are only able to prevent processes from performing actions that a well-behaved process would never do. Unfortunately, a well-behaved Firefox process (especially one with add-ons installed) needs access to much of the network and file system. Consequently, a sandbox for single-process Firefox couldn’t restrict much.

In multiprocess Firefox, content processes will be sandboxed. A well-behaved content process won’t access the filesystem directly; it will have to ask the main process to perform the request. At that time, the main process can verify that the request is safe and that it makes sense. Consequently, the sandbox for content processes can be quite restrictive. Our hope is that this arrangement will make it much harder to craft exploitable security holes for Firefox.

Stability. Although Firefox usually doesn’t crash very often, multiple processes will make crashes much less annoying. Rather than killing the entire browser, the crash will only take down the content process that crashed.

Trying it out

We’ve been doing all of our development on mozilla-central, which means that all of our code is available in Firefox nightlies. You can try out multiprocess Firefox with the following steps:

  • Update to a current nightly.
  • We strongly recommend you create a new profile!
  • Set the browser.tabs.remote preference to true.
  • Restart Firefox.

Here’s what Firefox looks like in multiprocess mode. Notice that the title of the tab is underlined. This means that the content for the tab is being rendered in a separate process.

e10s-1

This screenshot shows what happens when a content process crashes. The main Firefox process remains alive, along with any tabs being rendered in other processes.

e10s-2

What works?

The best way to find out is to try it! All basic browsing functionality should work at this time. In particular: back and forward navigation, the URL bar and search bar, the context menu, bookmarks, tabs, Flash (except on Macs), the findbar, and even Adblock Plus. Some less-used features are still missing: developer tools, printing, and saving pages to disk. Add-on support is pretty spotty—some add-ons work, but most don’t.

To be conservative, we’re using only one content process for all web content. To get all of the performance, security, and stability benefits of multiple processes, we’ll need to use multiple content processes.

When will it be released?

We simply don’t know. It’s a large project and any predictions at this point would be foolhardy. There are a lot of concerns about compatibility with add-ons and plugins that will need to be settled before we have any sense of a release date.

How much memory will it use?

Many people who hear about the project are concerned that multiple processes will cause the memory usage of Firefox to balloon. There’s a fairly widespread belief that more processes will require a lot more memory. I don’t believe this will be the case though.

The actual overhead of an empty process is very small. A normal desktop system can easily support thousands of processes. Even when all the code for Firefox is loaded into each process, this memory is shared, so not much more memory is used than in the single-process case.

Using multiple processes does take more memory if data that could be shared between tabs in a single process must be duplicated in a multiprocess browser. There are many different caches and other shared data structures in Firefox where this might be a problem. We have plans to mitigate these issues. For example, it should be possible for processes to store some of this data in read-only shared memory. A process would be able to observe that it is holding local data that is identical to some data already stored in the read-only cache. In that case, it would drop its own copy of the data and use the shared version instead. Periodically, processes could add new data to the cache and mark it read-only. Processes would want to avoid sharing data that might include sensitive information like credit card numbers or bank data. However, it would probably be safe to share image data, JavaScript scripts, and the like.

Admittedly, it will take some time to optimize the memory usage of multiprocess Firefox. We already have about:memory working for multiple processes, which should make the work go more quickly. Nevertheless, until we feel that multiprocess Firefox is competitive with the single-process version, we will probably use a single content process to render all tabs. That model brings most of the security benefits and some of the performance and stability advantages we’re seeking. It also takes only a small amount more memory than single-process Firefox. I’ve taken some simple measurements using about:memory and Gregor Wagner’s MemBench benchmark. The benchmark opens 50 tabs and then measures memory usage.

Memory usage Single-process Multiprocess
UI process 974 MB 124 MB
Content process 860 MB
Total 974 MB 984 MB

The total memory usage for multiprocess Firefox is only 10MB greater than single-process Firefox. We should be able to shrink this difference with some effort.

How will it work?

At a very high level, multiprocess Firefox works as follows. The process that starts up when Firefox launches is called the parent process. Initially, this process works similarly to single-process Firefox: it opens a window displaying browser.xul, which contains all the principal UI elements for Firefox. Firefox has a flexible GUI toolkit called XUL that allows GUI elements to be declared and laid out declaratively, similar to web content. Just like web content, the Firefox UI has a window object, which has a document property, and this document contains all the XML elements from browser.xul. All the Firefox menus, toolbars, sidebars, and tabs are XML elements in this document. Each tab element contains a <browser> element to display web content.

The first place where multiprocess Firefox diverges from single-process Firefox is that each <browser> element has a remote="true" attribute. When such a browser element is added to the document, a new content process is started. This process is called a child process. An IPC channel is created that links the parent and child processes. Initially, the child displays about:blank, but the parent can send the child a command to navigate elsewhere.

In the remainder of this section, I will discuss the most important aspects of multiprocess Firefox.

Drawing. 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 off main thread compositing (OMTC). In brief, each Firefox window is broken into a series of layers, 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.

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.

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.

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.

User input. 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 <browser> 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.

With multiple processes, event handling works the same way until the <browser> 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. There are still some problems with how this works. Ideally, the parent process would wait to run its bubbling phase until the content process had finished handling the event. Right now, though, the two happen simultaneously, which can cause problems if the content process called stopPropagation() on the event. Bug 862519 covers this problem. The usual manifestation is that hitting a key combination like Cmd-Left on a Mac will go back even if a textbox is in focus.

Inter-process communication. 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.

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 IPDL, which sort of stands for “inter-* protocol definition language”. A typical IPDL file is PNecko.ipdl. It defines a set messages and their parameters. Parameters are serialized and included in the message. To send a message M, C++ code just needs to call the method SendM. To receive the message, it implements the method RecvM.

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:

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;
}

When AddHistoryEntry 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 AddHistoryEntry on its side.

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 this code. Notice the content process check followed by the SendVisitURI call and an immediate return. The message is received here; this code just calls VisitURI in the parent.

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.

Content scripts. 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 the message manager 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 <browser> element. A message manager can be used to load JS code into the child process and to exchange messages with it.

As a simple example, imagine that we want to be informed every time a load 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:

// Get the global message manager.
let mm = Cc["@mozilla.org/globalmessagemanager;1"].
         getService(Ci.nsIMessageListenerManager);

// Wait for load event.
mm.addMessageListener("GotLoadEvent", function (msg) {
  dump("Received load event: " + msg.data.url + "\n");
});

// Load code into the child process to listen for the event.
mm.loadFrameScript("chrome://content/content-script.js", true);

For this to work, we also need to have a file content-script.js:

// Listen for the load event.
addEventListener("load", function (e) {
  // Inform the parent process.
  let docURL = content.document.documentURI;
  sendAsyncMessage("GotLoadEvent", {url: docURL});
}, false);

This file is called a content script. When the loadFrameScript function call runs, the code for the script is run once for each <browser> 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 true parameter to loadFrameScript). Since the script is run once per browser, it can access the browser’s window object and docshell via the content and docShell globals.

The great thing about content scripts is that they work in both single-process and multiprocess Firefox. So if you write your code using the message manager now, it will be forward-compatible with multiprocessing. Tim Taubert wrote a more comprehensive look at the message manager in his blog.

Cross-process APIs. There are a lot of APIs in Firefox that cross between the parent and child processes. An example is the webNavigation property of XUL <browser> elements. The webNavigation property is an object that provides methods like loadURI, goBack, and goForward. 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.

The webNavigation 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 <browser> and <tabbrowser>. The <browser> customizations reside in browser.xml. Here is how browser.webNavigation is defined:

<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>

This code is invoked whenever JavaScript code in Firefox accesses browser.webNavigation, where browser is some <browser> element. It checks if the result has already been cached in the browser._webNavigation field. If it hasn’t been cached, then it fetches the navigation object based off the browser’s docshell. 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 webNavigation accessor runs in the parent process, this.docShell above will just return null. As a consequence, this code will fail completely.

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 <browser> elements. It is called remote-browser.xml, and it extends the existing browser.xml binding.

The remote-browser.xml binding returns a JavaScript shim object whenever anyone uses browser.webNavigation or other similar objects. The shim object is implemented in its own JavaScript module. It uses the message manager to send messages like "WebNavigation:LoadURI" to a content script loaded by remote-browser.xml. The content script performs the actual action.

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.

Cross-process object wrappers. The message manager API does not allow the parent process to call sendSyncMessage; 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 sendAsyncMessage 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.

These objects are called cross-process object wrappers—frequently abbreviated CPOWs. They’re created using the message manager. Consider this example content script:

addEventListener("load", function (e) {
  let doc = content.document;
  sendAsyncMessage("GotLoadEvent", {}, {document: doc});
}, false);

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 sendAsyncMessage 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:

let mm = Cc["@mozilla.org/globalmessagemanager;1"].
         getService(Ci.nsIMessageListenerManager);

mm.addMessageListener("GotLoadEvent", function (msg) {
  let uri = msg.objects.document.documentURI;
  dump("Received load event: " + uri + "\n");
});
mm.loadFrameScript("chrome://content/content-script.js", true);

It’s important to realize that we’re send object references. The msg.objects.document object is only a wrapper. The access to its documentURI 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.

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.

Another problem with CPOWs is that they violate some assumptions people might have about message ordering. Consider this code:

mm.addMessageListener("GotLoadEvent", function (msg) {
  mm.sendAsyncMessage("ChangeDocumentURI", {newURI: "hello.com"});
  let uri = msg.objects.document.documentURI;
  dump("Received load event: " + uri + "\n");
});

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 uri to come back as "hello.com". 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 documentURI property will be processed before the "ChangeDocumentURI" message, in which case uri will have some other value.

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.

Despite these problems, we’ve found CPOWs to be useful for converting certain parts of Firefox to be multiprocess-comptabile. 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.

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.

How will add-ons be affected?

The effect of multiple processes on add-ons is a huge topic. The most important thing I want to emphasize is that we intend to go to great lengths to ensure compatibility with add-ons. We realize that add-ons are extremely important to Firefox users, and we have no intention of abandoning or disrupting add-ons. At the same time, we feel strongly that users will appreciate the security and responsiveness benefits of multiprocess Firefox, so we’re willing to work very hard to get add-ons on board. We’re very interested in working with add-on developers to ensure that their add-ons work well in multiprocess Firefox. I hope to cover this topic in greater depths in future blog posts.

By default, add-on code runs in the parent process. Some add-ons have no need to access content objects, and so they will continue to function without any need for changes. For the remaining add-ons, we have a number of ideas and proposals to ensure compatibility.

The best way to ensure that add-ons are multiprocess-compatible is for them to use the message manager to touch content objects. This is what we’ve been doing for Firefox itself. It’s the most efficient option, and it gives the best user experience. Developers can use the message manager in their code right now; it was added in Firefox 4. We plan to convert the add-on SDK to use the message manager, which should allow most Jetpack add-ons to work with multiple processes without any additional changes. If you’re writing a new add-on, you would be doing Mozilla a great service if you use the message manager or the add-on SDK from the start.

Realistically, though, it will be a long time before all add-ons are converted to use the message manager. Until then, we have two strategies for coping with incompatible add-ons:

  • Any time an add-on accesses a content object (like a DOM node), we can give it a CPOW instead. For the most part, CPOWs behave exactly like the objects they’re wrapping. The main difference is that only primitive values can be passed as arguments when calling CPOW methods. This problem usually surfaces when the add-on calls element.addEventListener("load", function (e) {...}). This won’t work if element is a CPOW because the second argument is a function, which is not a primitive. We have some ideas for mitigating this restriction. Despite the argument problem, CPOWs make it possible for some add-ons to work with multiple processes. It’s possible to use Adblock Plus with multiprocess Firefox right now. As our CPOW techniques become better, we expect that more and more add-ons will be compatible.
  • For add-ons that don’t use the message manager or the add-on SDK and that don’t work with CPOWs, we can fall back to single-process Firefox. There are a few difficulties with this approach (what happens to your existing tabs if you install an incompatible add-on?), but we think they’re tractable.

In the future, I’m planning to update some popular add-ons to work with multiprocess Firefox. I’ll blog about difficulties I run into and possible solutions. I hope this material will be useful to people trying to use the message manager in their add-ons.

More information

Tim Taubert and David Rajchenbach-Teller have written great blog posts about message passing and the new process model.

This entry was posted in Uncategorized. Bookmark the permalink.

60 Responses to Multiprocess Firefox

  1. It’s hard to convey how glad I am to see the you’ve made on this and knowing the memory stats and that it’s reached a point where limited functionality is available in main-branch nightlies is very pleasing.

    However, I hope that, in the long term, you’ll try to push for the option to force each addon into its own killable, memory-measurable process so I can finally figure out which of my 30+ addons is causing a leak too slow to be feasible to debug via binary search. (I just can’t live without half of my addons for the several days I’d need for the initial pass.)

    Yeah, having 16GiB of RAM and improvements to the garbage collector have delayed the onset of the problem so I only need to restart to recover from GC-related jank when it leaks its way up to around 4GiB resident (it used to become unbearable around 2GiB) but, even if eventually having multiple separate content processes compartmentalizes collection enough to prevent that, I’d rather not have to restart Firefox at all.

    (Especially since that’s multiple gigs that Linux could be putting to use as an opportunistic cache for 2.5TiB of old-fashioned rotating platter hard drives.)

    Also, I’m curious. You say that, initially, you’ll have only one content process. Have you considered actually having two? One for regular windows and one that gets launched on demand for private ones? I’d think that an extra 10MiB would be a small price to pay to let the sandbox further isolate private tabs from non-private ones.

    • Also, I’m curious. You say that, initially, you’ll have only one content process. Have you considered actually having two? One for regular windows and one that gets launched on demand for private ones? I’d think that an extra 10MiB would be a small price to pay to let the sandbox further isolate private tabs from non-private ones.

      Yes, that’s something we will consider. It should be pretty easy to implement.

      • Glad to hear it. :)

        Also, I just noticed that I didn’t clarify where I got the “extra 10MiB” number. Before anyone jumps in to correct me, I’m aware the growth in memory consumption from a parent-child split with one child process is not the same thing as the cost of each child subsequent child process.

        I’m just using it as a reasonable example which, based on the optimistic view on optimization, doesn’t sound too difficult to attain if the two child processes never visit the same domains and, therefore, wouldn’t be sharing much cached data anyway.

  2. Monica says:

    YES!!! Also, best first blog post ever :)

  3. jr says:

    > When Chrome was released, it used one UI process and separate “content” processes to host web content. Other browsers have adopted similar strategies since then.

    To be fair, IE8 was the first. Chrome would be announced ~6 months later.

    • Thanks for the correction. I updated the post.

      • jr says:

        Thanks. for the curious, some technical details from IE team: blogs.msdn.com/b/ie/archive/2008/03/11/ie8-and-loosely-coupled-ie-lcie.aspx

    • John P says:

      And people ask why the IE team look down on other browsers. They practically created all the modern implementations of a browser way before others get around to it. GPU acceleration, multiprocess, devtool, etc.

      • Ferdinand says:

        Firefox was first with GPU acceleration. IE8 was first with multiprocess but IE has always been such a basic browser with such an antiquated options screen and stupid integration with windows that it didn’t matter to me. Opera was first with a weird version of tabs but Phoenix was the first browser with tabs that I liked.
        IE could be really good if it wasn’t tied to MS.

      • I think that’s all overshadowed by how long old versions of IE hang around in the wild. It’s easy to forget about a browser’s innovations when you’re stuck supporting years-old versions of it which lack said innovations.

      • Omega X says:

        IE didn’t adopt dev tools until Firebug in Firefox became a main reason to run Firefox.

  4. Hogart says:

    Nice to see that you’ve finally got a devblog going, Bill :) Thanks for the post, there are a lot of people who don’t even realize that Mozilla has resumed the e10s project and even fewer who realize how much effort you guys have put into fixing jank. It looks like OMTC is coming along nicely, so I hope that it and the coming Moz2D/Skia and generational garbage collector efforts will help even more.

  5. ertt says:

    So if i have a quad core CPU, and i run a JS benchmark like Octane in 3 tabs simultaneously, then :
    1. My CPU usage will be 75%/ 3 cores will be used
    2. The performance of Firefox for working in other tabs wont take a major hit.

    • Damian says:

      Presumably this will be true when it becomes on a per-tab(/iframe?) basis. Currently it’s just Firefox Chrome (UI stuff) vs. everything else,

  6. Damian says:

    Was hoping to try this out. Unfortunately this led to immediate crashes, but I am running a dirty profile (lots of history and add ons). Can’t wait till it’s more stable!

  7. matejcepltest says:

    For me it was the other one than “everything works” (Mozilla/5.0 (X11; Linux x86_64; rv:28.0) Gecko/20100101 Firefox/28.0) with browser.tabs.remote = true I wasn’t able even to display login screen to my email (which is a rather plain Zarafa page with static content), not mentioning my news site (http://ihned.cz). The fact that Password Manager didn’t provide my remembered passwords (which would be otherwise a deal breaker for me) is such circumstance irrelevant.

  8. Pingback: Multi-process Firefox is a go | Ghacks

  9. nnethercote says:

    Great post, thanks for writing.

    Performance (responsiveness) is one of the major motivations. Do you have any interesting measurements or anecdotes about the effects?

  10. Pingback: Multiprocess Firefox | Enjoying The Moment

  11. Anonymous says:

    Can’t some the benefits of multi-processing (separate event loops, asynchronous rendering) also be had with the multi-threading approach without disrupting add-ons? Of course it would have some drawbacks like the absence of locking or sandboxing, but to me it seems that multi-processing is orthogonal to to parallelizing the runtime.

  12. Stu says:

    Excellent, been hoping for this to comeback since electrolysis was frozen !

  13. There’s no single mention of input -> output latency in this blog post. So I have to assume that the very concept is entirely unbeknown to the authors.

    Now, let me explain first what input -> output latency is. The user provides some input, such as a mouseclick, a mouse move a key press, a gamepad event, and so forth. In turn, such an event is processed by the browser or passed to JS and might change anything in the DOM or might draw different things on the canvas and this eventually hits trough to the monitor, where it’s displayed to the user. The time it takes from an input to percolate trough the system and make it on screen, this is the input -> output latency.

    The next thing I want to explain is, how input -> output latency affects user experience. To do that, let’s imagine an infeasably large latency, such as 10 seconds. It’s obvious to anybody, that pretty much no usecase would survive this kind of latency. So let’s imagine it goes down to 1 second, are there some use cases that work? There are usecases that would work, such as typing (although awkwardly), clicking with the mouse to fire a navigation and perhaps some simple CSS styling. What if you went down to 200ms? Well, most usecases would probably be pretty fine.

    So we can see from this that a lower latency expresses a gradient in how useful the browser is. The lower the latency, the more usecases it opens up. The essential question is, what usecases are there that require lower latencies?

    A 200ms latency is still pretty big, typing can still be slightly awkward for touch typists, most things to do with mouse-moves can’t be used because representatins would lag behind the mouse position substantially and fast animated effects would fire long after they should’ve been triggered.

    At a 60ms latency, you’ve covered most usecases, but there are still exceptions that matter. And those exceptions are twitchy browser games (such as jump&runs, FPS shooters and so forth) and oculus rift applications where high latency can cause nausea and discomfort, and makes the experience less enjoyable.

    For that latter class of applications, you cannot essentially be fast enough. Every teensy millisecond that you can sqeeze out, matters. And the absolute top acceptable latency is 30 milliseconds, you cannot go over that limit for that class of applications. And if you do, you’re essentially crippling them.

    Now, the reason I explain all this is because I know that latency in multipress chrome is bad. Like really bad > 150ms. And the reason it’s bad because any signal hops trough multiple processes and threads before it even gets to emit rendering commands, and then it still takes time till the image appears on screen.

    So, if you don’t want to excessively penalize certain applications of innovative browser technology, you’ll make input -> output latency a PRIME PRIORITY.

    • nnethercote says:

      WTF? You clearly didn’t read the article. The very first part of the “Why are we doing this?” section says: “The goal is to reduce “jank”—those times when the browser seems to briefly freeze when loading a big page, typing in a form, or scrolling”.

    • I think their reasoning is “we can’t have predictable input-output latency as long as the entire Firefox “Web platform runtime” is a single process implementing one giant cooperative multitasking event loop with arbitrary extensions hooked in. Therefore, let’s delegate concurrency handling to the preemptively multitasked OS before we work on the dependent problems.”

  14. Firefox User says:

    Sorry I did not read this article completely since it was very long. If this multiprocess model is similar to Google Chrome, then I’d just have to give up on Mozilla Firefox altogether. I’m a super heavy user of tabs and one big reason why I HATE (yes, all caps) Google Chrome is that it gets very sluggish after opening say 10-20 tabs. Its memory consumption balloons up and it brings the system to a crawl (this is on systems with 4GiB as well as 8GiB RAM). On the other hand, Mozilla Firefox handles even a hundred tabs much better than Google Chrome with a much smaller memory footprint. Please do not arm-twist me into giving up on Mozilla Firefox!!! As a staunch Firefox user since the Phoenix days, I’d be really sad if that happens.

    If your multiprocess model is similar to that of Google Chrome, then please at least provide an option for some more years for users to choose how they’d like to use the browser (while hardware picks up a lot more and code maintenance becomes a bigger priority than supporting different threaded vs. process models).

    • It really isn’t nice to comment if you TL;DR’d the article.

      Memory consumption was addressed. They’ll start by only having one content process (which they’ve measured as adding only 10MiB to the memory footprint) and they’re confident that the difference can be optimized away before turning on the spawning of multiple content processes.

      As for Chrome getting sluggish after 10-20 tabs, I use Chrome in my development and used to use it for private browsing before Firefox got “New Private Window”. I know what you mean and I don’t see how that’d be an inherent flaw in the multiprocess model. It’s just a bad implementation.

    • nnethercote says:

      Go read the section “How much memory will it use?” There’s a link right at the top of the post. It would have taken you less time to find and read that than it did to write that comment.

  15. The current implementation causes a serious performance degration for WebGL demos (e.g. here: http://www.flohofwoe.net/demos/dsomapviewer_asmjs.html), this runs at full 60fps in “traditional” mode, but only 36fps in multi-process mode. Should I already starts writing tickets or is this a known issue?

    Great work nethertheless :) Will GL rendering happen in a different process than Javascript execution? Theoretically this should give a perf improvement for WebGL games since work is spread over 2 cores.

    • He did say that Off-Main-Thread Compositing was only ready for prime-time on MacOS and it IS required for multi-process operation so I’m guessing that’s why you’re seeing the performance degradation.

  16. Pingback: codescaling | Multiprocess Firefox, Kexec and Secure Boot, Poisoning GCC and OpenNebula 4.4 – Snippets

  17. Not very thrilled with all this. Chrome takes huge chunks of my memory with the thread-for-each-tab approach. Its close to unusable beyond 10 tabs. Firefox excels at this. Atleast currently. I don’t like to see that going away.

    • Chrome developers do a lot of things that look very lazy to me. Bill already wrote that it’ll be limited to one content process for all tabs until they can optimize away that bloat.

      (Total added memory footprint under a 50 tab test: 10MiB)

  18. Did you guys evaluate any existing RPC frameworks before going off and writing your own? Apache Thrift, for instance, has a full RPC implementation and IDL complete with code generation for a dozen languages

    • “All IPC happens using the Chromium IPC libraries.”

      Unless I’m misunderstanding that line, they didn’t write their own.

      • …and yes, I know IPC and RPC aren’t the same thing.

        Firefox already has its own IDL and code generator it’s been using and evolving since the Mozilla Suite days and adding IPC to it is simpler and less bug-prone than rewriting the entire thing on top of someone else’s framework.

        (WordPress really needs a “first five minutes” edit button)

  19. Pingback: Multi-process Firefox is a go | vpsdash

  20. stqn says:

    Glad to read you’re working on this, as “jank” as you call it can be very annoying at times. I hope addons won’t be able to freeze the GUI if they still run in the main thread.

  21. bryanquigley says:

    Could this allow us to turn on something like AppArmor (apparmor.net linux only MAC control) for the child processes only? Will they run with a different process name?

    • Right now we’re planning to use seccomp sandboxing. I don’t know anything about AppArmor, but perhaps it could work too. I don’t think we’ve settled on a process name yet; right now the content processes are called “plugin-container”.

  22. azakai says:

    Very exciting! Pretty much everything I tried worked except for WebGL and the web console which is empty. Are those known issues or should I file bugs?

    • The web console is a known issue. I don’t think it should be hard to fix, since it already uses the remote debugging protocol; we just have to hook it up in the right way. Bug 875871 is tracking this work.

      I’m less sure about the situation with WebGL. It wouldn’t hurt to file a bug that blocks core-e10s.

      • azakai says:

        I’ll follow that bug, thanks. About webgl, turns out I forget to force-enable webgl in my new profile.

  23. Mai Salf says:

    Are you able to provide some details about how this will interact with virtual memory paging systems?

  24. philipp says:

    does anybody know how to get a tab to crash to observe the new model? ;-)

  25. Pingback: Multiprocess Firefox | Bill McCloskey’s Blog | Buy Smartphone and Tablet Firefox OS

  26. Kelly Clowers says:

    Any chance that when this come to fruition it could be disabled with about:config or at least a build-time option? Though the two process model might not be too bad. I just don’t want to end up with process per tab. I don’t need or want 50-200 SeaMonkey processes running on my system.

  27. testFF says:

    Copying still doesn’t work with multiple processes

  28. Pingback: Доступна для тестирования многопроцессная версия Firefox | AllUNIX.ru — Всероссийский портал о UNIX-системах

  29. Carsten Mattner says:

    Anyone tried NoScript?

  30. Carsten Mattner says:

    Focus on jank is good and hopefully multi-process firefox isn’t as bad with latency as Chrome is. The way Chrome does IPC make their UI less responsive than Opera 12 than Firefox. Like memory use in Firefox vs Chrome you have the chance to make a responsive multi-process browser.

  31. Pingback: Lilbits (12-05-2013): Seeing the world through Google colored glasses - Liliputing

  32. Pingback: Mozilla takes another stab at elusive multiprocess Firefox - Turkey Innovation

  33. Pingback: Mozilla takes another stab at elusive multiprocess Firefox | 新闻摘录

  34. Pingback: Mozilla werkt weer aan multiprocess-functionaliteit voor Firefox | ICT Magazine

Comments are closed.