Merge m-c to b2g-inbound. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 30 Apr 2015 14:03:11 -0400
changeset 273162 4869fe8e1487bc202bd1166c94dc73c8eb01b0db
parent 273161 dd9b1124836d4e57a91e89b4998242e82c345221 (current diff)
parent 273159 7723b15ea695e321e6acda04beef9bc98728dd13 (diff)
child 273163 0034c0db6313d8e7a64c33137e32fceb0ea85b2a
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone40.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to b2g-inbound. a=merge
browser/devtools/performance/modules/performance-graphs.js
browser/devtools/shared/test/browser_inplace-editor.js
dom/animation/AnimationEffectReadonly.cpp
dom/animation/AnimationEffectReadonly.h
dom/webidl/AnimationEffectReadonly.webidl
testing/marionette/client/marionette/tests/unit/test_submit.py
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,10 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 1154356: escape variable name in Declaration::AppendVariableAndValueToString;
+Bug 1159082 - Renaming *Readonly animation interfaces to *ReadOnly causes
+build bustage on case-insensitive filesystems.
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -313,16 +313,21 @@ pref("media.cache_size", 4096);    // 4M
 // below 10s of buffered data.
 pref("media.cache_resume_threshold", 10);
 pref("media.cache_readahead_limit", 30);
 
 #ifdef MOZ_FMP4
 // Enable/Disable Gonk Decoder Module
 pref("media.fragmented-mp4.gonk.enabled", true);
 #endif
+
+//Encrypted media extensions.
+pref("media.eme.enabled", true);
+pref("media.eme.apiVisible", true);
+
 // The default number of decoded video frames that are enqueued in
 // MediaDecoderReader's mVideoQueue.
 pref("media.video-queue.default-size", 3);
 
 // optimize images' memory usage
 pref("image.decode-only-on-draw.enabled", false);
 pref("image.mem.allow_locking_in_content_processes", true);
 // Limit the surface cache to 1/8 of main memory or 128MB, whichever is smaller.
--- a/browser/base/content/aboutNetError.xhtml
+++ b/browser/base/content/aboutNetError.xhtml
@@ -459,28 +459,32 @@
 
       <!-- UI for option to report certificate errors to Mozilla. Removed on
            init for other error types .-->
       <div id="certificateErrorReporting">
         <a id="showCertificateErrorReportingPanel" href="#">&errorReporting.title;<span class="downArrow"> ▼</span></a>
       </div>
 
       <div id="certificateErrorReportingPanel">
-        <p>&errorReporting.longDesc;</p>
-        <p>
-          <input type="checkbox" id="automaticallyReportInFuture" />
-          <label for="automaticallyReportInFuture" id="automaticallyReportInFuture">&errorReporting.automatic;</label>
-        </p>
-        <a href="https://support.mozilla.org/kb/tls-error-reports" id="learnMoreLink" target="new">&errorReporting.learnMore;</a>
-        <span id="reportingState">
-          <button id="reportCertificateError">&errorReporting.report;</button>
-          <button id="reportCertificateErrorRetry">&errorReporting.tryAgain;</button>
-          <span id="reportSendingMessage">&errorReporting.sending;</span>
-          <span id="reportSentMessage">&errorReporting.sent;</span>
-        </span>
+        <div id="certificateErrorReportingDescription">
+          <p>&errorReporting.longDesc;</p>
+          <p>
+            <input type="checkbox" id="automaticallyReportInFuture" />
+            <label for="automaticallyReportInFuture" id="automaticallyReportInFuture">&errorReporting.automatic;</label>
+          </p>
+        </div>
+        <div id="errorStatePanel">
+          <a href="https://support.mozilla.org/kb/tls-error-reports" id="learnMoreLink" target="new">&errorReporting.learnMore;</a>
+          <span id="reportingState">
+            <button id="reportCertificateError">&errorReporting.report;</button>
+            <button id="reportCertificateErrorRetry">&errorReporting.tryAgain;</button>
+            <span id="reportSendingMessage">&errorReporting.sending;</span>
+            <span id="reportSentMessage">&errorReporting.sent;</span>
+          </span>
+        </div>
       </div>
 
     </div>
 
     <!--
     - Note: It is important to run the script this way, instead of using
     - an onload handler. This is because error pages are loaded as
     - LOAD_BACKGROUND, which means that onload handlers will not be executed.
--- a/browser/base/content/browser-menubar.inc
+++ b/browser/base/content/browser-menubar.inc
@@ -23,20 +23,16 @@
                           key="key_newNavigator"
                           command="cmd_newNavigator"/>
                 <menuitem id="menu_newPrivateWindow"
                           label="&newPrivateWindow.label;"
                           accesskey="&newPrivateWindow.accesskey;"
                           command="Tools:PrivateBrowsing"
                           key="key_privatebrowsing"/>
 #ifdef E10S_TESTING_ONLY
-                <menuitem id="menu_newRemoteWindow"
-                          label="New e10s Window"
-                          hidden="true"
-                          command="Tools:RemoteWindow"/>
                 <menuitem id="menu_newNonRemoteWindow"
                           label="New Non-e10s Window"
                           hidden="true"
                           command="Tools:NonRemoteWindow"/>
 #endif
 #ifdef MAC_NON_BROWSER_WINDOW
                 <menuitem id="menu_openLocation"
                           label="&openLocationCmd.label;"
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -109,18 +109,16 @@
     <command id="Tools:Addons" oncommand="BrowserOpenAddonsMgr();"/>
     <command id="Tools:ErrorConsole" oncommand="toJavaScriptConsole()" disabled="true" hidden="true"/>
     <command id="Tools:DevToolsConnect" oncommand="gDevToolsBrowser.openConnectScreen(gBrowser)" disabled="true" hidden="true"/>
     <command id="Tools:Sanitize"
      oncommand="Cc['@mozilla.org/browser/browserglue;1'].getService(Ci.nsIBrowserGlue).sanitize(window);"/>
     <command id="Tools:PrivateBrowsing"
       oncommand="OpenBrowserWindow({private: true});" reserved="true"/>
 #ifdef E10S_TESTING_ONLY
-    <command id="Tools:RemoteWindow"
-      oncommand="OpenBrowserWindow({remote: true});"/>
     <command id="Tools:NonRemoteWindow"
       oncommand="OpenBrowserWindow({remote: false});"/>
 #endif
     <command id="History:UndoCloseTab" oncommand="undoCloseTab();"/>
     <command id="History:UndoCloseWindow" oncommand="undoCloseWindow();"/>
     <command id="Social:SharePage" oncommand="SocialShare.sharePage();"/>
     <command id="Social:ToggleSidebar" oncommand="SocialSidebar.toggleSidebar();" hidden="true"/>
     <command id="Social:ToggleNotifications" oncommand="Social.toggleNotifications();" hidden="true"/>
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -7225,20 +7225,18 @@ let gRemoteTabsUI = {
 #ifdef XP_MACOSX
     if (Services.prefs.getBoolPref("layers.acceleration.disabled")) {
       // On OS X, "Disable Hardware Acceleration" also disables OMTC and forces
       // a fallback to Basic Layers. This is incompatible with e10s.
       return;
     }
 #endif
 
-    let newRemoteWindow = document.getElementById("menu_newRemoteWindow");
     let newNonRemoteWindow = document.getElementById("menu_newNonRemoteWindow");
     let autostart = Services.appinfo.browserTabsRemoteAutostart;
-    newRemoteWindow.hidden = autostart;
     newNonRemoteWindow.hidden = !autostart;
   }
 };
 
 /**
  * Switch to a tab that has a given URI, and focusses its browser window.
  * If a matching tab is in this window, it will be switched to. Otherwise, other
  * windows will be searched.
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -203,16 +203,25 @@ let AboutNetErrorListener = {
               enabled: Services.prefs.getBoolPref("security.ssl.errorReporting.enabled"),
             automatic: automatic
             })
           }
     ));
     if (automatic) {
       this.onSendReport(evt);
     }
+    // hide parts of the UI we don't need yet
+    let contentDoc = content.document;
+
+    let reportSendingMsg = contentDoc.getElementById("reportSendingMessage");
+    let reportSentMsg = contentDoc.getElementById("reportSentMessage");
+    let retryBtn = contentDoc.getElementById("reportCertificateErrorRetry");
+    reportSendingMsg.style.display = "none";
+    reportSentMsg.style.display = "none";
+    retryBtn.style.display = "none";
   },
 
   onSetAutomatic: function(evt) {
     sendAsyncMessage("Browser:SetSSLErrorReportAuto", {
         automatic: evt.detail
       });
   },
 
@@ -229,33 +238,32 @@ let AboutNetErrorListener = {
       // document - we'll compare on document URI
       if (contentDoc.documentURI === message.data.documentURI) {
         switch(message.data.reportStatus) {
         case "activity":
           // Hide the button that was just clicked
           reportBtn.style.display = "none";
           retryBtn.style.display = "none";
           reportSentMsg.style.display = "none";
-          reportSendingMsg.style.display = "inline";
+          reportSendingMsg.style.removeProperty("display");
           break;
         case "error":
           // show the retry button
-          retryBtn.style.display = "inline";
+          retryBtn.style.removeProperty("display");
           reportSendingMsg.style.display = "none";
           break;
         case "complete":
           // Show a success indicator
-          reportSentMsg.style.display = "inline";
+          reportSentMsg.style.removeProperty("display");
           reportSendingMsg.style.display = "none";
           break;
         }
       }
     });
 
-
     let failedChannel = docShell.failedChannel;
     let location = contentDoc.location.href;
 
     let serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
                      .getService(Ci.nsISerializationHelper);
 
     let serializable =  docShell.failedChannel.securityInfo
                                 .QueryInterface(Ci.nsITransportSecurityInfo)
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -526,48 +526,42 @@
         <parameter name="aBrowser"/>
         <parameter name="aMethod"/>
         <parameter name="aArguments"/>
         <parameter name="aCallGlobalListeners"/>
         <parameter name="aCallTabsListeners"/>
         <body><![CDATA[
           var rv = true;
 
-          if (!aBrowser)
-            aBrowser = this.mCurrentBrowser;
-
-          if (aCallGlobalListeners != false &&
-              aBrowser == this.mCurrentBrowser) {
-            for (let p of this.mProgressListeners) {
+          function callListeners(listeners, args) {
+            for (let p of listeners) {
               if (aMethod in p) {
                 try {
-                  if (!p[aMethod].apply(p, aArguments))
+                  if (!p[aMethod].apply(p, args))
                     rv = false;
                 } catch (e) {
                   // don't inhibit other listeners
                   Components.utils.reportError(e);
                 }
               }
             }
           }
 
+          if (!aBrowser)
+            aBrowser = this.mCurrentBrowser;
+
+          if (aCallGlobalListeners != false &&
+              aBrowser == this.mCurrentBrowser) {
+            callListeners(this.mProgressListeners, aArguments);
+          }
+
           if (aCallTabsListeners != false) {
             aArguments.unshift(aBrowser);
 
-            for (let p of this.mTabsProgressListeners) {
-              if (aMethod in p) {
-                try {
-                  if (!p[aMethod].apply(p, aArguments))
-                    rv = false;
-                } catch (e) {
-                  // don't inhibit other listeners
-                  Components.utils.reportError(e);
-                }
-              }
-            }
+            callListeners(this.mTabsProgressListeners, aArguments);
           }
 
           return rv;
         ]]></body>
       </method>
 
       <!-- A web progress listener object definition for a given tab. -->
       <method name="mTabProgressListener">
@@ -1513,23 +1507,21 @@
             // keep the old one. Re-set it explicitly after unbinding from DOM.
             aBrowser._permanentKey = permanentKey;
             parent.appendChild(aBrowser);
 
             // Restore the progress listener.
             aBrowser.webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL);
 
             if (aShouldBeRemote) {
-              tab.setAttribute("remote", "true");
               // Switching the browser to be remote will connect to a new child
               // process so the browser can no longer be considered to be
               // crashed.
               tab.removeAttribute("crashed");
             } else {
-              tab.removeAttribute("remote");
               aBrowser.messageManager.sendAsyncMessage("Browser:AppTab", { isAppTab: tab.pinned })
             }
 
             if (wasActive)
               aBrowser.focus();
 
             let evt = document.createEvent("Events");
             evt.initEvent("TabRemotenessChange", true, false);
@@ -1736,18 +1728,16 @@
             t.setAttribute("onerror", "this.removeAttribute('image');");
             t.className = "tabbrowser-tab";
 
             // The new browser should be remote if this is an e10s window and
             // the uri to load can be loaded remotely.
             let remote = gMultiProcessBrowser &&
                          !aForceNotRemote &&
                          E10SUtils.canLoadURIInProcess(aURI, Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT);
-            if (remote)
-              t.setAttribute("remote", "true");
 
             this.tabContainer._unlockTabSizing();
 
             // When overflowing, new tabs are scrolled into view smoothly, which
             // doesn't go well together with the width transition. So we skip the
             // transition in that case.
             let animate = !aSkipAnimation &&
                           this.tabContainer.getAttribute("overflow") != "true" &&
@@ -3680,19 +3670,21 @@
         <parameter name="event"/>
         <body><![CDATA[
           event.stopPropagation();
           var tab = document.tooltipNode;
           if (tab.localName != "tab") {
             event.preventDefault();
             return;
           }
-          event.target.setAttribute("label", tab.mOverCloseButton ?
-                                             tab.getAttribute("closetabtext") :
-                                             tab.getAttribute("label"));
+          event.target.setAttribute("label",
+                                    tab.mOverCloseButton ?
+                                    tab.getAttribute("closetabtext") :
+                                    tab.getAttribute("label") +
+                                      (this.AppConstants.E10S_TESTING_ONLY && tab.linkedBrowser && tab.linkedBrowser.isRemoteBrowser ? " - e10s" : ""));
         ]]></body>
       </method>
 
       <method name="handleEvent">
         <parameter name="aEvent"/>
         <body><![CDATA[
           switch (aEvent.type) {
             case "keydown":
--- a/browser/base/content/test/general/browser_bug623155.js
+++ b/browser/base/content/test/general/browser_bug623155.js
@@ -110,17 +110,17 @@ function delayed(aIsSelectedTab) {
 
   ok(isRedirectedURISpec(content.location.href),
      "The content area is redirected. aIsSelectedTab:" + aIsSelectedTab);
   is(gURLBar.value, content.location.href,
      "The URL bar shows the content URI. aIsSelectedTab:" + aIsSelectedTab);
 
   if (!aIsSelectedTab) {
     // If this was a background request, go on a foreground request.
-    content.location = REDIRECT_FROM + "#FG";
+    gBrowser.selectedBrowser.loadURI(REDIRECT_FROM + "#FG");
   }
   else {
     // Othrewise, nothing to do remains.
     finish();
   }
 }
 
 /* Cleanup */
--- a/browser/base/content/test/general/browser_e10s_switchbrowser.js
+++ b/browser/base/content/test/general/browser_e10s_switchbrowser.js
@@ -102,158 +102,158 @@ let forward = Task.async(function*() {
   gBrowser.goForward();
   yield waitForDocLoadComplete();
   gExpectedHistory.index++;
 });
 
 // Tests that navigating from a page that should be in the remote process and
 // a page that should be in the main process works and retains history
 add_task(function* test_navigation() {
-  let expectedRemote = gMultiProcessBrowser ? "true" : "";
+  let expectedRemote = gMultiProcessBrowser;
 
   info("1");
   // Create a tab and load a remote page in it
   gBrowser.selectedTab = gBrowser.addTab("about:blank", {skipAnimation: true});
   let {permanentKey} = gBrowser.selectedBrowser;
   yield waitForLoad("http://example.org/" + DUMMY_PATH);
-  is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
+  is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
   is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
 
   info("2");
   // Load another page
   yield waitForLoad("http://example.com/" + DUMMY_PATH);
-  is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
+  is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
   is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
   yield check_history();
 
   info("3");
   // Load a non-remote page
   yield waitForLoad("about:robots");
-  is(gBrowser.selectedTab.getAttribute("remote"), "", "Remote attribute should be correct");
+  is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct");
   is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
   yield check_history();
 
   info("4");
   // Load a remote page
   yield waitForLoad("http://example.org/" + DUMMY_PATH);
-  is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
+  is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
   is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
   yield check_history();
 
   info("5");
   yield back();
-  is(gBrowser.selectedTab.getAttribute("remote"), "", "Remote attribute should be correct");
+  is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct");
   is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
   yield check_history();
 
   info("6");
   yield back();
-  is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
+  is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
   is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
   yield check_history();
 
   info("7");
   yield forward();
-  is(gBrowser.selectedTab.getAttribute("remote"), "", "Remote attribute should be correct");
+  is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct");
   is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
   yield check_history();
 
   info("8");
   yield forward();
-  is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
+  is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
   is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
   yield check_history();
 
   info("9");
   yield back();
-  is(gBrowser.selectedTab.getAttribute("remote"), "", "Remote attribute should be correct");
+  is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct");
   is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
   yield check_history();
 
   info("10");
   // Load a new remote page, this should replace the last history entry
   gExpectedHistory.entries.splice(gExpectedHistory.entries.length - 1, 1);
   yield waitForLoad("http://example.com/" + DUMMY_PATH);
-  is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
+  is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
   is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
   yield check_history();
 
   info("11");
   gBrowser.removeCurrentTab();
   clear_history();
 });
 
 // Tests that calling gBrowser.loadURI or browser.loadURI to load a page in a
 // different process updates the browser synchronously
 add_task(function* test_synchronous() {
-  let expectedRemote = gMultiProcessBrowser ? "true" : "";
+  let expectedRemote = gMultiProcessBrowser;
 
   info("1");
   // Create a tab and load a remote page in it
   gBrowser.selectedTab = gBrowser.addTab("about:blank", {skipAnimation: true});
   let {permanentKey} = gBrowser.selectedBrowser;
   yield waitForLoad("http://example.org/" + DUMMY_PATH);
-  is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
+  is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
   is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
 
   info("2");
   // Load another page
   info("Loading about:robots");
   gBrowser.selectedBrowser.loadURI("about:robots");
-  is(gBrowser.selectedTab.getAttribute("remote"), "", "Remote attribute should be correct");
+  is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct");
   is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
 
   yield waitForDocLoadComplete();
-  is(gBrowser.selectedTab.getAttribute("remote"), "", "Remote attribute should be correct");
+  is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct");
   is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
 
   info("3");
   // Load the remote page again
   info("Loading http://example.org/" + DUMMY_PATH);
   gBrowser.loadURI("http://example.org/" + DUMMY_PATH);
-  is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
+  is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
   is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
 
   yield waitForDocLoadComplete();
-  is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
+  is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
   is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
 
   info("4");
   gBrowser.removeCurrentTab();
   clear_history();
 });
 
 // Tests that load flags are correctly passed through to the child process with
 // normal loads
 add_task(function* test_loadflags() {
-  let expectedRemote = gMultiProcessBrowser ? "true" : "";
+  let expectedRemote = gMultiProcessBrowser;
 
   info("1");
   // Create a tab and load a remote page in it
   gBrowser.selectedTab = gBrowser.addTab("about:blank", {skipAnimation: true});
   yield waitForLoadWithFlags("about:robots");
-  is(gBrowser.selectedTab.getAttribute("remote"), "", "Remote attribute should be correct");
+  is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct");
   yield check_history();
 
   info("2");
   // Load a page in the remote process with some custom flags
   yield waitForLoadWithFlags("http://example.com/" + DUMMY_PATH, Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY);
-  is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
+  is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
   yield check_history();
 
   info("3");
   // Load a non-remote page
   yield waitForLoadWithFlags("about:robots");
-  is(gBrowser.selectedTab.getAttribute("remote"), "", "Remote attribute should be correct");
+  is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct");
   yield check_history();
 
   info("4");
   // Load another remote page
   yield waitForLoadWithFlags("http://example.org/" + DUMMY_PATH, Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY);
-  is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
+  is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
   yield check_history();
 
   is(gExpectedHistory.entries.length, 2, "Should end with the right number of history entries");
 
   info("5");
   gBrowser.removeCurrentTab();
   clear_history();
 });
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -1212,45 +1212,31 @@ if (Services.prefs.getBoolPref("browser.
     };
 
     CustomizableWidgets.push(pocketButton);
     CustomizableUI.addListener(pocketButton);
   }
 }
 
 #ifdef E10S_TESTING_ONLY
-/**
-  * The e10s button's purpose is to lower the barrier of entry
-  * for our Nightly testers to use e10s windows. We'll be removing it
-  * once remote tabs are enabled. This button should never ever make it
-  * to production. If it does, that'd be bad, and we should all feel bad.
-  */
-let getCommandFunction = function(aOpenRemote) {
-  return function(aEvent) {
-    let win = aEvent.view;
-    if (win && typeof win.OpenBrowserWindow == "function") {
-      win.OpenBrowserWindow({remote: aOpenRemote});
-    }
-  };
-}
-
-let openRemote = !Services.appinfo.browserTabsRemoteAutostart;
-// Like the XUL menuitem counterparts, we hard-code these strings in because
-// this button should never roll into production.
-let buttonLabel = openRemote ? "New e10s Window"
-                              : "New Non-e10s Window";
-
 let e10sDisabled = Services.appinfo.inSafeMode;
 #ifdef XP_MACOSX
 // On OS X, "Disable Hardware Acceleration" also disables OMTC and forces
 // a fallback to Basic Layers. This is incompatible with e10s.
 e10sDisabled |= Services.prefs.getBoolPref("layers.acceleration.disabled");
 #endif
 
-CustomizableWidgets.push({
-  id: "e10s-button",
-  label: buttonLabel,
-  tooltiptext: buttonLabel,
-  disabled: e10sDisabled,
-  defaultArea: CustomizableUI.AREA_PANEL,
-  onCommand: getCommandFunction(openRemote),
-});
+if (Services.appinfo.browserTabsRemoteAutostart) {
+  CustomizableWidgets.push({
+    id: "e10s-button",
+    label: "New Non-e10s Window",
+    tooltiptext: "New Non-e10s Window",
+    disabled: e10sDisabled,
+    defaultArea: CustomizableUI.AREA_PANEL,
+    onCommand: function(aEvent) {
+      let win = aEvent.view;
+      if (win && typeof win.OpenBrowserWindow == "function") {
+        win.OpenBrowserWindow({remote: false});
+      }
+    },
+  });
+}
 #endif
--- a/browser/devtools/definitions.js
+++ b/browser/devtools/definitions.js
@@ -234,16 +234,17 @@ Tools.canvasDebugger = {
   }
 };
 
 Tools.performance = {
   id: "performance",
   ordinal: 7,
   icon: "chrome://browser/skin/devtools/tool-profiler.svg",
   invertIconForLightTheme: true,
+  highlightedicon: "chrome://browser/skin/devtools/tool-profiler-active.svg",
   url: "chrome://browser/content/devtools/performance.xul",
   visibilityswitch: "devtools.performance.enabled",
   label: l10n("profiler.label2", profilerStrings),
   panelLabel: l10n("profiler.panelLabel2", profilerStrings),
   tooltip: l10n("profiler.tooltip2", profilerStrings),
   accesskey: l10n("profiler.accesskey", profilerStrings),
   key: l10n("profiler.commandkey2", profilerStrings),
   modifiers: "shift",
--- a/browser/devtools/markupview/markup-view.js
+++ b/browser/devtools/markupview/markup-view.js
@@ -2259,16 +2259,17 @@ function TextEditor(aContainer, aNode, a
 
   this.markup.template(aTemplate, this);
 
   editableField({
     element: this.value,
     stopOnReturn: true,
     trigger: "dblclick",
     multiline: true,
+    trimOutput: false,
     done: (aVal, aCommit) => {
       if (!aCommit) {
         return;
       }
       this.node.getNodeValue().then(longstr => {
         longstr.string().then(oldValue => {
           longstr.release().then(null, console.error);
 
--- a/browser/devtools/performance/modules/front.js
+++ b/browser/devtools/performance/modules/front.js
@@ -32,17 +32,18 @@ loader.lazyImporter(this, "Promise",
 
 
 // How often do we pull allocation sites from the memory actor.
 const DEFAULT_ALLOCATION_SITES_PULL_TIMEOUT = 200; // ms
 
 // Events to pipe from PerformanceActorsConnection to the PerformanceFront
 const CONNECTION_PIPE_EVENTS = [
   "console-profile-start", "console-profile-ending", "console-profile-end",
-  "timeline-data", "profiler-already-active", "profiler-activated"
+  "timeline-data", "profiler-already-active", "profiler-activated",
+  "recording-started", "recording-stopped"
 ];
 
 // Events to listen to from the profiler actor
 const PROFILER_EVENTS = ["console-api-profiler", "profiler-stopped"];
 
 /**
  * A cache of all PerformanceActorsConnection instances.
  * The keys are Target objects.
@@ -95,19 +96,19 @@ function PerformanceActorsConnection(tar
   this._onProfilerEvent = this._onProfilerEvent.bind(this);
   this._pullAllocationSites = this._pullAllocationSites.bind(this);
 
   Services.obs.notifyObservers(null, "performance-actors-connection-created", null);
 }
 
 PerformanceActorsConnection.prototype = {
 
-  // Properties set when mocks are being used
-  _usingMockMemory: false,
-  _usingMockTimeline: false,
+  // Properties set based off of server actor support
+  _memorySupported: true,
+  _timelineSupported: true,
 
   /**
    * Initializes a connection to the profiler and other miscellaneous actors.
    * If in the process of opening, or already open, nothing happens.
    *
    * @return object
    *         A promise that is resolved once the connection is established.
    */
@@ -169,32 +170,32 @@ PerformanceActorsConnection.prototype = 
   /**
    * Initializes a connection to a timeline actor.
    */
   _connectTimelineActor: function() {
     let supported = yield compatibility.timelineActorSupported(this._target);
     if (supported) {
       this._timeline = new TimelineFront(this._target.client, this._target.form);
     } else {
-      this._usingMockTimeline = true;
       this._timeline = new compatibility.MockTimelineFront();
     }
+    this._timelineSupported = supported;
   },
 
   /**
    * Initializes a connection to a memory actor.
    */
   _connectMemoryActor: Task.async(function* () {
     let supported = yield compatibility.memoryActorSupported(this._target);
     if (supported) {
       this._memory = new MemoryFront(this._target.client, this._target.form);
     } else {
-      this._usingMockMemory = true;
       this._memory = new compatibility.MockMemoryFront();
     }
+    this._memorySupported = supported;
   }),
 
   /**
    * Registers listeners on events from the underlying
    * actors, so the connection can handle them.
    */
   _registerListeners: Task.async(function*() {
     // Pipe events from TimelineActor to the PerformanceFront
@@ -408,16 +409,17 @@ PerformanceActorsConnection.prototype = 
       memoryStartTime
     };
 
     // Signify to the model that the recording has started,
     // populate with data and store the recording model here.
     model.populate(data);
     this._recordings.push(model);
 
+    this.emit("recording-started", model);
     return model;
   }),
 
   /**
    * Manually ends the recording session for the corresponding RecordingModel.
    *
    * @param RecordingModel model
    *        The corresponding RecordingModel that belongs to the recording session wished to stop.
@@ -462,16 +464,17 @@ PerformanceActorsConnection.prototype = 
       profile: profilerData.profile,
 
       // End times for all the actors.
       profilerEndTime: profilerData.currentTime,
       timelineEndTime: timelineEndTime,
       memoryEndTime: memoryEndTime
     });
 
+    this.emit("recording-stopped", model);
     return model;
   }),
 
   /**
    * Checks all currently stored recording models and returns a boolean
    * if there is a session currently being recorded.
    *
    * @return Boolean
@@ -615,33 +618,34 @@ PerformanceActorsConnection.prototype = 
  */
 function PerformanceFront(connection) {
   EventEmitter.decorate(this);
 
   this._connection = connection;
   this._request = connection._request;
 
   // Set when mocks are being used
-  this._usingMockMemory = connection._usingMockMemory;
-  this._usingMockTimeline = connection._usingMockTimeline;
+  this._memorySupported = connection._memorySupported;
+  this._timelineSupported = connection._timelineSupported;
 
   // Pipe the console profile events from the connection
   // to the front so that the UI can listen.
   CONNECTION_PIPE_EVENTS.forEach(eventName => this._connection.on(eventName, () => this.emit.apply(this, arguments)));
 }
 
 PerformanceFront.prototype = {
 
   /**
    * Manually begins a recording session and creates a RecordingModel.
    * Calls the underlying PerformanceActorsConnection's startRecording method.
    *
    * @param object options
    *        An options object to pass to the actors. Supported properties are
-   *        `withTicks`, `withMemory` and `withAllocations`, `probability` and `maxLogLength`.
+   *        `withTicks`, `withMemory` and `withAllocations`,
+   *        `probability` and `maxLogLength`.
    * @return object
    *         A promise that is resolved once recording has started.
    */
   startRecording: function (options) {
     return this._connection.startRecording(options);
   },
 
   /**
@@ -653,31 +657,43 @@ PerformanceFront.prototype = {
    * @return RecordingModel
    *         Returns the same model, populated with the profiling data.
    */
   stopRecording: function (model) {
     return this._connection.stopRecording(model);
   },
 
   /**
-   * Returns an object indicating if mock actors are being used or not.
+   * Returns an object indicating what server actors are available and
+   * initialized. A falsy value indicates that the server does not support
+   * that feature, or that mock actors were explicitly requested (tests).
    */
-  getMocksInUse: function () {
+  getActorSupport: function () {
     return {
-      memory: this._usingMockMemory,
-      timeline: this._usingMockTimeline
+      memory: this._memorySupported,
+      timeline: this._timelineSupported
     };
+  },
+
+  /**
+   * Returns a boolean indicating whether or not the current performance connection is recording.
+   *
+   * @return Boolean
+   */
+  isRecording: function () {
+    return this._connection.isRecording();
   }
 };
 
 /**
  * Creates an object of configurations based off of preferences for a RecordingModel.
  */
 function getRecordingModelPrefs () {
   return {
+    withMarkers: true,
     withMemory: Services.prefs.getBoolPref("devtools.performance.ui.enable-memory"),
     withTicks: Services.prefs.getBoolPref("devtools.performance.ui.enable-framerate"),
     withAllocations: Services.prefs.getBoolPref("devtools.performance.ui.enable-memory"),
     allocationsSampleProbability: +Services.prefs.getCharPref("devtools.performance.memory.sample-probability"),
     allocationsMaxLogLength: Services.prefs.getIntPref("devtools.performance.memory.max-log-length")
   };
 }
 
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/modules/graphs.js
@@ -0,0 +1,409 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+/**
+ * This file contains the base line graph that all Performance line graphs use.
+ */
+
+const {Cc, Ci, Cu, Cr} = require("chrome");
+
+Cu.import("resource:///modules/devtools/Graphs.jsm");
+Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+
+const { colorUtils: { setAlpha }} = require("devtools/css-color");
+const { getColor } = require("devtools/shared/theme");
+
+loader.lazyRequireGetter(this, "ProfilerGlobal",
+  "devtools/shared/profiler/global");
+loader.lazyRequireGetter(this, "TimelineGlobal",
+  "devtools/shared/timeline/global");
+loader.lazyRequireGetter(this, "MarkersOverview",
+  "devtools/shared/timeline/markers-overview", true);
+loader.lazyRequireGetter(this, "EventEmitter",
+  "devtools/toolkit/event-emitter");
+
+/**
+ * For line graphs
+ */
+const HEIGHT = 35; // px
+const STROKE_WIDTH = 1; // px
+const DAMPEN_VALUES = 0.95;
+const CLIPHEAD_LINE_COLOR = "#666";
+const SELECTION_LINE_COLOR = "#555";
+const SELECTION_BACKGROUND_COLOR_NAME = "highlight-blue";
+const FRAMERATE_GRAPH_COLOR_NAME = "highlight-green";
+const MEMORY_GRAPH_COLOR_NAME = "highlight-blue";
+
+/**
+ * For timeline overview
+ */
+const MARKERS_GRAPH_HEADER_HEIGHT = 14; // px
+const MARKERS_GRAPH_ROW_HEIGHT = 10; // px
+const MARKERS_GROUP_VERTICAL_PADDING = 4; // px
+
+/**
+ * A base class for performance graphs to inherit from.
+ *
+ * @param nsIDOMNode parent
+ *        The parent node holding the overview.
+ * @param string metric
+ *        The unit of measurement for this graph.
+ */
+function PerformanceGraph(parent, metric) {
+  LineGraphWidget.call(this, parent, { metric });
+  this.setTheme();
+}
+
+PerformanceGraph.prototype = Heritage.extend(LineGraphWidget.prototype, {
+  strokeWidth: STROKE_WIDTH,
+  dampenValuesFactor: DAMPEN_VALUES,
+  fixedHeight: HEIGHT,
+  clipheadLineColor: CLIPHEAD_LINE_COLOR,
+  selectionLineColor: SELECTION_LINE_COLOR,
+  withTooltipArrows: false,
+  withFixedTooltipPositions: true,
+
+  /**
+   * Disables selection and empties this graph.
+   */
+  clearView: function() {
+    this.selectionEnabled = false;
+    this.dropSelection();
+    this.setData([]);
+  },
+
+  /**
+   * Sets the theme via `theme` to either "light" or "dark",
+   * and updates the internal styling to match. Requires a redraw
+   * to see the effects.
+   */
+  setTheme: function (theme) {
+    theme = theme || "light";
+    let mainColor = getColor(this.mainColor || "highlight-blue", theme);
+    this.backgroundColor = getColor("body-background", theme);
+    this.strokeColor = mainColor;
+    this.backgroundGradientStart = setAlpha(mainColor, 0.2);
+    this.backgroundGradientEnd = setAlpha(mainColor, 0.2);
+    this.selectionBackgroundColor = setAlpha(getColor(SELECTION_BACKGROUND_COLOR_NAME, theme), 0.25);
+    this.selectionStripesColor = "rgba(255, 255, 255, 0.1)";
+    this.maximumLineColor = setAlpha(mainColor, 0.4);
+    this.averageLineColor = setAlpha(mainColor, 0.7);
+    this.minimumLineColor = setAlpha(mainColor, 0.9);
+  }
+});
+
+/**
+ * Constructor for the framerate graph. Inherits from PerformanceGraph.
+ *
+ * @param nsIDOMNode parent
+ *        The parent node holding the overview.
+ */
+function FramerateGraph(parent) {
+  PerformanceGraph.call(this, parent, ProfilerGlobal.L10N.getStr("graphs.fps"));
+}
+
+FramerateGraph.prototype = Heritage.extend(PerformanceGraph.prototype, {
+  mainColor: FRAMERATE_GRAPH_COLOR_NAME,
+  setPerformanceData: function ({ duration, ticks }, resolution) {
+    this.dataDuration = duration;
+    return this.setDataFromTimestamps(ticks, resolution, duration);
+  }
+});
+
+/**
+ * Constructor for the memory graph. Inherits from PerformanceGraph.
+ *
+ * @param nsIDOMNode parent
+ *        The parent node holding the overview.
+ */
+function MemoryGraph(parent) {
+  PerformanceGraph.call(this, parent, TimelineGlobal.L10N.getStr("graphs.memory"));
+}
+
+MemoryGraph.prototype = Heritage.extend(PerformanceGraph.prototype, {
+  mainColor: MEMORY_GRAPH_COLOR_NAME,
+  setPerformanceData: function ({ duration, memory }) {
+    this.dataDuration = duration;
+    return this.setData(memory);
+  }
+});
+
+function TimelineGraph(parent, blueprint) {
+  MarkersOverview.call(this, parent, blueprint);
+}
+
+TimelineGraph.prototype = Heritage.extend(MarkersOverview.prototype, {
+  headerHeight: MARKERS_GRAPH_HEADER_HEIGHT,
+  rowHeight: MARKERS_GRAPH_ROW_HEIGHT,
+  groupPadding: MARKERS_GROUP_VERTICAL_PADDING,
+  setPerformanceData: MarkersOverview.prototype.setData
+});
+
+/**
+ * Definitions file for GraphsController, indicating the constructor,
+ * selector and other meta for each of the graphs controller by
+ * GraphsController.
+ */
+const GRAPH_DEFINITIONS = {
+  memory: {
+    constructor: MemoryGraph,
+    selector: "#memory-overview",
+  },
+  framerate: {
+    constructor: FramerateGraph,
+    selector: "#time-framerate",
+  },
+  timeline: {
+    constructor: TimelineGraph,
+    selector: "#markers-overview",
+    needsBlueprints: true,
+    primaryLink: true
+  }
+};
+
+/**
+ * A controller for orchestrating the performance's tool overview graphs. Constructs,
+ * syncs, toggles displays and defines the memory, framerate and timeline view.
+ *
+ * @param {object} definition
+ * @param {DOMElement} root
+ * @param {function} getBlueprint
+ * @param {function} getTheme
+ */
+function GraphsController ({ definition, root, getBlueprint, getTheme }) {
+  this._graphs = {};
+  this._enabled = new Set();
+  this._definition = definition || GRAPH_DEFINITIONS;
+  this._root = root;
+  this._getBlueprint = getBlueprint;
+  this._getTheme = getTheme;
+  this._primaryLink = Object.keys(this._definition).filter(name => this._definition[name].primaryLink)[0];
+  this.$ = root.ownerDocument.querySelector.bind(root.ownerDocument);
+
+  EventEmitter.decorate(this);
+  this._onSelecting = this._onSelecting.bind(this);
+}
+
+GraphsController.prototype = {
+
+  /**
+   * Returns the corresponding graph by `graphName`.
+   */
+  get: function (graphName) {
+    return this._graphs[graphName];
+  },
+
+  /**
+   * Iterates through all graphs and renders the data
+   * from a RecordingModel. Takes a resolution value used in
+   * some graphs.
+   * Saves rendering progress as a promise to be consumed by `destroy`,
+   * to wait for cleaning up rendering during destruction.
+   */
+  render: Task.async(function *(recordingData, resolution) {
+    // Get the previous render promise so we don't start rendering
+    // until the previous render cycle completes, which can occur
+    // especially when a recording is finished, and triggers a
+    // fresh rendering at a higher rate
+    yield (this._rendering && this._rendering.promise);
+
+    // Check after yielding to ensure we're not tearing down,
+    // as this can create a race condition in tests
+    if (this._destroyed) {
+      return;
+    }
+
+    this._rendering = Promise.defer();
+    for (let graph of (yield this._getEnabled())) {
+      yield graph.setPerformanceData(recordingData, resolution);
+      this.emit("rendered", graph.graphName);
+    }
+    this._rendering.resolve();
+  }),
+
+  /**
+   * Destroys the underlying graphs.
+   */
+  destroy: Task.async(function *() {
+    let primary = this._getPrimaryLink();
+
+    this._destroyed = true;
+
+    if (primary) {
+      primary.off("selecting", this._onSelecting);
+    }
+
+    // If there was rendering, wait until the most recent render cycle
+    // has finished
+    if (this._rendering) {
+      yield this._rendering.promise;
+    }
+
+    for (let graphName in this._graphs) {
+      yield this._graphs[graphName].destroy();
+    }
+  }),
+
+  /**
+   * Applies the theme to the underlying graphs. Optionally takes
+   * a `redraw` boolean in the options to force redraw.
+   */
+  setTheme: function (options={}) {
+    let theme = options.theme || this._getTheme();
+    for (let graph in this._graphs) {
+      this._graphs[graph].setTheme(theme);
+      this._graphs[graph].refresh({ force: options.redraw });
+    }
+  },
+
+  /**
+   * Sets up the graph, if needed. Returns a promise resolving
+   * to the graph if it is enabled once it's ready, or otherwise returns
+   * null if disabled.
+   */
+  isAvailable: Task.async(function *(graphName) {
+    if (!this._enabled.has(graphName)) {
+      return null;
+    }
+
+    let graph = this.get(graphName);
+
+    if (!graph) {
+      graph = yield this._construct(graphName);
+    }
+
+    yield graph.ready();
+    return graph;
+  }),
+
+  /**
+   * Enable or disable a subgraph controlled by GraphsController.
+   * This determines what graphs are visible and get rendered.
+   */
+  enable: function (graphName, isEnabled) {
+    let el = this.$(this._definition[graphName].selector);
+    el.hidden = !isEnabled;
+
+    // If no status change, just return
+    if (this._enabled.has(graphName) === isEnabled) {
+      return;
+    }
+    if (isEnabled) {
+      this._enabled.add(graphName);
+    } else {
+      this._enabled.delete(graphName);
+    }
+
+    // Invalidate our cache of ready-to-go graphs
+    this._enabledGraphs = null;
+  },
+
+  /**
+   * Disables all graphs controller by the GraphsController, and
+   * also hides the root element. This is a one way switch, and used
+   * when older platforms do not have any timeline data.
+   */
+  disableAll: function () {
+    this._root.hidden = true;
+    // Hide all the subelements
+    Object.keys(this._definition).forEach(graphName => this.enable(graphName, false));
+  },
+
+  /**
+   * Sets a mapped selection on the graph that is the main controller
+   * for keeping the graphs' selections in sync.
+   */
+  setMappedSelection: function (selection, { mapStart, mapEnd }) {
+    return this._getPrimaryLink().setMappedSelection(selection, { mapStart, mapEnd });
+  },
+
+  getMappedSelection: function ({ mapStart, mapEnd }) {
+    return this._getPrimaryLink().getMappedSelection({ mapStart, mapEnd });
+  },
+
+  /**
+   * Drops the selection.
+   */
+  dropSelection: function () {
+    if (this._getPrimaryLink()) {
+      return this._getPrimaryLink().dropSelection();
+    }
+  },
+
+  /**
+   * Makes sure the selection is enabled or disabled in all the graphs.
+   */
+  selectionEnabled: Task.async(function *(enabled) {
+    for (let graph of (yield this._getEnabled())) {
+      graph.selectionEnabled = enabled;
+    }
+  }),
+
+  /**
+   * Creates the graph `graphName` and initializes it.
+   */
+  _construct: Task.async(function *(graphName) {
+    let def = this._definition[graphName];
+    let el = this.$(def.selector);
+    let blueprint = def.needsBlueprints ? this._getBlueprint() : void 0;
+    let graph = this._graphs[graphName] = new def.constructor(el, blueprint);
+    graph.graphName = graphName;
+
+    yield graph.ready();
+
+    // Sync the graphs' animations and selections together
+    if (def.primaryLink) {
+      graph.on("selecting", this._onSelecting);
+    } else {
+      CanvasGraphUtils.linkAnimation(this._getPrimaryLink(), graph);
+      CanvasGraphUtils.linkSelection(this._getPrimaryLink(), graph);
+    }
+
+    this.setTheme();
+    return graph;
+  }),
+
+  /**
+   * Returns the main graph for this collection, that all graphs
+   * are bound to for syncing and selection.
+   */
+  _getPrimaryLink: function () {
+    return this.get(this._primaryLink);
+  },
+
+  /**
+   * Emitted when a selection occurs.
+   */
+  _onSelecting: function () {
+    this.emit("selecting");
+  },
+
+  /**
+   * Resolves to an array with all graphs that are enabled, and
+   * creates them if needed. Different than just iterating over `this._graphs`,
+   * as those could be enabled. Uses caching, as rendering happens many times per second,
+   * compared to how often which graphs/features are changed (rarely).
+   */
+  _getEnabled: Task.async(function *() {
+    if (this._enabledGraphs) {
+      return this._enabledGraphs;
+    }
+    let enabled = [];
+    for (let graphName of this._enabled) {
+      let graph;
+      if (graph = yield this.isAvailable(graphName)) {
+        enabled.push(graph);
+      }
+    }
+    return this._enabledGraphs = enabled;
+  }),
+};
+
+exports.FramerateGraph = FramerateGraph;
+exports.MemoryGraph = MemoryGraph;
+exports.TimelineGraph = TimelineGraph;
+exports.GraphsController = GraphsController;
--- a/browser/devtools/performance/modules/io.js
+++ b/browser/devtools/performance/modules/io.js
@@ -143,13 +143,21 @@ function convertLegacyData (legacyData) 
   let data = {
     label: profilerData.profilerLabel,
     duration: recordingDuration,
     markers: [],
     frames: [],
     memory: [],
     ticks: ticksData,
     allocations: { sites: [], timestamps: [], frames: [], counts: [] },
-    profile: profilerData.profile
+    profile: profilerData.profile,
+    // Fake a configuration object here if there's tick data,
+    // so that it can be rendered
+    configuration: {
+      withTicks: !!ticksData.length,
+      withMarkers: false,
+      withMemory: false,
+      withAllocations: false
+    }
   };
 
   return data;
 }
deleted file mode 100644
--- a/browser/devtools/performance/modules/performance-graphs.js
+++ /dev/null
@@ -1,113 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-"use strict";
-
-/**
- * This file contains the base line graph that all Performance line graphs use.
- */
-
-const {Cc, Ci, Cu, Cr} = require("chrome");
-
-Cu.import("resource:///modules/devtools/Graphs.jsm");
-Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
-
-const { colorUtils: { setAlpha }} = require("devtools/css-color");
-const { getColor } = require("devtools/shared/theme");
-
-loader.lazyRequireGetter(this, "ProfilerGlobal",
-  "devtools/shared/profiler/global");
-loader.lazyRequireGetter(this, "TimelineGlobal",
-  "devtools/shared/timeline/global");
-
-const HEIGHT = 35; // px
-const STROKE_WIDTH = 1; // px
-const DAMPEN_VALUES = 0.95;
-const CLIPHEAD_LINE_COLOR = "#666";
-const SELECTION_LINE_COLOR = "#555";
-const SELECTION_BACKGROUND_COLOR_NAME = "highlight-blue";
-const FRAMERATE_GRAPH_COLOR_NAME = "highlight-green";
-const MEMORY_GRAPH_COLOR_NAME = "highlight-blue";
-
-/**
- * A base class for performance graphs to inherit from.
- *
- * @param nsIDOMNode parent
- *        The parent node holding the overview.
- * @param string metric
- *        The unit of measurement for this graph.
- */
-function PerformanceGraph(parent, metric) {
-  LineGraphWidget.call(this, parent, { metric });
-  this.setTheme();
-}
-
-PerformanceGraph.prototype = Heritage.extend(LineGraphWidget.prototype, {
-  strokeWidth: STROKE_WIDTH,
-  dampenValuesFactor: DAMPEN_VALUES,
-  fixedHeight: HEIGHT,
-  clipheadLineColor: CLIPHEAD_LINE_COLOR,
-  selectionLineColor: SELECTION_LINE_COLOR,
-  withTooltipArrows: false,
-  withFixedTooltipPositions: true,
-
-  /**
-   * Disables selection and empties this graph.
-   */
-  clearView: function() {
-    this.selectionEnabled = false;
-    this.dropSelection();
-    this.setData([]);
-  },
-
-  /**
-   * Sets the theme via `theme` to either "light" or "dark",
-   * and updates the internal styling to match. Requires a redraw
-   * to see the effects.
-   */
-  setTheme: function (theme) {
-    theme = theme || "light";
-    let mainColor = getColor(this.mainColor || "highlight-blue", theme);
-    this.backgroundColor = getColor("body-background", theme);
-    this.strokeColor = mainColor;
-    this.backgroundGradientStart = setAlpha(mainColor, 0.2);
-    this.backgroundGradientEnd = setAlpha(mainColor, 0.2);
-    this.selectionBackgroundColor = setAlpha(getColor(SELECTION_BACKGROUND_COLOR_NAME, theme), 0.25);
-    this.selectionStripesColor = "rgba(255, 255, 255, 0.1)";
-    this.maximumLineColor = setAlpha(mainColor, 0.4);
-    this.averageLineColor = setAlpha(mainColor, 0.7);
-    this.minimumLineColor = setAlpha(mainColor, 0.9);
-  }
-});
-
-/**
- * Constructor for the framerate graph. Inherits from PerformanceGraph.
- *
- * @param nsIDOMNode parent
- *        The parent node holding the overview.
- */
-function FramerateGraph(parent) {
-  PerformanceGraph.call(this, parent, ProfilerGlobal.L10N.getStr("graphs.fps"));
-}
-
-FramerateGraph.prototype = Heritage.extend(PerformanceGraph.prototype, {
-  mainColor: FRAMERATE_GRAPH_COLOR_NAME
-});
-
-exports.FramerateGraph = FramerateGraph;
-
-/**
- * Constructor for the memory graph. Inherits from PerformanceGraph.
- *
- * @param nsIDOMNode parent
- *        The parent node holding the overview.
- */
-function MemoryGraph(parent) {
-  PerformanceGraph.call(this, parent, TimelineGlobal.L10N.getStr("graphs.memory"));
-}
-
-MemoryGraph.prototype = Heritage.extend(PerformanceGraph.prototype, {
-  mainColor: MEMORY_GRAPH_COLOR_NAME
-});
-
-exports.MemoryGraph = MemoryGraph;
--- a/browser/devtools/performance/modules/recording-model.js
+++ b/browser/devtools/performance/modules/recording-model.js
@@ -17,16 +17,17 @@ loader.lazyRequireGetter(this, "Recordin
  * a recording as 'in progress' or 'finished'.
  */
 
 const RecordingModel = function (options={}) {
   this._label = options.label || "";
   this._console = options.console || false;
 
   this._configuration = {
+    withMarkers: options.withMarkers || false,
     withTicks: options.withTicks || false,
     withMemory: options.withMemory || false,
     withAllocations: options.withAllocations || false,
     allocationsSampleProbability: options.allocationsSampleProbability || 0,
     allocationsMaxLogLength: options.allocationsMaxLogLength || 0,
     bufferSize: options.bufferSize || 0,
     sampleFrequency: options.sampleFrequency || 1
   };
@@ -65,16 +66,17 @@ RecordingModel.prototype = {
     this._label = recordingData.label || "";
     this._duration = recordingData.duration;
     this._markers = recordingData.markers;
     this._frames = recordingData.frames;
     this._memory = recordingData.memory;
     this._ticks = recordingData.ticks;
     this._allocations = recordingData.allocations;
     this._profile = recordingData.profile;
+    this._configuration = recordingData.configuration || {};
   }),
 
   /**
    * Saves the current recording to a file.
    *
    * @param nsILocalFile file
    *        The file to stream the data into.
    */
@@ -221,17 +223,18 @@ RecordingModel.prototype = {
     let label = this.getLabel();
     let duration = this.getDuration();
     let markers = this.getMarkers();
     let frames = this.getFrames();
     let memory = this.getMemory();
     let ticks = this.getTicks();
     let allocations = this.getAllocations();
     let profile = this.getProfile();
-    return { label, duration, markers, frames, memory, ticks, allocations, profile };
+    let configuration = this.getConfiguration();
+    return { label, duration, markers, frames, memory, ticks, allocations, profile, configuration };
   },
 
   /**
    * Returns a boolean indicating whether or not this recording model
    * was imported via file.
    */
   isImported: function () {
     return this._imported;
@@ -258,51 +261,58 @@ RecordingModel.prototype = {
    */
   addTimelineData: function (eventName, ...data) {
     // If this model isn't currently recording,
     // ignore the timeline data.
     if (!this._recording) {
       return;
     }
 
+    let config = this.getConfiguration();
+
     switch (eventName) {
       // Accumulate timeline markers into an array. Furthermore, the timestamps
       // do not have a zero epoch, so offset all of them by the start time.
       case "markers": {
+        if (!config.withMarkers) { break; }
         let [markers] = data;
         RecordingUtils.offsetMarkerTimes(markers, this._timelineStartTime);
         Array.prototype.push.apply(this._markers, markers);
         break;
       }
       // Accumulate stack frames into an array.
       case "frames": {
+        if (!config.withMarkers) { break; }
         let [, frames] = data;
         Array.prototype.push.apply(this._frames, frames);
         break;
       }
       // Accumulate memory measurements into an array. Furthermore, the timestamp
       // does not have a zero epoch, so offset it by the actor's start time.
       case "memory": {
+        if (!config.withMemory) { break; }
         let [currentTime, measurement] = data;
         this._memory.push({
           delta: currentTime - this._timelineStartTime,
           value: measurement.total / 1024 / 1024
         });
         break;
       }
       // Save the accumulated refresh driver ticks.
       case "ticks": {
+        if (!config.withTicks) { break; }
         let [, timestamps] = data;
         this._ticks = timestamps;
         break;
       }
       // Accumulate allocation sites into an array. Furthermore, the timestamps
       // do not have a zero epoch, and are microseconds instead of milliseconds,
       // so offset all of them by the start time, also converting from µs to ms.
       case "allocations": {
+        if (!config.withAllocations) { break; }
         let [{ sites, timestamps, frames, counts }] = data;
         let timeOffset = this._memoryStartTime * 1000;
         let timeScale = 1000;
         RecordingUtils.offsetAndScaleTimestamps(timestamps, timeOffset, timeScale);
         Array.prototype.push.apply(this._allocations.sites, sites);
         Array.prototype.push.apply(this._allocations.timestamps, timestamps);
         Array.prototype.push.apply(this._allocations.frames, frames);
         Array.prototype.push.apply(this._allocations.counts, counts);
--- a/browser/devtools/performance/moz.build
+++ b/browser/devtools/performance/moz.build
@@ -1,16 +1,16 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXTRA_JS_MODULES.devtools.performance += [
     'modules/compatibility.js',
     'modules/front.js',
+    'modules/graphs.js',
     'modules/io.js',
-    'modules/performance-graphs.js',
     'modules/recording-model.js',
     'modules/recording-utils.js',
     'panel.js'
 ]
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/browser/devtools/performance/panel.js
+++ b/browser/devtools/performance/panel.js
@@ -29,25 +29,28 @@ PerformancePanel.prototype = {
    *
    * @return object
    *         A promise that is resolved when the Performance tool
    *         completes opening.
    */
   open: Task.async(function*() {
     this.panelWin.gToolbox = this._toolbox;
     this.panelWin.gTarget = this.target;
+    this._onRecordingStartOrStop = this._onRecordingStartOrStop.bind(this);
 
     // Connection is already created in the toolbox; reuse
     // the same connection.
     this._connection = this.panelWin.gToolbox.getPerformanceActorsConnection();
     // The toolbox will also open the connection, but attempt to open it again
     // incase it's still in the process of opening.
     yield this._connection.open();
 
     this.panelWin.gFront = new PerformanceFront(this._connection);
+    this.panelWin.gFront.on("recording-started", this._onRecordingStartOrStop);
+    this.panelWin.gFront.on("recording-stopped", this._onRecordingStartOrStop);
 
     yield this.panelWin.startupPerformance();
 
     this.isReady = true;
     this.emit("ready");
     return this;
   }),
 
@@ -56,13 +59,24 @@ PerformancePanel.prototype = {
   get target() this._toolbox.target,
 
   destroy: Task.async(function*() {
     // Make sure this panel is not already destroyed.
     if (this._destroyed) {
       return;
     }
 
+    this.panelWin.gFront.off("recording-started", this._onRecordingStartOrStop);
+    this.panelWin.gFront.off("recording-stopped", this._onRecordingStartOrStop);
     yield this.panelWin.shutdownPerformance();
     this.emit("destroyed");
     this._destroyed = true;
-  })
+  }),
+
+  _onRecordingStartOrStop: function () {
+    let front = this.panelWin.gFront;
+    if (front.isRecording()) {
+      this._toolbox.highlightTool("performance");
+    } else {
+      this._toolbox.unhighlightTool("performance");
+    }
+  }
 };
--- a/browser/devtools/performance/performance-controller.js
+++ b/browser/devtools/performance/performance-controller.js
@@ -22,22 +22,18 @@ devtools.lazyRequireGetter(this, "TreeWi
 devtools.lazyRequireGetter(this, "TIMELINE_BLUEPRINT",
   "devtools/shared/timeline/global", true);
 devtools.lazyRequireGetter(this, "L10N",
   "devtools/shared/profiler/global", true);
 devtools.lazyRequireGetter(this, "RecordingUtils",
   "devtools/performance/recording-utils", true);
 devtools.lazyRequireGetter(this, "RecordingModel",
   "devtools/performance/recording-model", true);
-devtools.lazyRequireGetter(this, "FramerateGraph",
-  "devtools/performance/performance-graphs", true);
-devtools.lazyRequireGetter(this, "MemoryGraph",
-  "devtools/performance/performance-graphs", true);
-devtools.lazyRequireGetter(this, "MarkersOverview",
-  "devtools/shared/timeline/markers-overview", true);
+devtools.lazyRequireGetter(this, "GraphsController",
+  "devtools/performance/graphs", true);
 devtools.lazyRequireGetter(this, "Waterfall",
   "devtools/shared/timeline/waterfall", true);
 devtools.lazyRequireGetter(this, "MarkerDetails",
   "devtools/shared/timeline/marker-details", true);
 devtools.lazyRequireGetter(this, "CallView",
   "devtools/shared/profiler/tree-view", true);
 devtools.lazyRequireGetter(this, "ThreadNode",
   "devtools/shared/profiler/tree-model", true);
@@ -47,18 +43,16 @@ devtools.lazyRequireGetter(this, "JITOpt
   "devtools/shared/profiler/jit", true);
 devtools.lazyRequireGetter(this, "OptionsView",
   "devtools/shared/options-view", true);
 devtools.lazyRequireGetter(this, "FlameGraphUtils",
   "devtools/shared/widgets/FlameGraph", true);
 devtools.lazyRequireGetter(this, "FlameGraph",
   "devtools/shared/widgets/FlameGraph", true);
 
-devtools.lazyImporter(this, "CanvasGraphUtils",
-  "resource:///modules/devtools/Graphs.jsm");
 devtools.lazyImporter(this, "SideMenuWidget",
   "resource:///modules/devtools/SideMenuWidget.jsm");
 devtools.lazyImporter(this, "PluralForm",
   "resource://gre/modules/PluralForm.jsm");
 
 const BRANCH_NAME = "devtools.performance.ui.";
 
 // Events emitted by various objects in the panel.
@@ -286,16 +280,17 @@ let PerformanceController = {
   },
 
   /**
    * Starts recording with the PerformanceFront. Emits `EVENTS.RECORDING_STARTED`
    * when the front has started to record.
    */
   startRecording: Task.async(function *() {
     let options = {
+      withMarkers: true,
       withMemory: this.getOption("enable-memory"),
       withTicks: this.getOption("enable-framerate"),
       withAllocations: this.getOption("enable-memory"),
       allocationsSampleProbability: this.getPref("memory-sample-probability"),
       allocationsMaxLogLength: this.getPref("memory-max-log-length"),
       bufferSize: this.getPref("profiler-buffer-size"),
       sampleFrequency: this.getPref("profiler-sample-frequency")
     };
@@ -465,16 +460,52 @@ let PerformanceController = {
 
   /**
    * Returns the internal store of recording models.
    */
   getRecordings: function () {
     return this._recordings;
   },
 
+  /**
+   * Utility method taking the currently selected recording item's features, or optionally passed
+   * in recording item, as well as the actor support on the server, returning a boolean
+   * indicating if the requirements pass or not. Used to toggle features' visibility mostly.
+   *
+   * @option {Array<string>} features
+   *         An array of strings indicating what configuration is needed on the recording
+   *         model, like `withTicks`, or `withMemory`.
+   * @option {Array<string>} actors
+   *         An array of strings indicating what actors must exist.
+   * @option {boolean} isRecording
+   *         A boolean indicating whether the recording must be either recording or not
+   *         recording. Setting to undefined will allow either state.
+   * @param {RecordingModel} recording
+   *        An optional recording model to use instead of the currently selected.
+   *
+   * @return boolean
+   */
+  isFeatureSupported: function ({ features, actors, isRecording: shouldBeRecording }, recording) {
+    recording = recording || this.getCurrentRecording();
+    let recordingConfig = recording ? recording.getConfiguration() : {};
+    let currentRecordingState = recording ? recording.isRecording() : void 0;
+    let actorsSupported = gFront.getActorSupport();
+
+    if (shouldBeRecording != null && shouldBeRecording !== currentRecordingState) {
+      return false;
+    }
+    if (actors && !actors.every(a => actorsSupported[a])) {
+      return false;
+    }
+    if (features && !features.every(f => recordingConfig[f])) {
+      return false;
+    }
+    return true;
+  },
+
   toString: () => "[object PerformanceController]"
 };
 
 /**
  * Convenient way of emitting events from the controller.
  */
 EventEmitter.decorate(PerformanceController);
 
--- a/browser/devtools/performance/performance.xul
+++ b/browser/devtools/performance/performance.xul
@@ -97,32 +97,37 @@
                          popup="performance-filter-menupopup"
                          class="devtools-toolbarbutton"
                          tooltiptext="&profilerUI.options.filter.tooltiptext;"/>
         </hbox>
         <hbox id="performance-toolbar-controls-detail-views" class="devtools-toolbarbutton-group">
           <toolbarbutton id="select-waterfall-view"
                          class="devtools-toolbarbutton devtools-button"
                          label="&profilerUI.toolbar.waterfall;"
+                         hidden="true"
                          data-view="waterfall" />
           <toolbarbutton id="select-js-calltree-view"
                          class="devtools-toolbarbutton devtools-button"
                          label="&profilerUI.toolbar.js-calltree;"
+                         hidden="true"
                          data-view="js-calltree" />
           <toolbarbutton id="select-js-flamegraph-view"
                          class="devtools-toolbarbutton devtools-button"
                          label="&profilerUI.toolbar.js-flamegraph;"
+                         hidden="true"
                          data-view="js-flamegraph" />
           <toolbarbutton id="select-memory-calltree-view"
                          class="devtools-toolbarbutton devtools-button"
                          label="&profilerUI.toolbar.memory-calltree1;"
+                         hidden="true"
                          data-view="memory-calltree" />
           <toolbarbutton id="select-memory-flamegraph-view"
                          class="devtools-toolbarbutton devtools-button"
                          label="&profilerUI.toolbar.memory-flamegraph1;"
+                         hidden="true"
                          data-view="memory-flamegraph" />
         </hbox>
         <spacer flex="1"></spacer>
         <hbox id="performance-toolbar-control-options" class="devtools-toolbarbutton-group">
           <toolbarbutton id="performance-options-button"
                          class="devtools-toolbarbutton devtools-option-toolbarbutton"
                          popup="performance-options-menupopup"
                          tooltiptext="&profilerUI.options.gear.tooltiptext;"/>
--- a/browser/devtools/performance/test/browser.ini
+++ b/browser/devtools/performance/test/browser.ini
@@ -6,19 +6,18 @@ support-files =
   doc_innerHTML.html
   doc_simple-test.html
   head.js
 
 # Commented out tests are profiler tests
 # that need to be moved over to performance tool
 
 [browser_perf-aaa-run-first-leaktest.js]
-
+[browser_markers-gc.js]
 [browser_markers-parse-html.js]
-
 [browser_perf-allocations-to-samples.js]
 [browser_perf-compatibility-01.js]
 [browser_perf-compatibility-02.js]
 [browser_perf-compatibility-03.js]
 [browser_perf-compatibility-04.js]
 [browser_perf-compatibility-05.js]
 [browser_perf-clear-01.js]
 [browser_perf-clear-02.js]
@@ -38,27 +37,29 @@ support-files =
 [browser_perf-details-flamegraph-render.js]
 [browser_perf-details-memory-calltree-render.js]
 [browser_perf-details-memory-flamegraph-render.js]
 [browser_perf-details-waterfall-render.js]
 [browser_perf-details-01.js]
 [browser_perf-details-02.js]
 [browser_perf-details-03.js]
 [browser_perf-details-04.js]
+[browser_perf-details-05.js]
 [browser_perf-events-calltree.js]
 [browser_perf-front-basic-profiler-01.js]
 [browser_perf-front-basic-timeline-01.js]
 #[browser_perf-front-profiler-01.js] bug 1077464
 [browser_perf-front-profiler-02.js]
 [browser_perf-front-profiler-03.js]
 [browser_perf-front-profiler-04.js]
 #[browser_perf-front-profiler-05.js] bug 1077464
 #[browser_perf-front-profiler-06.js]
 [browser_perf-front-01.js]
 [browser_perf-front-02.js]
+[browser_perf-highlighted.js]
 [browser_perf-jit-view-01.js]
 [browser_perf-jit-view-02.js]
 [browser_perf-jit-model-01.js]
 [browser_perf-jit-model-02.js]
 [browser_perf-options-01.js]
 [browser_perf-options-02.js]
 [browser_perf-options-invert-call-tree-01.js]
 [browser_perf-options-invert-call-tree-02.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/test/browser_markers-gc.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we get a "GarbageCollection" marker.
+ */
+
+const TIME_CLOSE_TO = 10000;
+
+function* spawnTest () {
+  let { target, front } = yield initBackend(SIMPLE_URL);
+  let markers;
+
+  front.on("timeline-data", handler);
+  let model = yield front.startRecording({ withTicks: true });
+
+  // Check async for markers found while GC/CCing between
+  yield waitUntil(() => {
+    forceCC();
+    return !!markers;
+  }, 100);
+
+  front.off("timeline-data", handler);
+  yield front.stopRecording(model);
+
+  info(`Got ${markers.length} markers.`);
+
+  let maxMarkerTime = model._timelineStartTime + model.getDuration() + TIME_CLOSE_TO;
+
+  ok(markers.every(({name}) => name === "GarbageCollection"), "All markers found are GC markers");
+  ok(markers.length > 0, "found atleast one GC marker");
+  ok(markers.every(({start}) => typeof start === "number" && start > 0 && start < maxMarkerTime),
+    "All markers have a start time between the valid range.");
+  ok(markers.every(({end}) => typeof end === "number" && end > 0 && end < maxMarkerTime),
+    "All markers have an end time between the valid range.");
+  ok(markers.every(({causeName}) => typeof causeName === "string"),
+    "All markers have a causeName.");
+
+  yield removeTab(target.tab);
+  finish();
+
+  function handler (_, name, m) {
+    if (name === "markers" && m[0].name === "GarbageCollection") {
+      markers = m;
+    }
+  }
+}
--- a/browser/devtools/performance/test/browser_markers-parse-html.js
+++ b/browser/devtools/performance/test/browser_markers-parse-html.js
@@ -11,17 +11,17 @@ function* getMarkers(front) {
   const { promise, resolve } = Promise.defer();
   const handler = (_, name, markers) => {
     if (name === "markers") {
       resolve(markers);
     }
   };
   front.on("timeline-data", handler);
 
-  yield front.startRecording({ withTicks: true });
+  yield front.startRecording({ withMarkers: true, withTicks: true });
 
   const markers = yield promise;
   front.off("timeline-data", handler);
   yield front.stopRecording();
 
   return markers;
 }
 
--- a/browser/devtools/performance/test/browser_perf-columns-js-calltree.js
+++ b/browser/devtools/performance/test/browser_perf-columns-js-calltree.js
@@ -6,24 +6,23 @@
  */
 function spawnTest () {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, $, $$, DetailsView, JsCallTreeView } = panel.panelWin;
 
   // Enable platform data to show the `busyWait` function in the tree.
   Services.prefs.setBoolPref(PLATFORM_DATA_PREF, true);
 
-  yield DetailsView.selectView("js-calltree");
-  ok(DetailsView.isViewSelected(JsCallTreeView), "The call tree is now selected.");
-
   yield startRecording(panel);
   yield busyWait(1000);
 
   let rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
   yield stopRecording(panel);
+  yield DetailsView.selectView("js-calltree");
+  ok(DetailsView.isViewSelected(JsCallTreeView), "The call tree is now selected.");
   yield rendered;
 
   testCells($, $$, {
     "duration": true,
     "percentage": true,
     "allocations": false,
     "self-duration": true,
     "self-percentage": true,
--- a/browser/devtools/performance/test/browser_perf-columns-memory-calltree.js
+++ b/browser/devtools/performance/test/browser_perf-columns-memory-calltree.js
@@ -6,24 +6,23 @@
  */
 function spawnTest () {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, $, $$, DetailsView, MemoryCallTreeView } = panel.panelWin;
 
   // Enable memory to test.
   Services.prefs.setBoolPref(MEMORY_PREF, true);
 
-  yield DetailsView.selectView("memory-calltree");
-  ok(DetailsView.isViewSelected(MemoryCallTreeView), "The call tree is now selected.");
-
   yield startRecording(panel);
   yield busyWait(1000);
 
   let rendered = once(MemoryCallTreeView, EVENTS.MEMORY_CALL_TREE_RENDERED);
   yield stopRecording(panel);
+  yield DetailsView.selectView("memory-calltree");
+  ok(DetailsView.isViewSelected(MemoryCallTreeView), "The call tree is now selected.");
   yield rendered;
 
   testCells($, $$, {
     "duration": false,
     "percentage": false,
     "allocations": true,
     "self-duration": false,
     "self-percentage": false,
--- a/browser/devtools/performance/test/browser_perf-compatibility-01.js
+++ b/browser/devtools/performance/test/browser_perf-compatibility-01.js
@@ -9,22 +9,23 @@ let WAIT_TIME = 100;
 
 function spawnTest () {
   let { target, front } = yield initBackend(SIMPLE_URL, {
     TEST_MOCK_MEMORY_ACTOR: true,
     TEST_MOCK_TIMELINE_ACTOR: true
   });
   Services.prefs.setBoolPref(MEMORY_PREF, true);
 
-  let { memory, timeline } = front.getMocksInUse();
-  ok(memory, "memory should be mocked.");
-  ok(timeline, "timeline should be mocked.");
+  let { memory, timeline } = front.getActorSupport();
+  ok(!memory, "memory should be mocked.");
+  ok(!timeline, "timeline should be mocked.");
 
   let recording = yield front.startRecording({
     withTicks: true,
+    withMarkers: true,
     withMemory: true,
     withAllocations: true,
     allocationsSampleProbability: +Services.prefs.getCharPref(MEMORY_SAMPLE_PROB_PREF),
     allocationsMaxLogLength: Services.prefs.getIntPref(MEMORY_MAX_LOG_LEN_PREF)
   });
 
   ok(typeof recording._profilerStartTime === "number",
     "The front.startRecording() returns a recording with a profiler start time");
--- a/browser/devtools/performance/test/browser_perf-compatibility-02.js
+++ b/browser/devtools/performance/test/browser_perf-compatibility-02.js
@@ -11,19 +11,19 @@ const WAIT_TIME = 1000;
 let test = Task.async(function*() {
   let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL, "performance", {
     TEST_MOCK_MEMORY_ACTOR: true,
     TEST_MOCK_TIMELINE_ACTOR: true
   });
   Services.prefs.setBoolPref(MEMORY_PREF, true);
   let { EVENTS, $, gFront, PerformanceController, PerformanceView, DetailsView, JsCallTreeView } = panel.panelWin;
 
-  let { memory: memoryMock, timeline: timelineMock } = gFront.getMocksInUse();
-  ok(memoryMock, "memory should be mocked.");
-  ok(timelineMock, "timeline should be mocked.");
+  let { memory: memorySupport, timeline: timelineSupport } = gFront.getActorSupport();
+  ok(!memorySupport, "memory should be mocked.");
+  ok(!timelineSupport, "timeline should be mocked.");
 
   yield startRecording(panel, { waitForOverview: false });
   busyWait(WAIT_TIME); // allow the profiler module to sample some cpu activity
   yield stopRecording(panel, { waitForOverview: false });
 
   let {
     label, duration, markers, frames, memory, ticks, allocations, profile
   } = PerformanceController.getCurrentRecording().getAllData();
--- a/browser/devtools/performance/test/browser_perf-compatibility-03.js
+++ b/browser/devtools/performance/test/browser_perf-compatibility-03.js
@@ -8,22 +8,23 @@
 let WAIT_TIME = 100;
 
 function spawnTest () {
   let { target, front } = yield initBackend(SIMPLE_URL, {
     TEST_MOCK_MEMORY_ACTOR: true
   });
   Services.prefs.setBoolPref(MEMORY_PREF, true);
 
-  let { memory, timeline } = front.getMocksInUse();
-  ok(memory, "memory should be mocked.");
-  ok(!timeline, "timeline should not be mocked.");
+  let { memory, timeline } = front.getActorSupport();
+  ok(!memory, "memory should be mocked.");
+  ok(timeline, "timeline should not be mocked.");
 
   let recording = yield front.startRecording({
     withTicks: true,
+    withMarkers: true,
     withMemory: true,
     withAllocations: true,
     allocationsSampleProbability: +Services.prefs.getCharPref(MEMORY_SAMPLE_PROB_PREF),
     allocationsMaxLogLength: Services.prefs.getIntPref(MEMORY_MAX_LOG_LEN_PREF)
   });
 
   ok(typeof recording._profilerStartTime === "number",
     "The front.startRecording() returns a recording with a profiler start time");
--- a/browser/devtools/performance/test/browser_perf-compatibility-04.js
+++ b/browser/devtools/performance/test/browser_perf-compatibility-04.js
@@ -11,19 +11,19 @@ const WAIT_TIME = 1000;
 let test = Task.async(function*() {
   let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL, "performance", {
     TEST_MOCK_MEMORY_ACTOR: true
   });
   Services.prefs.setBoolPref(MEMORY_PREF, true);
   let { EVENTS, $, gFront, PerformanceController, PerformanceView, DetailsView, WaterfallView } = panel.panelWin;
 
 
-  let { memory: memoryMock, timeline: timelineMock } = gFront.getMocksInUse();
-  ok(memoryMock, "memory should be mocked.");
-  ok(!timelineMock, "timeline should not be mocked.");
+  let { memory: memorySupport, timeline: timelineSupport } = gFront.getActorSupport();
+  ok(!memorySupport, "memory should be mocked.");
+  ok(timelineSupport, "timeline should not be mocked.");
 
   yield startRecording(panel);
   yield busyWait(100);
   yield waitUntil(() => PerformanceController.getCurrentRecording().getTicks().length);
   yield waitUntil(() => PerformanceController.getCurrentRecording().getMemory().length);
   yield waitUntil(() => PerformanceController.getCurrentRecording().getMarkers().length);
   yield stopRecording(panel);
 
--- a/browser/devtools/performance/test/browser_perf-console-record-06.js
+++ b/browser/devtools/performance/test/browser_perf-console-record-06.js
@@ -24,28 +24,26 @@ function spawnTest () {
   is(RecordingsView.selectedItem.attachment, recordings[0],
     "The first console recording should still be selected.");
 
   // Ensure overview is still rendering
   yield once(OverviewView, EVENTS.OVERVIEW_RENDERED);
   yield once(OverviewView, EVENTS.OVERVIEW_RENDERED);
   yield once(OverviewView, EVENTS.OVERVIEW_RENDERED);
 
-  let detailsRendered = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
   yield consoleProfileEnd(panel.panelWin, "rust");
-  yield detailsRendered;
 
   recordings = PerformanceController.getRecordings();
   is(recordings.length, 2, "two recordings found in the performance panel.");
   is(RecordingsView.selectedItem.attachment, recordings[0],
     "The first console recording should still be selected.");
   is(RecordingsView.selectedItem.attachment.isRecording(), false,
     "The first console recording should no longer be recording.");
 
-  detailsRendered = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
+  let detailsRendered = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
   yield consoleProfileEnd(panel.panelWin, "golang");
   yield detailsRendered;
 
   recordings = PerformanceController.getRecordings();
   is(recordings.length, 2, "two recordings found in the performance panel.");
   is(RecordingsView.selectedItem.attachment, recordings[0],
     "The first console recording should still be selected.");
   is(recordings[1].isRecording(), false,
--- a/browser/devtools/performance/test/browser_perf-details-01.js
+++ b/browser/devtools/performance/test/browser_perf-details-01.js
@@ -3,16 +3,19 @@
 
 /**
  * Tests that the details view toggles subviews.
  */
 function spawnTest () {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, $, DetailsView, document: doc } = panel.panelWin;
 
+  yield startRecording(panel);
+  yield stopRecording(panel);
+
   info("views on startup");
   checkViews(DetailsView, doc, "waterfall");
 
   // Select calltree view
   let viewChanged = onceSpread(DetailsView, EVENTS.DETAILS_VIEW_SELECTED);
   command($("toolbarbutton[data-view='js-calltree']"));
   let [_, viewName] = yield viewChanged;
   is(viewName, "js-calltree", "DETAILS_VIEW_SELECTED fired with view name");
--- a/browser/devtools/performance/test/browser_perf-details-02.js
+++ b/browser/devtools/performance/test/browser_perf-details-02.js
@@ -4,26 +4,29 @@
 /**
  * Tests that the details view utility functions work as advertised.
  */
 function spawnTest () {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, DetailsView } = panel.panelWin;
   let { WaterfallView, JsCallTreeView, JsFlameGraphView } = panel.panelWin;
 
+  yield startRecording(panel);
+  yield stopRecording(panel);
+
   ok(DetailsView.isViewSelected(WaterfallView),
     "The waterfall view is selected by default in the details view.");
 
   let selected = DetailsView.whenViewSelected(JsCallTreeView);
   let notified = DetailsView.once(EVENTS.DETAILS_VIEW_SELECTED);
   yield DetailsView.selectView("js-calltree");
   yield Promise.all([selected, notified]);
 
   ok(DetailsView.isViewSelected(JsCallTreeView),
-    "The waterfall view is now selected in the details view.");
+    "The jscalltree view is now selected in the details view.");
 
   selected = DetailsView.whenViewSelected(JsFlameGraphView);
   notified = DetailsView.once(EVENTS.DETAILS_VIEW_SELECTED);
   yield DetailsView.selectView("js-flamegraph");
   yield Promise.all([selected, notified]);
 
   ok(DetailsView.isViewSelected(JsFlameGraphView),
     "The flamegraph view is now selected in the details view.");
--- a/browser/devtools/performance/test/browser_perf-details-03.js
+++ b/browser/devtools/performance/test/browser_perf-details-03.js
@@ -1,38 +1,39 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
- * Tests that the details view hides the memory buttons when `enable-memory` is toggled,
- * and that it switches to default panel if toggling while a memory panel is selected.
+ * Tests that the details view hides the memory buttons when a recording does not
+ * have memory data (withMemory: false), and that when a memory panel is selected,
+ * switching to a panel that does not have memory goes to a default panel instead.
  */
 function spawnTest () {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, PerformanceController, OverviewView, DetailsView } = panel.panelWin;
-  let { $, WaterfallView, MemoryCallTreeView, MemoryFlameGraphView } = panel.panelWin;
+  let { $, RecordingsView, WaterfallView, MemoryCallTreeView, MemoryFlameGraphView } = panel.panelWin;
+
+  Services.prefs.setBoolPref(MEMORY_PREF, false);
+  yield startRecording(panel);
+  yield stopRecording(panel);
 
   ok(DetailsView.isViewSelected(WaterfallView),
     "The waterfall view is selected by default in the details view.");
 
+  Services.prefs.setBoolPref(MEMORY_PREF, true);
   // The toolbar buttons will always be hidden when a recording isn't available,
   // so make sure we have one that's finished.
   yield startRecording(panel);
   yield stopRecording(panel);
 
   let flameBtn = $("toolbarbutton[data-view='memory-flamegraph']");
   let callBtn = $("toolbarbutton[data-view='memory-calltree']");
 
-  Services.prefs.setBoolPref(MEMORY_PREF, false);
-  is(flameBtn.hidden, true, "memory-flamegraph button hidden when enable-memory=false");
-  is(callBtn.hidden, true, "memory-calltree button hidden when enable-memory=false");
-
-  Services.prefs.setBoolPref(MEMORY_PREF, true);
-  is(flameBtn.hidden, false, "memory-flamegraph button shown when enable-memory=true");
-  is(callBtn.hidden, false, "memory-calltree button shown when enable-memory=true");
+  is(flameBtn.hidden, false, "memory-flamegraph button shown when recording has memory data");
+  is(callBtn.hidden, false, "memory-calltree button shown when recording has memory data");
 
   let selected = DetailsView.whenViewSelected(MemoryCallTreeView);
   let notified = DetailsView.once(EVENTS.DETAILS_VIEW_SELECTED);
   DetailsView.selectView("memory-calltree");
   yield Promise.all([selected, notified]);
 
   ok(DetailsView.isViewSelected(MemoryCallTreeView),
     "The memory call tree view can now be selected.");
@@ -40,45 +41,37 @@ function spawnTest () {
   selected = DetailsView.whenViewSelected(MemoryFlameGraphView);
   notified = DetailsView.once(EVENTS.DETAILS_VIEW_SELECTED);
   DetailsView.selectView("memory-flamegraph");
   yield Promise.all([selected, notified]);
 
   ok(DetailsView.isViewSelected(MemoryFlameGraphView),
     "The memory flamegraph view can now be selected.");
 
+  // Select the first recording with no memory data
   selected = DetailsView.whenViewSelected(WaterfallView);
   notified = DetailsView.once(EVENTS.DETAILS_VIEW_SELECTED);
-  Services.prefs.setBoolPref(MEMORY_PREF, false);
+  RecordingsView.selectedIndex = 0;
   yield Promise.all([selected, notified]);
 
   ok(DetailsView.isViewSelected(WaterfallView),
-    "The waterfall view is now selected when toggling off enable-memory when a memory panel is selected.");
+    "The waterfall view is now selected when switching back to a recording that does not have memory data");
+  is(flameBtn.hidden, true, "memory-flamegraph button hidden when recording does not have memory data");
+  is(callBtn.hidden, true, "memory-calltree button hidden when recording does not have memory data");
 
-  Services.prefs.setBoolPref(MEMORY_PREF, true);
+  // Go back to the recording with memory data
+  let render = WaterfallView.once(EVENTS.WATERFALL_RENDERED);
+  RecordingsView.selectedIndex = 1;
+  yield render;
 
   selected = DetailsView.whenViewSelected(MemoryCallTreeView);
   notified = DetailsView.once(EVENTS.DETAILS_VIEW_SELECTED);
   DetailsView.selectView("memory-calltree");
   yield Promise.all([selected, notified]);
 
   ok(DetailsView.isViewSelected(MemoryCallTreeView),
-    "The memory call tree view can be selected again after re-enabling memory.");
-
-  selected = DetailsView.whenViewSelected(MemoryFlameGraphView);
-  notified = DetailsView.once(EVENTS.DETAILS_VIEW_SELECTED);
-  DetailsView.selectView("memory-flamegraph");
-  yield Promise.all([selected, notified]);
-
-  ok(DetailsView.isViewSelected(MemoryFlameGraphView),
-    "The memory flamegraph view can be selected again after re-enabling memory.");
-
-  selected = DetailsView.whenViewSelected(WaterfallView);
-  notified = DetailsView.once(EVENTS.DETAILS_VIEW_SELECTED);
-  Services.prefs.setBoolPref(MEMORY_PREF, false);
-  yield Promise.all([selected, notified]);
-
-  ok(DetailsView.isViewSelected(WaterfallView),
-    "The waterfall view is now selected when toggling off enable-memory when a memory panel is selected.");
+    "The memory call tree view can be selected again after going back to the view with memory data");
+  is(flameBtn.hidden, false, "memory-flamegraph button shown when recording has memory data");
+  is(callBtn.hidden, false, "memory-calltree button shown when recording has memory data");
 
   yield teardown(panel);
   finish();
 }
--- a/browser/devtools/performance/test/browser_perf-details-04.js
+++ b/browser/devtools/performance/test/browser_perf-details-04.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that the details view hides the toolbar buttons when a recording
  * doesn't exist or is in progress.
  */
 function spawnTest () {
   let { panel } = yield initPerformance(SIMPLE_URL);
-  let { EVENTS, $, $$, PerformanceController, RecordingsView, DetailsView } = panel.panelWin;
+  let { EVENTS, $, $$, PerformanceController, RecordingsView, WaterfallView } = panel.panelWin;
 
   let waterfallBtn = $("toolbarbutton[data-view='waterfall']");
   let jsFlameBtn = $("toolbarbutton[data-view='js-flamegraph']");
   let jsCallBtn = $("toolbarbutton[data-view='js-calltree']");
   let memFlameBtn = $("toolbarbutton[data-view='memory-flamegraph']");
   let memCallBtn = $("toolbarbutton[data-view='memory-calltree']");
 
   is(waterfallBtn.hidden, true, "waterfall button hidden when tool starts.");
@@ -41,18 +41,19 @@ function spawnTest () {
 
   is(waterfallBtn.hidden, true, "waterfall button hidden when another recording starts.");
   is(jsFlameBtn.hidden, true, "js-flamegraph button hidden when another recording starts.");
   is(jsCallBtn.hidden, true, "js-calltree button hidden when another recording starts.");
   is(memFlameBtn.hidden, true, "memory-flamegraph button hidden when another recording starts.");
   is(memCallBtn.hidden, true, "memory-calltree button hidden when another recording starts.");
 
   let select = once(PerformanceController, EVENTS.RECORDING_SELECTED);
+  let render = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
   mousedown(panel.panelWin, $$(".recording-item")[0]);
-  yield select;
+  yield Promise.all([select, render]);
 
   is(RecordingsView.selectedIndex, 0,
     "The first recording was selected again.");
 
   is(waterfallBtn.hidden, false, "waterfall button visible when first recording selected.");
   is(jsFlameBtn.hidden, false, "js-flamegraph button visible when first recording selected.");
   is(jsCallBtn.hidden, false, "js-calltree button visible when first recording selected.");
   is(memFlameBtn.hidden, true, "memory-flamegraph button hidden when first recording selected.");
@@ -66,17 +67,19 @@ function spawnTest () {
     "The second recording was selected again.");
 
   is(waterfallBtn.hidden, true, "waterfall button still hidden when second recording selected.");
   is(jsFlameBtn.hidden, true, "js-flamegraph button still hidden when second recording selected.");
   is(jsCallBtn.hidden, true, "js-calltree button still hidden when second recording selected.");
   is(memFlameBtn.hidden, true, "memory-flamegraph button still hidden when second recording selected.");
   is(memCallBtn.hidden, true, "memory-calltree button still hidden when second recording selected.");
 
+  render = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
   yield stopRecording(panel);
+  yield render;
 
   is(RecordingsView.selectedIndex, 1,
     "The second recording is still selected.");
 
   is(waterfallBtn.hidden, false, "waterfall button visible when second recording finished.");
   is(jsFlameBtn.hidden, false, "js-flamegraph button visible when second recording finished.");
   is(jsCallBtn.hidden, false, "js-calltree button visible when second recording finished.");
   is(memFlameBtn.hidden, true, "memory-flamegraph button hidden when second recording finished.");
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/test/browser_perf-details-05.js
@@ -0,0 +1,38 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the details view utility functions work as advertised.
+ */
+function spawnTest () {
+  let { panel } = yield initPerformance(SIMPLE_URL);
+  let { EVENTS, DetailsView } = panel.panelWin;
+  let { PerformanceController, WaterfallView, JsCallTreeView } = panel.panelWin;
+
+  yield startRecording(panel);
+  yield stopRecording(panel);
+
+  ok(DetailsView.isViewSelected(WaterfallView),
+    "The waterfall view is selected by default in the details view.");
+
+  let selected = DetailsView.whenViewSelected(JsCallTreeView);
+  let notified = DetailsView.once(EVENTS.DETAILS_VIEW_SELECTED);
+  yield DetailsView.selectView("js-calltree");
+  yield Promise.all([selected, notified]);
+
+  ok(DetailsView.isViewSelected(JsCallTreeView),
+    "The jscalltree view is now selected in the details view.");
+
+  yield PerformanceController.clearRecordings();
+
+  yield startRecording(panel);
+  let render = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
+  yield stopRecording(panel);
+  yield render;
+
+  ok(DetailsView.isViewSelected(JsCallTreeView),
+    "The jscalltree view is still selected in the details view");
+
+  yield teardown(panel);
+  finish();
+}
--- a/browser/devtools/performance/test/browser_perf-details-calltree-render.js
+++ b/browser/devtools/performance/test/browser_perf-details-calltree-render.js
@@ -3,24 +3,23 @@
 
 /**
  * Tests that the call tree view renders content after recording.
  */
 function spawnTest () {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, DetailsView, JsCallTreeView } = panel.panelWin;
 
+  yield startRecording(panel);
+  yield busyWait(100);
+  yield stopRecording(panel);
+
+  let rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
   yield DetailsView.selectView("js-calltree");
   ok(DetailsView.isViewSelected(JsCallTreeView), "The call tree is now selected.");
-
-  yield startRecording(panel);
-  yield busyWait(100);
-
-  let rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
-  yield stopRecording(panel);
   yield rendered;
 
   ok(true, "JsCallTreeView rendered after recording is stopped.");
 
   yield startRecording(panel);
   yield busyWait(100);
 
   rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
--- a/browser/devtools/performance/test/browser_perf-details-flamegraph-render.js
+++ b/browser/devtools/performance/test/browser_perf-details-flamegraph-render.js
@@ -3,24 +3,23 @@
 
 /**
  * Tests that the flamegraph view renders content after recording.
  */
 function spawnTest () {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, DetailsView, JsFlameGraphView } = panel.panelWin;
 
+  yield startRecording(panel);
+  yield busyWait(100);
+  yield stopRecording(panel);
+
+  let rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
   yield DetailsView.selectView("js-flamegraph");
   ok(DetailsView.isViewSelected(JsFlameGraphView), "The flamegraph is now selected.");
-
-  yield startRecording(panel);
-  yield busyWait(100);
-
-  let rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
-  yield stopRecording(panel);
   yield rendered;
 
   ok(true, "JsFlameGraphView rendered after recording is stopped.");
 
   yield startRecording(panel);
   yield busyWait(100);
 
   rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
--- a/browser/devtools/performance/test/browser_perf-details-memory-calltree-render.js
+++ b/browser/devtools/performance/test/browser_perf-details-memory-calltree-render.js
@@ -6,24 +6,23 @@
  */
 function spawnTest () {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, DetailsView, MemoryCallTreeView } = panel.panelWin;
 
   // Enable memory to test.
   Services.prefs.setBoolPref(MEMORY_PREF, true);
 
+  yield startRecording(panel);
+  yield busyWait(100);
+  yield stopRecording(panel);
+
+  let rendered = once(MemoryCallTreeView, EVENTS.MEMORY_CALL_TREE_RENDERED);
   yield DetailsView.selectView("memory-calltree");
   ok(DetailsView.isViewSelected(MemoryCallTreeView), "The call tree is now selected.");
-
-  yield startRecording(panel);
-  yield busyWait(100);
-
-  let rendered = once(MemoryCallTreeView, EVENTS.MEMORY_CALL_TREE_RENDERED);
-  yield stopRecording(panel);
   yield rendered;
 
   ok(true, "MemoryCallTreeView rendered after recording is stopped.");
 
   yield startRecording(panel);
   yield busyWait(100);
 
   rendered = once(MemoryCallTreeView, EVENTS.MEMORY_CALL_TREE_RENDERED);
--- a/browser/devtools/performance/test/browser_perf-details-memory-flamegraph-render.js
+++ b/browser/devtools/performance/test/browser_perf-details-memory-flamegraph-render.js
@@ -6,24 +6,23 @@
  */
 function spawnTest () {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, DetailsView, MemoryFlameGraphView } = panel.panelWin;
 
   // Enable memory to test.
   Services.prefs.setBoolPref(MEMORY_PREF, true);
 
+  yield startRecording(panel);
+  yield busyWait(100);
+  yield stopRecording(panel);
+
+  let rendered = once(MemoryFlameGraphView, EVENTS.MEMORY_FLAMEGRAPH_RENDERED);
   yield DetailsView.selectView("memory-flamegraph");
   ok(DetailsView.isViewSelected(MemoryFlameGraphView), "The flamegraph is now selected.");
-
-  yield startRecording(panel);
-  yield busyWait(100);
-
-  let rendered = once(MemoryFlameGraphView, EVENTS.MEMORY_FLAMEGRAPH_RENDERED);
-  yield stopRecording(panel);
   yield rendered;
 
   ok(true, "MemoryFlameGraphView rendered after recording is stopped.");
 
   yield startRecording(panel);
   yield busyWait(100);
 
   rendered = once(MemoryFlameGraphView, EVENTS.MEMORY_FLAMEGRAPH_RENDERED);
--- a/browser/devtools/performance/test/browser_perf-details-waterfall-render.js
+++ b/browser/devtools/performance/test/browser_perf-details-waterfall-render.js
@@ -3,24 +3,23 @@
 
 /**
  * Tests that the waterfall view renders content after recording.
  */
 function spawnTest () {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, PerformanceController, DetailsView, WaterfallView } = panel.panelWin;
 
-  ok(DetailsView.isViewSelected(WaterfallView),
-    "The waterfall view is selected by default in the details view.");
-
   yield startRecording(panel);
   yield waitUntil(() => PerformanceController.getCurrentRecording().getMarkers().length);
 
   let rendered = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
   yield stopRecording(panel);
+  ok(DetailsView.isViewSelected(WaterfallView),
+    "The waterfall view is selected by default in the details view.");
   yield rendered;
 
   ok(true, "WaterfallView rendered after recording is stopped.");
 
   yield startRecording(panel);
   yield waitUntil(() => PerformanceController.getCurrentRecording().getMarkers().length);
 
   rendered = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
--- a/browser/devtools/performance/test/browser_perf-front-02.js
+++ b/browser/devtools/performance/test/browser_perf-front-02.js
@@ -5,17 +5,17 @@
  * Test that timeline and memory actors can be started multiple times to get
  * different start times for different recording sessions.
  */
 
 let WAIT_TIME = 1000;
 
 function spawnTest () {
   let { target, front } = yield initBackend(SIMPLE_URL);
-  let config = { withMemory: true, withTicks: true };
+  let config = { withMarkers: true, withMemory: true, withTicks: true };
 
   yield front._request("memory", "attach");
 
   let timelineStart1 = yield front._request("timeline", "start", config);
   let memoryStart1 = yield front._request("memory", "startRecordingAllocations");
   let timelineStart2 = yield front._request("timeline", "start", config);
   let memoryStart2 = yield front._request("memory", "startRecordingAllocations");
   let timelineStop = yield front._request("timeline", "stop");
--- a/browser/devtools/performance/test/browser_perf-front-basic-timeline-01.js
+++ b/browser/devtools/performance/test/browser_perf-front-basic-timeline-01.js
@@ -20,17 +20,17 @@ function spawnTest () {
   let deferreds = {
     markers: Promise.defer(),
     memory: Promise.defer(),
     ticks: Promise.defer()
   };
 
   front.on("timeline-data", handler);
 
-  yield front.startRecording({ withMemory: true, withTicks: true });
+  yield front.startRecording({ withMarkers: true, withMemory: true, withTicks: true });
   yield Promise.all(Object.keys(deferreds).map(type => deferreds[type].promise));
   yield front.stopRecording();
   front.off("timeline-data", handler);
 
   is(counters.markers.length, 1, "one marker event fired.");
   is(counters.memory.length, 3, "three memory events fired.");
   is(counters.ticks.length, 3, "three ticks events fired.");
 
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/test/browser_perf-highlighted.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the toolbox tab for performance is highlighted when recording,
+ * whether already loaded, or via console.profile with an unloaded performance tools.
+ */
+
+let { getPerformanceActorsConnection } = devtools.require("devtools/performance/front");
+
+function spawnTest () {
+  let profilerConnected = waitForProfilerConnection();
+  let { target, toolbox, console } = yield initConsole(SIMPLE_URL);
+  yield profilerConnected;
+  let connection = getPerformanceActorsConnection(target);
+  let tab = toolbox.doc.getElementById("toolbox-tab-performance");
+
+  let profileStart = once(connection, "console-profile-start");
+  console.profile("rust");
+  yield profileStart;
+
+  ok(tab.hasAttribute("highlighted"),
+    "performance tab is highlighted during recording from console.profile when unloaded");
+
+  let profileEnd = once(connection, "console-profile-end");
+  console.profileEnd("rust");
+  yield profileEnd;
+
+  ok(!tab.hasAttribute("highlighted"),
+    "performance tab is no longer highlighted when console.profile recording finishes");
+
+  yield gDevTools.showToolbox(target, "performance");
+  let panel = toolbox.getCurrentPanel();
+  let { panelWin: { PerformanceController, RecordingsView }} = panel;
+
+  yield startRecording(panel);
+
+  ok(tab.hasAttribute("highlighted"),
+    "performance tab is highlighted during recording while in performance tool");
+
+  yield stopRecording(panel);
+
+  ok(!tab.hasAttribute("highlighted"),
+    "performance tab is no longer highlighted when recording finishes");
+
+  yield teardown(panel);
+  finish();
+}
--- a/browser/devtools/performance/test/browser_perf-options-enable-framerate.js
+++ b/browser/devtools/performance/test/browser_perf-options-enable-framerate.js
@@ -3,30 +3,30 @@
 
 /**
  * Tests that `enable-framerate` toggles the visibility of the fps graph,
  * as well as enabling ticks data on the PerformanceFront.
  */
 function spawnTest () {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, PerformanceController, $ } = panel.panelWin;
-
   Services.prefs.setBoolPref(FRAMERATE_PREF, false);
-  ok($("#time-framerate").hidden, "fps graph is hidden when ticks disabled");
 
   yield startRecording(panel);
   yield stopRecording(panel);
 
   is(PerformanceController.getCurrentRecording().getConfiguration().withTicks, false,
     "PerformanceFront started without ticks recording.");
+  ok($("#time-framerate").hidden, "fps graph is hidden when ticks disabled");
 
   Services.prefs.setBoolPref(FRAMERATE_PREF, true);
-  ok(!$("#time-framerate").hidden, "fps graph is not hidden when ticks enabled");
+  ok($("#time-framerate").hidden, "fps graph is still hidden if recording does not contain ticks.");
 
   yield startRecording(panel);
   yield stopRecording(panel);
 
+  ok(!$("#time-framerate").hidden, "fps graph is not hidden when ticks enabled before recording");
   is(PerformanceController.getCurrentRecording().getConfiguration().withTicks, true,
     "PerformanceFront started with ticks recording.");
 
   yield teardown(panel);
   finish();
 }
--- a/browser/devtools/performance/test/browser_perf-options-enable-memory-01.js
+++ b/browser/devtools/performance/test/browser_perf-options-enable-memory-01.js
@@ -5,32 +5,34 @@
  * Tests that `enable-memory` toggles the visibility of the memory graph,
  * as well as enabling memory data on the PerformanceFront.
  */
 function spawnTest () {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, PerformanceController, $ } = panel.panelWin;
 
   Services.prefs.setBoolPref(MEMORY_PREF, false);
-  ok($("#memory-overview").hidden, "memory graph is hidden when memory disabled");
 
   yield startRecording(panel);
   yield stopRecording(panel);
 
   is(PerformanceController.getCurrentRecording().getConfiguration().withMemory, false,
     "PerformanceFront started without memory recording.");
   is(PerformanceController.getCurrentRecording().getConfiguration().withAllocations, false,
     "PerformanceFront started without allocations recording.");
+  ok($("#memory-overview").hidden, "memory graph is hidden when memory disabled");
 
   Services.prefs.setBoolPref(MEMORY_PREF, true);
-  ok(!$("#memory-overview").hidden, "memory graph is not hidden when memory enabled");
+  ok($("#memory-overview").hidden,
+    "memory graph is still hidden after enabling if recording did not start recording memory");
 
   yield startRecording(panel);
   yield stopRecording(panel);
 
+  ok(!$("#memory-overview").hidden, "memory graph is not hidden when memory enabled before recording");
   is(PerformanceController.getCurrentRecording().getConfiguration().withMemory, true,
     "PerformanceFront started with memory recording.");
   is(PerformanceController.getCurrentRecording().getConfiguration().withAllocations, true,
     "PerformanceFront started with allocations recording.");
 
   yield teardown(panel);
   finish();
 }
--- a/browser/devtools/performance/test/browser_perf-options-flatten-tree-recursion-01.js
+++ b/browser/devtools/performance/test/browser_perf-options-flatten-tree-recursion-01.js
@@ -5,23 +5,22 @@
  * Tests that the js flamegraphs get rerendered when toggling `flatten-tree-recursion`
  */
 function spawnTest () {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, PerformanceController, DetailsView, JsFlameGraphView, FlameGraphUtils } = panel.panelWin;
 
   Services.prefs.setBoolPref(FLATTEN_PREF, true);
 
-  yield DetailsView.selectView("js-flamegraph");
-
   yield startRecording(panel);
   yield busyWait(100);
 
+  yield stopRecording(panel);
   let rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
-  yield stopRecording(panel);
+  yield DetailsView.selectView("js-flamegraph");
   yield rendered;
 
   let samples1 = PerformanceController.getCurrentRecording().getProfile().threads[0].samples;
   let rendering1 = FlameGraphUtils._cache.get(samples1);
 
   ok(samples1,
     "The samples were retrieved from the controller.");
   ok(rendering1,
--- a/browser/devtools/performance/test/browser_perf-options-flatten-tree-recursion-02.js
+++ b/browser/devtools/performance/test/browser_perf-options-flatten-tree-recursion-02.js
@@ -7,23 +7,22 @@
 function spawnTest () {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, PerformanceController, DetailsView, MemoryFlameGraphView, RecordingUtils, FlameGraphUtils } = panel.panelWin;
 
   // Enable memory to test
   Services.prefs.setBoolPref(MEMORY_PREF, true);
   Services.prefs.setBoolPref(FLATTEN_PREF, true);
 
-  yield DetailsView.selectView("memory-flamegraph");
-
   yield startRecording(panel);
   yield busyWait(100);
 
   let rendered = once(MemoryFlameGraphView, EVENTS.MEMORY_FLAMEGRAPH_RENDERED);
   yield stopRecording(panel);
+  yield DetailsView.selectView("memory-flamegraph");
   yield rendered;
 
   let allocations1 = PerformanceController.getCurrentRecording().getAllocations();
   let samples1 = RecordingUtils.getSamplesFromAllocations(allocations1);
   let rendering1 = FlameGraphUtils._cache.get(samples1);
 
   ok(allocations1,
     "The allocations were retrieved from the controller.");
--- a/browser/devtools/performance/test/browser_perf-options-invert-call-tree-01.js
+++ b/browser/devtools/performance/test/browser_perf-options-invert-call-tree-01.js
@@ -5,24 +5,23 @@
  * Tests that the js call tree views get rerendered when toggling `invert-call-tree`
  */
 function spawnTest () {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, DetailsView, JsCallTreeView } = panel.panelWin;
 
   Services.prefs.setBoolPref(INVERT_PREF, true);
 
+  yield startRecording(panel);
+  yield busyWait(100);
+  yield stopRecording(panel);
+
+  let rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
   yield DetailsView.selectView("js-calltree");
   ok(DetailsView.isViewSelected(JsCallTreeView), "The call tree is now selected.");
-
-  yield startRecording(panel);
-  yield busyWait(100);
-
-  let rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
-  yield stopRecording(panel);
   yield rendered;
 
   rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
   Services.prefs.setBoolPref(INVERT_PREF, false);
   yield rendered;
 
   ok(true, "JsCallTreeView rerendered when toggling invert-call-tree.");
 
--- a/browser/devtools/performance/test/browser_perf-options-invert-call-tree-02.js
+++ b/browser/devtools/performance/test/browser_perf-options-invert-call-tree-02.js
@@ -7,24 +7,23 @@
 function spawnTest () {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, DetailsView, MemoryCallTreeView } = panel.panelWin;
 
   // Enable memory to test
   Services.prefs.setBoolPref(MEMORY_PREF, true);
   Services.prefs.setBoolPref(INVERT_PREF, true);
 
+  yield startRecording(panel);
+  yield busyWait(100);
+  yield stopRecording(panel);
+
+  let rendered = once(MemoryCallTreeView, EVENTS.MEMORY_CALL_TREE_RENDERED);
   yield DetailsView.selectView("memory-calltree");
   ok(DetailsView.isViewSelected(MemoryCallTreeView), "The call tree is now selected.");
-
-  yield startRecording(panel);
-  yield busyWait(100);
-
-  let rendered = once(MemoryCallTreeView, EVENTS.MEMORY_CALL_TREE_RENDERED);
-  yield stopRecording(panel);
   yield rendered;
 
   rendered = once(MemoryCallTreeView, EVENTS.MEMORY_CALL_TREE_RENDERED);
   Services.prefs.setBoolPref(INVERT_PREF, false);
   yield rendered;
 
   ok(true, "MemoryCallTreeView rerendered when toggling invert-call-tree.");
 
--- a/browser/devtools/performance/test/browser_perf-options-invert-flame-graph-01.js
+++ b/browser/devtools/performance/test/browser_perf-options-invert-flame-graph-01.js
@@ -3,25 +3,24 @@
 
 /**
  * Tests that the js Flamegraphs gets rerendered when toggling `invert-flame-graph`
  */
 function spawnTest () {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, DetailsView, JsFlameGraphView } = panel.panelWin;
 
-  yield DetailsView.selectView("js-flamegraph");
-
   Services.prefs.setBoolPref(INVERT_FLAME_PREF, true);
 
   yield startRecording(panel);
   yield busyWait(100);
+  yield stopRecording(panel);
 
   let rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
-  yield stopRecording(panel);
+  yield DetailsView.selectView("js-flamegraph");
   yield rendered;
 
   rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
   Services.prefs.setBoolPref(INVERT_FLAME_PREF, false);
   yield rendered;
 
   ok(true, "JsFlameGraphView rerendered when toggling invert-flame-graph.");
 
--- a/browser/devtools/performance/test/browser_perf-options-invert-flame-graph-02.js
+++ b/browser/devtools/performance/test/browser_perf-options-invert-flame-graph-02.js
@@ -6,23 +6,22 @@
  */
 function spawnTest () {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, DetailsView, MemoryFlameGraphView } = panel.panelWin;
 
   Services.prefs.setBoolPref(MEMORY_PREF, true);
   Services.prefs.setBoolPref(INVERT_FLAME_PREF, true);
 
-  yield DetailsView.selectView("memory-flamegraph");
-
   yield startRecording(panel);
   yield busyWait(100);
+  yield stopRecording(panel);
 
   let rendered = once(MemoryFlameGraphView, EVENTS.MEMORY_FLAMEGRAPH_RENDERED);
-  yield stopRecording(panel);
+  yield DetailsView.selectView("memory-flamegraph");
   yield rendered;
 
   rendered = once(MemoryFlameGraphView, EVENTS.MEMORY_FLAMEGRAPH_RENDERED);
   Services.prefs.setBoolPref(INVERT_FLAME_PREF, false);
   yield rendered;
 
   ok(true, "MemoryFlameGraphView rerendered when toggling invert-flame-graph.");
 
--- a/browser/devtools/performance/test/browser_perf-options-show-idle-blocks-01.js
+++ b/browser/devtools/performance/test/browser_perf-options-show-idle-blocks-01.js
@@ -5,23 +5,22 @@
  * Tests that the js flamegraphs get rerendered when toggling `show-idle-blocks`
  */
 function spawnTest () {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, DetailsView, JsFlameGraphView } = panel.panelWin;
 
   Services.prefs.setBoolPref(IDLE_PREF, true);
 
-  yield DetailsView.selectView("js-flamegraph");
-
   yield startRecording(panel);
   yield busyWait(100);
+  yield stopRecording(panel);
 
   let rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
-  yield stopRecording(panel);
+  yield DetailsView.selectView("js-flamegraph");
   yield rendered;
 
   rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
   Services.prefs.setBoolPref(IDLE_PREF, false);
   yield rendered;
 
   ok(true, "JsFlameGraphView rerendered when toggling show-idle-blocks.");
 
--- a/browser/devtools/performance/test/browser_perf-options-show-idle-blocks-02.js
+++ b/browser/devtools/performance/test/browser_perf-options-show-idle-blocks-02.js
@@ -7,23 +7,22 @@
 function spawnTest () {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, DetailsView, MemoryFlameGraphView } = panel.panelWin;
 
   // Enable memory to test
   Services.prefs.setBoolPref(MEMORY_PREF, true);
   Services.prefs.setBoolPref(IDLE_PREF, true);
 
-  yield DetailsView.selectView("memory-flamegraph");
-
   yield startRecording(panel);
   yield busyWait(100);
+  yield stopRecording(panel);
 
   let rendered = once(MemoryFlameGraphView, EVENTS.MEMORY_FLAMEGRAPH_RENDERED);
-  yield stopRecording(panel);
+  yield DetailsView.selectView("memory-flamegraph");
   yield rendered;
 
   rendered = once(MemoryFlameGraphView, EVENTS.MEMORY_FLAMEGRAPH_RENDERED);
   Services.prefs.setBoolPref(IDLE_PREF, false);
   yield rendered;
 
   ok(true, "MemoryFlameGraphView rerendered when toggling show-idle-blocks.");
 
--- a/browser/devtools/performance/test/browser_perf-options-show-platform-data-01.js
+++ b/browser/devtools/performance/test/browser_perf-options-show-platform-data-01.js
@@ -5,23 +5,22 @@
  * Tests that the js call tree views get rerendered when toggling `show-platform-data`
  */
 function spawnTest () {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, DetailsView, JsCallTreeView } = panel.panelWin;
 
   Services.prefs.setBoolPref(PLATFORM_DATA_PREF, true);
 
-  yield DetailsView.selectView("js-calltree");
-
   yield startRecording(panel);
   yield busyWait(100);
+  yield stopRecording(panel);
 
   let rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
-  yield stopRecording(panel);
+  yield DetailsView.selectView("js-calltree");
   yield rendered;
 
   rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
   Services.prefs.setBoolPref(PLATFORM_DATA_PREF, false);
   yield rendered;
   ok(true, "JsCallTreeView rerendered when toggling off show-platform-data.");
 
   rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
--- a/browser/devtools/performance/test/browser_perf-options-show-platform-data-02.js
+++ b/browser/devtools/performance/test/browser_perf-options-show-platform-data-02.js
@@ -5,23 +5,22 @@
  * Tests that the js flamegraphs get rerendered when toggling `show-platform-data`
  */
 function spawnTest () {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, DetailsView, JsFlameGraphView } = panel.panelWin;
 
   Services.prefs.setBoolPref(PLATFORM_DATA_PREF, false);
 
-  yield DetailsView.selectView("js-flamegraph");
-
   yield startRecording(panel);
   yield busyWait(100);
+  yield stopRecording(panel);
 
   let rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
-  yield stopRecording(panel);
+  yield DetailsView.selectView("js-flamegraph");
   yield rendered;
 
   rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
   Services.prefs.setBoolPref(PLATFORM_DATA_PREF, true);
   yield rendered;
   ok(true, "JsFlameGraphView rerendered when toggling on show-platform-data.");
 
   rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
--- a/browser/devtools/performance/test/browser_perf-overview-render-02.js
+++ b/browser/devtools/performance/test/browser_perf-overview-render-02.js
@@ -16,70 +16,73 @@ function spawnTest () {
 
   yield Promise.all([
     once(OverviewView, EVENTS.FRAMERATE_GRAPH_RENDERED),
     once(OverviewView, EVENTS.MARKERS_GRAPH_RENDERED),
     once(OverviewView, EVENTS.MEMORY_GRAPH_RENDERED),
     once(OverviewView, EVENTS.OVERVIEW_RENDERED),
   ]);
 
-  ok("selectionEnabled" in OverviewView.framerateGraph,
+  let framerate = OverviewView.graphs.get("framerate");
+  ok("selectionEnabled" in framerate,
     "The selection should not be enabled for the framerate overview (1).");
-  is(OverviewView.framerateGraph.selectionEnabled, false,
+  is(framerate.selectionEnabled, false,
     "The selection should not be enabled for the framerate overview (2).");
-  is(OverviewView.framerateGraph.hasSelection(), false,
+  is(framerate.hasSelection(), false,
     "The framerate overview shouldn't have a selection before recording.");
 
-  ok("selectionEnabled" in OverviewView.markersOverview,
+  let markers = OverviewView.graphs.get("timeline");
+  ok("selectionEnabled" in markers,
     "The selection should not be enabled for the markers overview (1).");
-  is(OverviewView.markersOverview.selectionEnabled, false,
+  is(markers.selectionEnabled, false,
     "The selection should not be enabled for the markers overview (2).");
-  is(OverviewView.markersOverview.hasSelection(), false,
+  is(markers.hasSelection(), false,
     "The markers overview shouldn't have a selection before recording.");
 
-  ok("selectionEnabled" in OverviewView.memoryOverview,
+  let memory = OverviewView.graphs.get("memory");
+  ok("selectionEnabled" in memory,
     "The selection should not be enabled for the memory overview (1).");
-  is(OverviewView.memoryOverview.selectionEnabled, false,
+  is(memory.selectionEnabled, false,
     "The selection should not be enabled for the memory overview (2).");
-  is(OverviewView.memoryOverview.hasSelection(), false,
+  is(memory.hasSelection(), false,
     "The memory overview shouldn't have a selection before recording.");
 
   let updated = 0;
   OverviewView.on(EVENTS.OVERVIEW_RENDERED, () => updated++);
 
   ok((yield waitUntil(() => updated > 10)),
     "The overviews were updated several times.");
 
-  ok("selectionEnabled" in OverviewView.framerateGraph,
+  ok("selectionEnabled" in framerate,
     "The selection should still not be enabled for the framerate overview (1).");
-  is(OverviewView.framerateGraph.selectionEnabled, false,
+  is(framerate.selectionEnabled, false,
     "The selection should still not be enabled for the framerate overview (2).");
-  is(OverviewView.framerateGraph.hasSelection(), false,
+  is(framerate.hasSelection(), false,
     "The framerate overview still shouldn't have a selection before recording.");
 
-  ok("selectionEnabled" in OverviewView.markersOverview,
+  ok("selectionEnabled" in markers,
     "The selection should still not be enabled for the markers overview (1).");
-  is(OverviewView.markersOverview.selectionEnabled, false,
+  is(markers.selectionEnabled, false,
     "The selection should still not be enabled for the markers overview (2).");
-  is(OverviewView.markersOverview.hasSelection(), false,
+  is(markers.hasSelection(), false,
     "The markers overview still shouldn't have a selection before recording.");
 
-  ok("selectionEnabled" in OverviewView.memoryOverview,
+  ok("selectionEnabled" in memory,
     "The selection should still not be enabled for the memory overview (1).");
-  is(OverviewView.memoryOverview.selectionEnabled, false,
+  is(memory.selectionEnabled, false,
     "The selection should still not be enabled for the memory overview (2).");
-  is(OverviewView.memoryOverview.hasSelection(), false,
+  is(memory.hasSelection(), false,
     "The memory overview still shouldn't have a selection before recording.");
 
   yield stopRecording(panel);
 
-  is(OverviewView.framerateGraph.selectionEnabled, true,
+  is(framerate.selectionEnabled, true,
     "The selection should now be enabled for the framerate overview.");
 
-  is(OverviewView.markersOverview.selectionEnabled, true,
+  is(markers.selectionEnabled, true,
     "The selection should now be enabled for the markers overview.");
 
-  is(OverviewView.memoryOverview.selectionEnabled, true,
+  is(memory.selectionEnabled, true,
     "The selection should now be enabled for the memory overview.");
 
   yield teardown(panel);
   finish();
 }
--- a/browser/devtools/performance/test/browser_perf-overview-render-03.js
+++ b/browser/devtools/performance/test/browser_perf-overview-render-03.js
@@ -19,48 +19,47 @@ function spawnTest () {
   yield busyWait(100);
   yield waitUntil(() => PerformanceController.getCurrentRecording().getMarkers().length);
   yield waitUntil(() => PerformanceController.getCurrentRecording().getMemory().length);
   yield waitUntil(() => PerformanceController.getCurrentRecording().getTicks().length);
   yield waitUntil(() => updated > 10);
 
   yield stopRecording(panel);
 
-  ok(OverviewView.markersOverview.width > 0,
-    "The overview's framerate graph has a width.");
-  ok(OverviewView.markersOverview.dataScaleX > 0,
-    "The overview's framerate graph has a data scale factor.");
+  let markers = OverviewView.graphs.get("timeline");
+  let framerate = OverviewView.graphs.get("framerate");
+  let memory = OverviewView.graphs.get("memory");
+
+  ok(markers.width > 0,
+    "The overview's markers graph has a width.");
+  ok(markers.dataScaleX > 0,
+    "The overview's markers graph has a data scale factor.");
 
-  ok(OverviewView.memoryOverview.width > 0,
+  ok(memory.width > 0,
+    "The overview's memory graph has a width.");
+  ok(memory.dataDuration > 0,
+    "The overview's memory graph has a data duration.");
+  ok(memory.dataScaleX > 0,
+    "The overview's memory graph has a data scale factor.");
+
+  ok(framerate.width > 0,
     "The overview's framerate graph has a width.");
-  ok(OverviewView.memoryOverview.dataDuration > 0,
+  ok(framerate.dataDuration > 0,
     "The overview's framerate graph has a data duration.");
-  ok(OverviewView.memoryOverview.dataScaleX > 0,
+  ok(framerate.dataScaleX > 0,
     "The overview's framerate graph has a data scale factor.");
 
-  ok(OverviewView.framerateGraph.width > 0,
-    "The overview's framerate graph has a width.");
-  ok(OverviewView.framerateGraph.dataDuration > 0,
-    "The overview's framerate graph has a data duration.");
-  ok(OverviewView.framerateGraph.dataScaleX > 0,
-    "The overview's framerate graph has a data scale factor.");
-
-  is(OverviewView.markersOverview.width,
-     OverviewView.memoryOverview.width,
-    "The markers and memory graphs widths are the same.")
-  is(OverviewView.markersOverview.width,
-     OverviewView.framerateGraph.width,
+  is(markers.width, memory.width,
+    "The markers and memory graphs widths are the same.");
+  is(markers.width, framerate.width,
     "The markers and framerate graphs widths are the same.");
 
-  is(OverviewView.memoryOverview.dataDuration,
-     OverviewView.framerateGraph.dataDuration,
+  is(memory.dataDuration, framerate.dataDuration,
     "The memory and framerate graphs data duration are the same.");
 
-  is(OverviewView.markersOverview.dataScaleX,
-     OverviewView.memoryOverview.dataScaleX,
-    "The markers and memory graphs data scale are the same.")
-  is(OverviewView.markersOverview.dataScaleX,
-     OverviewView.framerateGraph.dataScaleX,
+  is(markers.dataScaleX, memory.dataScaleX,
+    "The markers and memory graphs data scale are the same.");
+  is(markers.dataScaleX, framerate.dataScaleX,
     "The markers and framerate graphs data scale are the same.");
 
   yield teardown(panel);
   finish();
 }
--- a/browser/devtools/performance/test/browser_perf-overview-selection-01.js
+++ b/browser/devtools/performance/test/browser_perf-overview-selection-01.js
@@ -14,17 +14,17 @@ function spawnTest () {
   yield Promise.all([
     once(OverviewView, EVENTS.FRAMERATE_GRAPH_RENDERED),
     once(OverviewView, EVENTS.MARKERS_GRAPH_RENDERED),
     once(OverviewView, EVENTS.OVERVIEW_RENDERED)
   ]);
 
   yield stopRecording(panel);
 
-  let graph = OverviewView.markersOverview;
+  let graph = OverviewView.graphs.get("timeline");
   let MAX = graph.width;
 
   // Select the first half of the graph
   let results = onceSpread(OverviewView, EVENTS.OVERVIEW_RANGE_SELECTED);
   dragStart(graph, 0);
   dragStop(graph, MAX / 2);
   [_, { startTime, endTime }] = yield results;
 
--- a/browser/devtools/performance/test/browser_perf-overview-selection-02.js
+++ b/browser/devtools/performance/test/browser_perf-overview-selection-02.js
@@ -15,19 +15,19 @@ function spawnTest () {
 
   yield Promise.all([
     once(OverviewView, EVENTS.FRAMERATE_GRAPH_RENDERED),
     once(OverviewView, EVENTS.MARKERS_GRAPH_RENDERED),
     once(OverviewView, EVENTS.MEMORY_GRAPH_RENDERED),
     once(OverviewView, EVENTS.OVERVIEW_RENDERED),
   ]);
 
-  let markersOverview = OverviewView.markersOverview;
-  let memoryOverview = OverviewView.memoryOverview;
-  let framerateGraph = OverviewView.framerateGraph;
+  let markersOverview = OverviewView.graphs.get("timeline");
+  let memoryOverview = OverviewView.graphs.get("memory");
+  let framerateGraph = OverviewView.graphs.get("framerate");
 
   ok(markersOverview,
     "The markers graph should have been created now.");
   ok(memoryOverview,
     "The memory graph should have been created now.");
   ok(framerateGraph,
     "The framerate graph should have been created now.");
 
--- a/browser/devtools/performance/test/browser_perf-overview-selection-03.js
+++ b/browser/devtools/performance/test/browser_perf-overview-selection-03.js
@@ -17,19 +17,19 @@ function spawnTest () {
     once(OverviewView, EVENTS.FRAMERATE_GRAPH_RENDERED),
     once(OverviewView, EVENTS.MARKERS_GRAPH_RENDERED),
     once(OverviewView, EVENTS.MEMORY_GRAPH_RENDERED),
     once(OverviewView, EVENTS.OVERVIEW_RENDERED),
   ]);
 
   yield stopRecording(panel);
 
-  let framerateGraph = OverviewView.framerateGraph;
-  let markersOverview = OverviewView.markersOverview;
-  let memoryOverview = OverviewView.memoryOverview;
+  let framerateGraph = OverviewView.graphs.get("framerate");
+  let markersOverview = OverviewView.graphs.get("timeline");
+  let memoryOverview = OverviewView.graphs.get("memory");
   let MAX = framerateGraph.width;
 
   // Perform a selection inside the framerate graph.
 
   let selected = once(OverviewView, EVENTS.OVERVIEW_RANGE_SELECTED);
   dragStart(framerateGraph, 0);
   dragStop(framerateGraph, MAX / 2);
   yield selected;
--- a/browser/devtools/performance/test/browser_perf-recording-selected-04.js
+++ b/browser/devtools/performance/test/browser_perf-recording-selected-04.js
@@ -12,30 +12,30 @@ let test = Task.async(function*() {
 
   // Enable memory to test the memory-calltree and memory-flamegraph.
   Services.prefs.setBoolPref(MEMORY_PREF, true);
 
   // Need to allow widgets to be updated while hidden, otherwise we can't use
   // `waitForWidgetsRendered`.
   DetailsSubview.canUpdateWhileHidden = true;
 
+  yield startRecording(panel);
+  yield stopRecording(panel);
+
   // Cycle through all the views to initialize them, otherwise we can't use
   // `waitForWidgetsRendered`. The waterfall is shown by default, but all the
   // other views are created lazily, so won't emit any events.
   yield DetailsView.selectView("js-calltree");
   yield DetailsView.selectView("js-flamegraph");
   yield DetailsView.selectView("memory-calltree");
   yield DetailsView.selectView("memory-flamegraph");
 
   yield startRecording(panel);
   yield stopRecording(panel);
 
-  yield startRecording(panel);
-  yield stopRecording(panel);
-
   let rerender = waitForWidgetsRendered(panel);
   RecordingsView.selectedIndex = 0;
   yield rerender;
 
   rerender = waitForWidgetsRendered(panel);
   RecordingsView.selectedIndex = 1;
   yield rerender;
 
--- a/browser/devtools/performance/test/browser_perf-states.js
+++ b/browser/devtools/performance/test/browser_perf-states.js
@@ -6,52 +6,52 @@
  */
 function spawnTest () {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, PerformanceView, OverviewView, DetailsView } = panel.panelWin;
 
   is(PerformanceView.getState(), "empty",
     "The intial state of the performance panel view is correct.");
 
-  ok(!("markersOverview" in OverviewView),
+  ok(!(OverviewView.graphs.get("timeline")),
     "The markers graph should not have been created yet.");
-  ok(!("memoryOverview" in OverviewView),
+  ok(!(OverviewView.graphs.get("memory")),
     "The memory graph should not have been created yet.");
-  ok(!("framerateGraph" in OverviewView),
+  ok(!(OverviewView.graphs.get("framerate")),
     "The framerate graph should not have been created yet.");
 
-  ok(DetailsView.components["waterfall"].initialized,
-    "The waterfall detail view should have been created by default.");
+  ok(!DetailsView.components["waterfall"].initialized,
+    "The waterfall detail view should not have been created yet.");
   ok(!DetailsView.components["js-calltree"].initialized,
     "The js-calltree detail view should not have been created yet.");
   ok(!DetailsView.components["js-flamegraph"].initialized,
     "The js-flamegraph detail view should not have been created yet.");
   ok(!DetailsView.components["memory-calltree"].initialized,
     "The memory-calltree detail view should not have been created yet.");
   ok(!DetailsView.components["memory-flamegraph"].initialized,
     "The memory-flamegraph detail view should not have been created yet.");
 
   Services.prefs.setBoolPref(MEMORY_PREF, true);
 
-  ok(!("markersOverview" in OverviewView),
+  ok(!(OverviewView.graphs.get("timeline")),
     "The markers graph should still not have been created yet.");
-  ok(!("memoryOverview" in OverviewView),
+  ok(!(OverviewView.graphs.get("memory")),
     "The memory graph should still not have been created yet.");
-  ok(!("framerateGraph" in OverviewView),
+  ok(!(OverviewView.graphs.get("framerate")),
     "The framerate graph should still not have been created yet.");
 
   let stateChanged = once(PerformanceView, EVENTS.UI_STATE_CHANGED);
   yield startRecording(panel);
   yield stateChanged;
 
   is(PerformanceView.getState(), "recording",
     "The current state of the performance panel view is 'recording'.");
-  ok(OverviewView.memoryOverview,
+  ok(OverviewView.graphs.get("memory"),
     "The memory graph should have been created now.");
-  ok(OverviewView.framerateGraph,
+  ok(OverviewView.graphs.get("framerate"),
     "The framerate graph should have been created now.");
 
   stateChanged = once(PerformanceView, EVENTS.UI_STATE_CHANGED);
   yield stopRecording(panel);
   yield stateChanged;
 
   is(PerformanceView.getState(), "recorded",
     "The current state of the performance panel view is 'recorded'.");
--- a/browser/devtools/performance/test/browser_perf-theme-toggle-01.js
+++ b/browser/devtools/performance/test/browser_perf-theme-toggle-01.js
@@ -12,58 +12,60 @@ const DARK_BG = "#14171a";
 setTheme("dark");
 Services.prefs.setBoolPref(MEMORY_PREF, false);
 
 function spawnTest () {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, $, OverviewView, document: doc } = panel.panelWin;
 
   yield startRecording(panel);
-  is(OverviewView.markersOverview.backgroundColor, DARK_BG,
+  let markers = OverviewView.graphs.get("timeline");
+  is(markers.backgroundColor, DARK_BG,
     "correct theme on load for markers.");
   yield stopRecording(panel);
 
-  let refreshed = once(OverviewView.markersOverview, "refresh");
+  let refreshed = once(markers, "refresh");
   setTheme("light");
   yield refreshed;
 
   ok(true, "markers were rerendered after theme change.");
-  is(OverviewView.markersOverview.backgroundColor, LIGHT_BG,
+  is(markers.backgroundColor, LIGHT_BG,
     "correct theme on after toggle for markers.");
 
   // reset back to dark
-  refreshed = once(OverviewView.markersOverview, "refresh");
+  refreshed = once(markers, "refresh");
   setTheme("dark");
   yield refreshed;
 
   info("Testing with memory overview");
 
   Services.prefs.setBoolPref(MEMORY_PREF, true);
 
   yield startRecording(panel);
-  is(OverviewView.memoryOverview.backgroundColor, DARK_BG,
+  let memory = OverviewView.graphs.get("memory");
+  is(memory.backgroundColor, DARK_BG,
     "correct theme on load for memory.");
   yield stopRecording(panel);
 
   refreshed = Promise.all([
-    once(OverviewView.markersOverview, "refresh"),
-    once(OverviewView.memoryOverview, "refresh"),
+    once(markers, "refresh"),
+    once(memory, "refresh"),
   ]);
   setTheme("light");
   yield refreshed;
 
   ok(true, "Both memory and markers were rerendered after theme change.");
-  is(OverviewView.markersOverview.backgroundColor, LIGHT_BG,
+  is(markers.backgroundColor, LIGHT_BG,
     "correct theme on after toggle for markers.");
-  is(OverviewView.memoryOverview.backgroundColor, LIGHT_BG,
+  is(memory.backgroundColor, LIGHT_BG,
     "correct theme on after toggle for memory.");
 
   refreshed = Promise.all([
-    once(OverviewView.markersOverview, "refresh"),
-    once(OverviewView.memoryOverview, "refresh"),
+    once(markers, "refresh"),
+    once(memory, "refresh"),
   ]);
 
   // Set theme back to light
   setTheme("light");
   yield refreshed;
 
   yield teardown(panel);
   finish();
--- a/browser/devtools/performance/test/browser_perf_recordings-io-01.js
+++ b/browser/devtools/performance/test/browser_perf_recordings-io-01.js
@@ -6,31 +6,33 @@
  */
 
 let test = Task.async(function*() {
   let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, PerformanceController, DetailsView, DetailsSubview } = panel.panelWin;
 
   // Enable memory to test the memory-calltree and memory-flamegraph.
   Services.prefs.setBoolPref(MEMORY_PREF, true);
+  Services.prefs.setBoolPref(FRAMERATE_PREF, true);
+
+  // Need to allow widgets to be updated while hidden, otherwise we can't use
+  // `waitForWidgetsRendered`.
+  DetailsSubview.canUpdateWhileHidden = true;
+
+  yield startRecording(panel);
+  yield stopRecording(panel);
 
   // Cycle through all the views to initialize them, otherwise we can't use
   // `waitForWidgetsRendered`. The waterfall is shown by default, but all the
   // other views are created lazily, so won't emit any events.
   yield DetailsView.selectView("js-calltree");
   yield DetailsView.selectView("js-flamegraph");
   yield DetailsView.selectView("memory-calltree");
   yield DetailsView.selectView("memory-flamegraph");
 
-  // Need to allow widgets to be updated while hidden, otherwise we can't use
-  // `waitForWidgetsRendered`.
-  DetailsSubview.canUpdateWhileHidden = true;
-
-  yield startRecording(panel);
-  yield stopRecording(panel);
 
   // Verify original recording.
 
   let originalData = PerformanceController.getCurrentRecording().getAllData();
   ok(originalData, "The original recording is not empty.");
 
   // Save recording.
 
@@ -55,25 +57,29 @@ let test = Task.async(function*() {
   yield rerendered;
   ok(true, "The imported data was re-rendered.");
 
   // Verify imported recording.
 
   let importedData = PerformanceController.getCurrentRecording().getAllData();
 
   is(importedData.label, originalData.label,
-    "The impored data is identical to the original data (1).");
+    "The imported data is identical to the original data (1).");
   is(importedData.duration, originalData.duration,
-    "The impored data is identical to the original data (2).");
+    "The imported data is identical to the original data (2).");
   is(importedData.markers.toSource(), originalData.markers.toSource(),
-    "The impored data is identical to the original data (3).");
+    "The imported data is identical to the original data (3).");
   is(importedData.memory.toSource(), originalData.memory.toSource(),
-    "The impored data is identical to the original data (4).");
+    "The imported data is identical to the original data (4).");
   is(importedData.ticks.toSource(), originalData.ticks.toSource(),
-    "The impored data is identical to the original data (5).");
+    "The imported data is identical to the original data (5).");
   is(importedData.allocations.toSource(), originalData.allocations.toSource(),
-    "The impored data is identical to the original data (6).");
+    "The imported data is identical to the original data (6).");
   is(importedData.profile.toSource(), originalData.profile.toSource(),
-    "The impored data is identical to the original data (7).");
+    "The imported data is identical to the original data (7).");
+  is(importedData.configuration.withTicks, originalData.configuration.withTicks,
+    "The imported data is identical to the original data (8).");
+  is(importedData.configuration.withMemory, originalData.configuration.withMemory,
+    "The imported data is identical to the original data (9).");
 
   yield teardown(panel);
   finish();
 });
--- a/browser/devtools/performance/test/browser_perf_recordings-io-04.js
+++ b/browser/devtools/performance/test/browser_perf_recordings-io-04.js
@@ -1,35 +1,23 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests if the performance tool can import profiler data from the
- * original profiler tool.
+ * original profiler tool and the correct views and graphs are loaded.
  */
 
 let test = Task.async(function*() {
   let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL);
-  let { EVENTS, PerformanceController, DetailsView, DetailsSubview } = panel.panelWin;
+  let { $, EVENTS, PerformanceController, DetailsView, OverviewView, JsCallTreeView } = panel.panelWin;
 
   // Enable memory to test the memory-calltree and memory-flamegraph.
   Services.prefs.setBoolPref(MEMORY_PREF, true);
 
-  // Cycle through all the views to initialize them, otherwise we can't use
-  // `waitForWidgetsRendered`. The waterfall is shown by default, but all the
-  // other views are created lazily, so won't emit any events.
-  yield DetailsView.selectView("js-calltree");
-  yield DetailsView.selectView("js-flamegraph");
-  yield DetailsView.selectView("memory-calltree");
-  yield DetailsView.selectView("memory-flamegraph");
-
-  // Need to allow widgets to be updated while hidden, otherwise we can't use
-  // `waitForWidgetsRendered`.
-  DetailsSubview.canUpdateWhileHidden = true;
-
   yield startRecording(panel);
   yield stopRecording(panel);
 
   // Get data from the current profiler
   let data = PerformanceController.getCurrentRecording().getAllData();
 
   // Create a structure from the data that mimics the old profiler's data.
   // Different name for `ticks`, different way of storing time,
@@ -44,26 +32,40 @@ let test = Task.async(function*() {
 
   // Save recording as an old profiler data.
   let file = FileUtils.getFile("TmpD", ["tmpprofile.json"]);
   file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
   yield asyncCopy(oldProfilerData, file);
 
   // Import recording.
 
-  let rerendered = waitForWidgetsRendered(panel);
+  let calltreeRendered = once(OverviewView, EVENTS.FRAMERATE_GRAPH_RENDERED);
+  let fpsRendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
   let imported = once(PerformanceController, EVENTS.RECORDING_IMPORTED);
   yield PerformanceController.importRecording("", file);
 
   yield imported;
   ok(true, "The original profiler data appears to have been successfully imported.");
 
-  yield rerendered;
+  yield calltreeRendered;
+  yield fpsRendered;
   ok(true, "The imported data was re-rendered.");
 
+  // Ensure that only framerate and js calltree/flamegraph view are available
+  is($("#overview-pane").hidden, false, "overview graph container still shown");
+  is($("#memory-overview").hidden, true, "memory graph hidden");
+  is($("#markers-overview").hidden, true, "markers overview graph hidden");
+  is($("#time-framerate").hidden, false, "fps graph shown");
+  is($("#select-waterfall-view").hidden, true, "waterfall button hidden");
+  is($("#select-js-calltree-view").hidden, false, "jscalltree button shown");
+  is($("#select-js-flamegraph-view").hidden, false, "jsflamegraph button shown");
+  is($("#select-memory-calltree-view").hidden, true, "memorycalltree button hidden");
+  is($("#select-memory-flamegraph-view").hidden, true, "memoryflamegraph button hidden");
+  ok(DetailsView.isViewSelected(JsCallTreeView), "jscalltree view selected as its the only option");
+
   // Verify imported recording.
 
   let importedData = PerformanceController.getCurrentRecording().getAllData();
 
   is(importedData.label, data.label,
     "The imported legacy data was successfully converted for the current tool (1).");
   is(importedData.duration, data.duration,
     "The imported legacy data was successfully converted for the current tool (2).");
@@ -74,16 +76,22 @@ let test = Task.async(function*() {
   is(importedData.memory.toSource(), [].toSource(),
     "The imported legacy data was successfully converted for the current tool (5).");
   is(importedData.ticks.toSource(), data.ticks.toSource(),
     "The imported legacy data was successfully converted for the current tool (6).");
   is(importedData.allocations.toSource(), ({sites:[], timestamps:[], frames:[], counts:[]}).toSource(),
     "The imported legacy data was successfully converted for the current tool (7).");
   is(importedData.profile.toSource(), data.profile.toSource(),
     "The imported legacy data was successfully converted for the current tool (8).");
+  is(importedData.configuration.withTicks, true,
+    "The imported legacy data was successfully converted for the current tool (9).");
+  is(importedData.configuration.withMemory, false,
+    "The imported legacy data was successfully converted for the current tool (10).");
+  is(importedData.configuration.sampleFrequency, void 0,
+    "The imported legacy data was successfully converted for the current tool (11).");
 
   yield teardown(panel);
   finish();
 });
 
 function getUnicodeConverter() {
   let className = "@mozilla.org/intl/scriptableunicodeconverter";
   let converter = Cc[className].createInstance(Ci.nsIScriptableUnicodeConverter);
--- a/browser/devtools/performance/test/browser_timeline_filters.js
+++ b/browser/devtools/performance/test/browser_timeline_filters.js
@@ -3,32 +3,33 @@
 
 /**
  * Tests markers filtering mechanism.
  */
 
 function spawnTest () {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { $, $$, PerformanceController, OverviewView, WaterfallView } = panel.panelWin;
-  let { MARKERS_GRAPH_ROW_HEIGHT } = panel.panelWin;
+  let { TimelineGraph } = devtools.require("devtools/performance/graphs");
+  let { rowHeight: MARKERS_GRAPH_ROW_HEIGHT } = TimelineGraph.prototype;
 
   yield startRecording(panel);
   ok(true, "Recording has started.");
 
   yield waitUntil(() => {
     // Wait until we get 3 different markers.
     let markers = PerformanceController.getCurrentRecording().getMarkers();
     return markers.some(m => m.name == "Styles") &&
            markers.some(m => m.name == "Reflow") &&
            markers.some(m => m.name == "Paint");
   });
 
   yield stopRecording(panel);
 
-  let overview = OverviewView.markersOverview;
+  let overview = OverviewView.graphs.get("timeline");
   let waterfall = WaterfallView.waterfall;
 
   // Select everything
   OverviewView.setTimeInterval({ startTime: 0, endTime: Number.MAX_VALUE })
 
   $("#filter-button").click();
 
   yield waitUntil(() => !waterfall._outstandingMarkers.length);
--- a/browser/devtools/performance/test/head.js
+++ b/browser/devtools/performance/test/head.js
@@ -349,16 +349,17 @@ function* startRecording(panel, options 
     "The record button should be locked.");
 
   yield willStart;
   let stateChanged = options.waitForStateChanged
     ? once(win.PerformanceView, win.EVENTS.UI_STATE_CHANGED)
     : Promise.resolve();
 
   yield hasStarted;
+
   let overviewRendered = options.waitForOverview
     ? once(win.OverviewView, win.EVENTS.OVERVIEW_RENDERED)
     : Promise.resolve();
 
   yield stateChanged;
   yield overviewRendered;
 
   is(win.PerformanceView.getState(), "recording",
@@ -374,16 +375,17 @@ function* stopRecording(panel, options =
   waitForOverview: true,
   waitForStateChanged: true
 }) {
   let win = panel.panelWin;
   let clicked = panel.panelWin.PerformanceView.once(win.EVENTS.UI_STOP_RECORDING);
   let willStop = panel.panelWin.PerformanceController.once(win.EVENTS.RECORDING_WILL_STOP);
   let hasStopped = panel.panelWin.PerformanceController.once(win.EVENTS.RECORDING_STOPPED);
   let button = win.$("#main-record-button");
+  let overviewRendered = null;
 
   ok(button.hasAttribute("checked"),
     "The record button should already be checked.");
   ok(!button.hasAttribute("locked"),
     "The record button should not be locked yet.");
 
   click(win, button);
   yield clicked;
@@ -394,22 +396,27 @@ function* stopRecording(panel, options =
     "The record button should be locked.");
 
   yield willStop;
   let stateChanged = options.waitForStateChanged
     ? once(win.PerformanceView, win.EVENTS.UI_STATE_CHANGED)
     : Promise.resolve();
 
   yield hasStopped;
-  let overviewRendered = options.waitForOverview
-    ? once(win.OverviewView, win.EVENTS.OVERVIEW_RENDERED)
-    : Promise.resolve();
+
+  // Wait for the final rendering of the overview, not a low res
+  // incremental rendering and less likely to be from another rendering that was selected
+  while (!overviewRendered && options.waitForOverview) {
+    let [_, res] = yield onceSpread(win.OverviewView, win.EVENTS.OVERVIEW_RENDERED);
+    if (res === win.FRAMERATE_GRAPH_HIGH_RES_INTERVAL) {
+      overviewRendered = true;
+    }
+  }
 
   yield stateChanged;
-  yield overviewRendered;
 
   is(win.PerformanceView.getState(), "recorded",
     "The current state is 'recorded'.");
 
   ok(!button.hasAttribute("checked"),
     "The record button should not be checked.");
   ok(!button.hasAttribute("locked"),
     "The record button should not be locked.");
@@ -485,8 +492,18 @@ function dropSelection(graph) {
 function fireKey (e) {
   EventUtils.synthesizeKey(e, {});
 }
 
 function reload (aTarget, aEvent = "navigate") {
   aTarget.activeTab.reload();
   return once(aTarget, aEvent);
 }
+
+/**
+* Forces cycle collection and GC, used in AudioNode destruction tests.
+*/
+function forceCC () {
+  info("Triggering GC/CC...");
+  SpecialPowers.DOMWindowUtils.cycleCollect();
+  SpecialPowers.DOMWindowUtils.garbageCollect();
+  SpecialPowers.DOMWindowUtils.garbageCollect();
+}
--- a/browser/devtools/performance/views/details.js
+++ b/browser/devtools/performance/views/details.js
@@ -11,38 +11,39 @@ let DetailsView = {
   /**
    * Name to (node id, view object, actor requirements, pref killswitch)
    * mapping of subviews.
    */
   components: {
     "waterfall": {
       id: "waterfall-view",
       view: WaterfallView,
-      requires: ["timeline"]
+      actors: ["timeline"],
+      features: ["withMarkers"]
     },
     "js-calltree": {
       id: "js-profile-view",
       view: JsCallTreeView
     },
     "js-flamegraph": {
       id: "js-flamegraph-view",
       view: JsFlameGraphView,
-      requires: ["timeline"]
+      actors: ["timeline"]
     },
     "memory-calltree": {
       id: "memory-calltree-view",
       view: MemoryCallTreeView,
-      requires: ["memory"],
-      pref: "enable-memory"
+      actors: ["memory"],
+      features: ["withAllocations"]
     },
     "memory-flamegraph": {
       id: "memory-flamegraph-view",
       view: MemoryFlameGraphView,
-      requires: ["memory", "timeline"],
-      pref: "enable-memory"
+      actors: ["memory", "timeline"],
+      features: ["withAllocations"]
     }
   },
 
   /**
    * Sets up the view with event binding, initializes subviews.
    */
   initialize: Task.async(function *() {
     this.el = $("#details-pane");
@@ -51,17 +52,16 @@ let DetailsView = {
     this._onViewToggle = this._onViewToggle.bind(this);
     this._onRecordingStoppedOrSelected = this._onRecordingStoppedOrSelected.bind(this);
     this.setAvailableViews = this.setAvailableViews.bind(this);
 
     for (let button of $$("toolbarbutton[data-view]", this.toolbar)) {
       button.addEventListener("command", this._onViewToggle);
     }
 
-    yield this.selectDefaultView();
     yield this.setAvailableViews();
 
     PerformanceController.on(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
     PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
     PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
     PerformanceController.on(EVENTS.PREF_CHANGED, this.setAvailableViews);
   }),
 
@@ -79,43 +79,64 @@ let DetailsView = {
 
     PerformanceController.off(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
     PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
     PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
     PerformanceController.off(EVENTS.PREF_CHANGED, this.setAvailableViews);
   }),
 
   /**
-   * Sets the possible views based off of prefs and server actor support by hiding/showing the
-   * buttons that select them and going to default view if currently selected.
-   * Called when a preference changes in `devtools.performance.ui.`.
+   * Sets the possible views based off of recording features and server actor support
+   * by hiding/showing the buttons that select them and going to default view
+   * if currently selected. Called when a preference changes in `devtools.performance.ui.`.
    */
   setAvailableViews: Task.async(function* () {
-    let mocks = gFront.getMocksInUse();
+    let recording = PerformanceController.getCurrentRecording();
+    let isRecording = recording && recording.isRecording();
+    let invalidCurrentView = false;
 
-    for (let [name, { view, pref, requires }] of Iterator(this.components)) {
-      let recording = PerformanceController.getCurrentRecording();
+    for (let [name, { view }] of Iterator(this.components)) {
+      let isSupported = this._isViewSupported(name, false);
 
-      let isRecorded = recording && !recording.isRecording();
-      // View is enabled by its corresponding pref
-      let isEnabled = !pref || PerformanceController.getOption(pref);
-      // View is supported by the server actor, and the requried actor is not being mocked
-      let isSupported = !requires || requires.every(r => !mocks[r]);
+      $(`toolbarbutton[data-view=${name}]`).hidden = !isSupported;
 
-      $(`toolbarbutton[data-view=${name}]`).hidden = !isRecorded || !(isEnabled && isSupported);
-
-      // If the view is currently selected and not enabled, go back to the
+      // If the view is currently selected and not supported, go back to the
       // default view.
-      if (!isEnabled && this.isViewSelected(view)) {
-        yield this.selectDefaultView();
+      if (!isSupported && this.isViewSelected(view)) {
+        invalidCurrentView = true;
       }
     }
+
+    // Two scenarios in which we select the default view.
+    //
+    // 1: If we currently have selected a view that is no longer valid due
+    // to feature support, and this isn't the first view, and the current recording
+    // is not recording.
+    //
+    // 2. If we have a finished recording and no panel was selected yet,
+    // use a default now that we have the recording configurations
+    if ((this._initialized  && !isRecording && invalidCurrentView) ||
+        (!this._initialized && !isRecording && recording)) {
+      yield this.selectDefaultView();
+    }
   }),
 
   /**
+   * Takes a view name and optionally if there must be a currently recording in progress.
+   *
+   * @param {string} viewName
+   * @param {boolean?} isRecording
+   * @return {boolean}
+   */
+  _isViewSupported: function (viewName, isRecording) {
+    let { features, actors } = this.components[viewName];
+    return PerformanceController.isFeatureSupported({ features, actors, isRecording });
+  },
+
+  /**
    * Select one of the DetailView's subviews to be rendered,
    * hiding the others.
    *
    * @param String viewName
    *        Name of the view to be shown.
    */
   selectView: Task.async(function *(viewName) {
     let component = this.components[viewName];
@@ -126,42 +147,53 @@ let DetailsView = {
     for (let button of $$("toolbarbutton[data-view]", this.toolbar)) {
       if (button.getAttribute("data-view") === viewName) {
         button.setAttribute("checked", true);
       } else {
         button.removeAttribute("checked");
       }
     }
 
+    // Set a flag indicating that a view was explicitly set based on a
+    // recording's features.
+    this._initialized = true;
+
     this.emit(EVENTS.DETAILS_VIEW_SELECTED, viewName);
   }),
 
   /**
    * Selects a default view based off of protocol support
    * and preferences enabled.
    */
   selectDefaultView: function () {
-    let { timeline: mockTimeline } = gFront.getMocksInUse();
-    // If timelines are mocked, the first view available is the js-calltree.
-    if (mockTimeline) {
+    // We want the waterfall to be default view in almost all cases, except when
+    // timeline actor isn't supported, or we have markers disabled (which should only
+    // occur temporarily via bug 1156499
+    if (this._isViewSupported("waterfall")) {
+      return this.selectView("waterfall");
+    } else {
+      // The JS CallTree should always be supported since the profiler
+      // actor is as old as the world.
       return this.selectView("js-calltree");
-    } else {
-      // In every other scenario with preferences and mocks, waterfall will
-      // be the default view.
-      return this.selectView("waterfall");
     }
   },
 
   /**
    * Checks if the provided view is currently selected.
    *
    * @param object viewObject
    * @return boolean
    */
   isViewSelected: function(viewObject) {
+    // If not initialized, and we have no recordings,
+    // no views are selected (even though there's a selected panel)
+    if (!this._initialized) {
+      return false;
+    }
+
     let selectedPanel = this.el.selectedPanel;
     let selectedId = selectedPanel.id;
 
     for (let [, { id, view }] of Iterator(this.components)) {
       if (id == selectedId && view == viewObject) {
         return true;
       }
     }
--- a/browser/devtools/performance/views/overview.js
+++ b/browser/devtools/performance/views/overview.js
@@ -2,131 +2,121 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 // No sense updating the overview more often than receiving data from the
 // backend. Make sure this isn't lower than DEFAULT_TIMELINE_DATA_PULL_TIMEOUT
 // in toolkit/devtools/server/actors/timeline.js
 const OVERVIEW_UPDATE_INTERVAL = 200; // ms
-
 const FRAMERATE_GRAPH_LOW_RES_INTERVAL = 100; // ms
 const FRAMERATE_GRAPH_HIGH_RES_INTERVAL = 16; // ms
-
-const MARKERS_GRAPH_HEADER_HEIGHT = 14; // px
-const MARKERS_GRAPH_ROW_HEIGHT = 10; // px
-const MARKERS_GROUP_VERTICAL_PADDING = 4; // px
+const GRAPH_REQUIREMENTS = {
+  timeline: {
+    actors: ["timeline"],
+    features: ["withMarkers"]
+  },
+  framerate: {
+    actors: ["timeline"],
+    features: ["withTicks"]
+  },
+  memory: {
+    actors: ["memory"],
+    features: ["withMemory"]
+  },
+}
 
 /**
  * View handler for the overview panel's time view, displaying
- * framerate, markers and memory over time.
+ * framerate, timeline and memory over time.
  */
 let OverviewView = {
+
   /**
    * Sets up the view with event binding.
    */
   initialize: function () {
-    if (gFront.getMocksInUse().timeline) {
+    this.graphs = new GraphsController({
+      root: $("#overview-pane"),
+      getBlueprint: () => PerformanceController.getTimelineBlueprint(),
+      getTheme: () => PerformanceController.getTheme(),
+    });
+
+    // If no timeline support, shut it all down.
+    if (!gFront.getActorSupport().timeline) {
       this.disable();
+      return;
     }
+
     this._onRecordingWillStart = this._onRecordingWillStart.bind(this);
     this._onRecordingStarted = this._onRecordingStarted.bind(this);
     this._onRecordingWillStop = this._onRecordingWillStop.bind(this);
     this._onRecordingStopped = this._onRecordingStopped.bind(this);
     this._onRecordingSelected = this._onRecordingSelected.bind(this);
     this._onRecordingTick = this._onRecordingTick.bind(this);
     this._onGraphSelecting = this._onGraphSelecting.bind(this);
+    this._onGraphRendered = this._onGraphRendered.bind(this);
     this._onPrefChanged = this._onPrefChanged.bind(this);
     this._onThemeChanged = this._onThemeChanged.bind(this);
 
     // Toggle the initial visibility of memory and framerate graph containers
     // based off of prefs.
-    $("#memory-overview").hidden = !PerformanceController.getOption("enable-memory");
-    $("#time-framerate").hidden = !PerformanceController.getOption("enable-framerate");
-
     PerformanceController.on(EVENTS.PREF_CHANGED, this._onPrefChanged);
     PerformanceController.on(EVENTS.THEME_CHANGED, this._onThemeChanged);
     PerformanceController.on(EVENTS.RECORDING_WILL_START, this._onRecordingWillStart);
     PerformanceController.on(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
     PerformanceController.on(EVENTS.RECORDING_WILL_STOP, this._onRecordingWillStop);
     PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
     PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
     PerformanceController.on(EVENTS.CONSOLE_RECORDING_STARTED, this._onRecordingStarted);
     PerformanceController.on(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStopped);
     PerformanceController.on(EVENTS.CONSOLE_RECORDING_WILL_STOP, this._onRecordingWillStop);
+    this.graphs.on("selecting", this._onGraphSelecting);
+    this.graphs.on("rendered", this._onGraphRendered);
   },
 
   /**
    * Unbinds events.
    */
   destroy: Task.async(function*() {
-    if (this.markersOverview) {
-      yield this.markersOverview.destroy();
-    }
-    if (this.memoryOverview) {
-      yield this.memoryOverview.destroy();
-    }
-    if (this.framerateGraph) {
-      yield this.framerateGraph.destroy();
-    }
-
     PerformanceController.off(EVENTS.PREF_CHANGED, this._onPrefChanged);
     PerformanceController.off(EVENTS.THEME_CHANGED, this._onThemeChanged);
     PerformanceController.off(EVENTS.RECORDING_WILL_START, this._onRecordingWillStart);
     PerformanceController.off(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
     PerformanceController.off(EVENTS.RECORDING_WILL_STOP, this._onRecordingWillStop);
     PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
     PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
     PerformanceController.off(EVENTS.CONSOLE_RECORDING_STARTED, this._onRecordingStarted);
     PerformanceController.off(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStopped);
     PerformanceController.off(EVENTS.CONSOLE_RECORDING_WILL_STOP, this._onRecordingWillStop);
+    this.graphs.off("selecting", this._onGraphSelecting);
+    this.graphs.off("rendered", this._onGraphRendered);
+    yield this.graphs.destroy();
   }),
 
   /**
    * Disabled in the event we're using a Timeline mock, so we'll have no
-   * markers, ticks or memory data to show, so just block rendering and hide
+   * timeline, ticks or memory data to show, so just block rendering and hide
    * the panel.
    */
   disable: function () {
     this._disabled = true;
-    $("#overview-pane").hidden = true;
+    this.graphs.disableAll();
   },
 
   /**
    * Returns the disabled status.
    *
    * @return boolean
    */
   isDisabled: function () {
     return this._disabled;
   },
 
   /**
-   * Sets the theme for the markers overview and memory overview.
-   */
-  setTheme: function (options={}) {
-    let theme = options.theme || PerformanceController.getTheme();
-
-    if (this.framerateGraph) {
-      this.framerateGraph.setTheme(theme);
-      this.framerateGraph.refresh({ force: options.redraw });
-    }
-
-    if (this.markersOverview) {
-      this.markersOverview.setTheme(theme);
-      this.markersOverview.refresh({ force: options.redraw });
-    }
-
-    if (this.memoryOverview) {
-      this.memoryOverview.setTheme(theme);
-      this.memoryOverview.refresh({ force: options.redraw });
-    }
-  },
-
-  /**
    * Sets the time interval selection for all graphs in this overview.
    *
    * @param object interval
    *        The { startTime, endTime }, in milliseconds.
    */
   setTimeInterval: function(interval, options = {}) {
     let recording = PerformanceController.getCurrentRecording();
     if (recording == null) {
@@ -134,17 +124,17 @@ let OverviewView = {
     }
     if (this.isDisabled()) {
       return;
     }
     let mapStart = () => 0;
     let mapEnd = () => recording.getDuration();
     let selection = { start: interval.startTime, end: interval.endTime };
     this._stopSelectionChangeEventPropagation = options.stopPropagation;
-    this.markersOverview.setMappedSelection(selection, { mapStart, mapEnd });
+    this.graphs.setMappedSelection(selection, { mapStart, mapEnd });
     this._stopSelectionChangeEventPropagation = false;
   },
 
   /**
    * Gets the time interval selection for all graphs in this overview.
    *
    * @return object
    *         The { startTime, endTime }, in milliseconds.
@@ -154,124 +144,35 @@ let OverviewView = {
     if (recording == null) {
       throw new Error("A recording should be available in order to get the selection.");
     }
     if (this.isDisabled()) {
       return { startTime: 0, endTime: recording.getDuration() };
     }
     let mapStart = () => 0;
     let mapEnd = () => recording.getDuration();
-    let selection = this.markersOverview.getMappedSelection({ mapStart, mapEnd });
+    let selection = this.graphs.getMappedSelection({ mapStart, mapEnd });
     return { startTime: selection.min, endTime: selection.max };
   },
 
   /**
-   * Sets up the markers overivew graph, if needed.
-   *
-   * @return object
-   *         A promise resolved to `true` when the graph was initialized.
-   */
-  _markersGraphAvailable: Task.async(function *() {
-    if (this.markersOverview) {
-      yield this.markersOverview.ready();
-      return true;
-    }
-    let blueprint = PerformanceController.getTimelineBlueprint();
-    this.markersOverview = new MarkersOverview($("#markers-overview"), blueprint);
-    this.markersOverview.headerHeight = MARKERS_GRAPH_HEADER_HEIGHT;
-    this.markersOverview.rowHeight = MARKERS_GRAPH_ROW_HEIGHT;
-    this.markersOverview.groupPadding = MARKERS_GROUP_VERTICAL_PADDING;
-    this.markersOverview.on("selecting", this._onGraphSelecting);
-    yield this.markersOverview.ready();
-    this.setTheme();
-    return true;
-  }),
-
-  /**
-   * Sets up the memory overview graph, if allowed and needed.
-   *
-   * @return object
-   *         A promise resolved to `true` if the graph was initialized and is
-   *         ready to use, `false` if the graph is disabled.
-   */
-  _memoryGraphAvailable: Task.async(function *() {
-    if (!PerformanceController.getOption("enable-memory")) {
-      return false;
-    }
-    if (this.memoryOverview) {
-      yield this.memoryOverview.ready();
-      return true;
-    }
-    this.memoryOverview = new MemoryGraph($("#memory-overview"));
-    yield this.memoryOverview.ready();
-    this.setTheme();
-
-    CanvasGraphUtils.linkAnimation(this.markersOverview, this.memoryOverview);
-    CanvasGraphUtils.linkSelection(this.markersOverview, this.memoryOverview);
-    return true;
-  }),
-
-  /**
-   * Sets up the framerate graph, if allowed and needed.
-   *
-   * @return object
-   *         A promise resolved to `true` if the graph was initialized and is
-   *         ready to use, `false` if the graph is disabled.
-   */
-  _framerateGraphAvailable: Task.async(function *() {
-    if (!PerformanceController.getOption("enable-framerate")) {
-      return false;
-    }
-    if (this.framerateGraph) {
-      yield this.framerateGraph.ready();
-      return true;
-    }
-    this.framerateGraph = new FramerateGraph($("#time-framerate"));
-    yield this.framerateGraph.ready();
-    this.setTheme();
-
-    CanvasGraphUtils.linkAnimation(this.markersOverview, this.framerateGraph);
-    CanvasGraphUtils.linkSelection(this.markersOverview, this.framerateGraph);
-    return true;
-  }),
-
-  /**
    * Method for handling all the set up for rendering the overview graphs.
    *
    * @param number resolution
    *        The fps graph resolution. @see Graphs.jsm
    */
   render: Task.async(function *(resolution) {
     if (this.isDisabled()) {
       return;
     }
     let recording = PerformanceController.getCurrentRecording();
-    let duration = recording.getDuration();
-    let markers = recording.getMarkers();
-    let memory = recording.getMemory();
-    let timestamps = recording.getTicks();
-
-    // Empty or older recordings might yield no markers, memory or timestamps.
-    if (markers && (yield this._markersGraphAvailable())) {
-      this.markersOverview.setData({ markers, duration });
-      this.emit(EVENTS.MARKERS_GRAPH_RENDERED);
-    }
-    if (memory && (yield this._memoryGraphAvailable())) {
-      this.memoryOverview.dataDuration = duration;
-      this.memoryOverview.setData(memory);
-      this.emit(EVENTS.MEMORY_GRAPH_RENDERED);
-    }
-    if (timestamps && (yield this._framerateGraphAvailable())) {
-      this.framerateGraph.dataDuration = duration;
-      yield this.framerateGraph.setDataFromTimestamps(timestamps, resolution);
-      this.emit(EVENTS.FRAMERATE_GRAPH_RENDERED);
-    }
+    yield this.graphs.render(recording.getAllData(), resolution);
 
     // Finished rendering all graphs in this overview.
-    this.emit(EVENTS.OVERVIEW_RENDERED);
+    this.emit(EVENTS.OVERVIEW_RENDERED, resolution);
   }),
 
   /**
    * Called at most every OVERVIEW_UPDATE_INTERVAL milliseconds
    * and uses data fetched from the controller to render
    * data into all the corresponding overview graphs.
    */
   _onRecordingTick: Task.async(function *() {
@@ -286,42 +187,24 @@ let OverviewView = {
     // Check here to see if there's still a _timeoutId, incase
     // `stop` was called before the _prepareNextTick call was executed.
     if (this.isRendering()) {
       this._timeoutId = setTimeout(this._onRecordingTick, OVERVIEW_UPDATE_INTERVAL);
     }
   },
 
   /**
-   * Fired when the graph selection has changed. Called by
-   * mouseup and scroll events.
-   */
-  _onGraphSelecting: function () {
-    if (this._stopSelectionChangeEventPropagation) {
-      return;
-    }
-    // If the range is smaller than a pixel (which can happen when performing
-    // a click on the graphs), treat this as a cleared selection.
-    let interval = this.getTimeInterval();
-    if (interval.endTime - interval.startTime < 1) {
-      this.emit(EVENTS.OVERVIEW_RANGE_CLEARED);
-    } else {
-      this.emit(EVENTS.OVERVIEW_RANGE_SELECTED, interval);
-    }
-  },
-
-  /**
    * Called when recording will start. No recording because it does not
    * exist yet, but can just disable from here. This will only trigger for
    * manual recordings.
    */
   _onRecordingWillStart: Task.async(function* () {
     this._onRecordingStateChange();
     yield this._checkSelection();
-    this.markersOverview.dropSelection();
+    this.graphs.dropSelection();
   }),
 
   /**
    * Called when recording actually starts.
    */
   _onRecordingStarted: function (_, recording) {
     this._onRecordingStateChange();
   },
@@ -353,22 +236,24 @@ let OverviewView = {
   /**
    * Called when a new recording is selected.
    */
   _onRecordingSelected: Task.async(function* (_, recording) {
     if (!recording) {
       return;
     }
     this._onRecordingStateChange();
+    this._setGraphVisibilityFromRecordingFeatures(recording);
+
     // If this recording is complete, render the high res graph
     if (!recording.isRecording()) {
       yield this.render(FRAMERATE_GRAPH_HIGH_RES_INTERVAL);
     }
     yield this._checkSelection(recording);
-    this.markersOverview.dropSelection();
+    this.graphs.dropSelection();
   }),
 
   /**
    * Called when a recording is starting, stopping, or about to start/stop.
    * Checks the current recording displayed to determine whether or not
    * the polling for rendering the overview graph needs to start or stop.
    */
   _onRecordingStateChange: function () {
@@ -402,58 +287,82 @@ let OverviewView = {
     return !!this._timeoutId;
   },
 
   /**
    * Makes sure the selection is enabled or disabled in all the graphs,
    * based on whether a recording currently exists and is not in progress.
    */
   _checkSelection: Task.async(function* (recording) {
-    let selectionEnabled = recording ? !recording.isRecording() : false;
-
-    if (yield this._markersGraphAvailable()) {
-      this.markersOverview.selectionEnabled = selectionEnabled;
-    }
-    if (yield this._memoryGraphAvailable()) {
-      this.memoryOverview.selectionEnabled = selectionEnabled;
-    }
-    if (yield this._framerateGraphAvailable()) {
-      this.framerateGraph.selectionEnabled = selectionEnabled;
-    }
+    let isEnabled = recording ? !recording.isRecording() : false;
+    yield this.graphs.selectionEnabled(isEnabled);
   }),
 
   /**
-   * Called whenever a preference in `devtools.performance.ui.` changes. Used
-   * to toggle the visibility of memory and framerate graphs.
+   * Fired when the graph selection has changed. Called by
+   * mouseup and scroll events.
+   */
+  _onGraphSelecting: function () {
+    if (this._stopSelectionChangeEventPropagation) {
+      return;
+    }
+    // If the range is smaller than a pixel (which can happen when performing
+    // a click on the graphs), treat this as a cleared selection.
+    let interval = this.getTimeInterval();
+    if (interval.endTime - interval.startTime < 1) {
+      this.emit(EVENTS.OVERVIEW_RANGE_CLEARED);
+    } else {
+      this.emit(EVENTS.OVERVIEW_RANGE_SELECTED, interval);
+    }
+  },
+
+  _onGraphRendered: function (_, graphName) {
+    switch (graphName) {
+      case "timeline":
+        this.emit(EVENTS.MARKERS_GRAPH_RENDERED);
+        break;
+      case "memory":
+        this.emit(EVENTS.MEMORY_GRAPH_RENDERED);
+        break;
+      case "framerate":
+        this.emit(EVENTS.FRAMERATE_GRAPH_RENDERED);
+        break;
+    }
+  },
+
+  /**
+   * Called whenever a preference in `devtools.performance.ui.` changes.
+   * Does not care about the enabling of memory/framerate graphs,
+   * because those will set values on a recording model, and
+   * the graphs will render based on the existence.
    */
   _onPrefChanged: Task.async(function* (_, prefName, prefValue) {
     switch (prefName) {
-      case "enable-memory": {
-        $("#memory-overview").hidden = !prefValue;
-        break;
-      }
-      case "enable-framerate": {
-        $("#time-framerate").hidden = !prefValue;
-        break;
-      }
       case "hidden-markers": {
-        if (yield this._markersGraphAvailable()) {
+        let graph;
+        if (graph = yield this.graphs.isAvailable("timeline")) {
           let blueprint = PerformanceController.getTimelineBlueprint();
-          this.markersOverview.setBlueprint(blueprint);
-          this.markersOverview.refresh({ force: true });
+          graph.setBlueprint(blueprint);
+          graph.refresh({ force: true });
         }
         break;
       }
     }
   }),
 
+  _setGraphVisibilityFromRecordingFeatures: function (recording) {
+    for (let [graphName, requirements] of Iterator(GRAPH_REQUIREMENTS)) {
+      this.graphs.enable(graphName, PerformanceController.isFeatureSupported(requirements));
+    }
+  },
+
   /**
    * Called when `devtools.theme` changes.
    */
   _onThemeChanged: function (_, theme) {
-    this.setTheme({ theme, redraw: true });
+    this.graphs.setTheme({ theme, redraw: true });
   },
 
   toString: () => "[object OverviewView]"
 };
 
 // Decorates the OverviewView as an EventEmitter
 EventEmitter.decorate(OverviewView);
--- a/browser/devtools/shared/inplace-editor.js
+++ b/browser/devtools/shared/inplace-editor.js
@@ -78,16 +78,20 @@ Cu.import("resource://gre/modules/devtoo
  *    {boolean} stopOnTab:
  *       If true, the tab key will not advance the editor to the next
  *       focusable element.
  *    {boolean} stopOnShiftTab:
  *       If true, shift tab will not advance the editor to the previous
  *       focusable element.
  *    {string} trigger: The DOM event that should trigger editing,
  *      defaults to "click"
+ *    {boolean} multiline: Should the editor be a multiline textarea?
+ *      defaults to false
+ *    {boolean} trimOutput: Should the returned string be trimmed?
+ *      defaults to true
  */
 function editableField(aOptions)
 {
   return editableItem(aOptions, function(aElement, aEvent) {
     if (!aOptions.element.inplaceEditor) {
       new InplaceEditor(aOptions, aEvent);
     }
   });
@@ -184,16 +188,17 @@ function InplaceEditor(aOptions, aEvent)
   this.doc = doc;
   this.elt.inplaceEditor = this;
 
   this.change = aOptions.change;
   this.done = aOptions.done;
   this.destroy = aOptions.destroy;
   this.initial = aOptions.initial ? aOptions.initial : this.elt.textContent;
   this.multiline = aOptions.multiline || false;
+  this.trimOutput = aOptions.trimOutput === undefined ? true : !!aOptions.trimOutput;
   this.stopOnShiftTab = !!aOptions.stopOnShiftTab;
   this.stopOnTab = !!aOptions.stopOnTab;
   this.stopOnReturn = !!aOptions.stopOnReturn;
   this.contentType = aOptions.contentType || CONTENT_TYPES.PLAIN_TEXT;
   this.property = aOptions.property;
   this.popup = aOptions.popup;
 
   this._onBlur = this._onBlur.bind(this);
@@ -249,16 +254,22 @@ function InplaceEditor(aOptions, aEvent)
   EventEmitter.decorate(this);
 }
 
 exports.InplaceEditor = InplaceEditor;
 
 InplaceEditor.CONTENT_TYPES = CONTENT_TYPES;
 
 InplaceEditor.prototype = {
+
+  get currentInputValue() {
+    let val = this.trimOutput ? this.input.value.trim() : this.input.value;
+    return val;
+  },
+
   _createInput: function InplaceEditor_createEditor()
   {
     this.input =
       this.doc.createElementNS(HTML_NS, this.multiline ? "textarea" : "input");
     this.input.inplaceEditor = this;
     this.input.classList.add("styleinspector-propertyeditor");
     this.input.value = this.initial;
 
@@ -393,17 +404,17 @@ InplaceEditor.prototype = {
     }
 
     this.input.value = newValue.value;
     this.input.setSelectionRange(newValue.start, newValue.end);
     this._doValidation();
 
     // Call the user's change handler if available.
     if (this.change) {
-      this.change(this.input.value.trim());
+      this.change(this.currentInputValue);
     }
 
     return true;
   },
 
   /**
    * Increment the property value based on the property type.
    *
@@ -785,18 +796,18 @@ InplaceEditor.prototype = {
   {
     if (this._applied) {
       return;
     }
 
     this._applied = true;
 
     if (this.done) {
-      let val = this.input.value.trim();
-      return this.done(this.cancelled ? this.initial : val, !this.cancelled, direction);
+      let val = this.cancelled ? this.initial : this.currentInputValue;
+      return this.done(val, !this.cancelled, direction);
     }
 
     return null;
   },
 
   /**
    * Handle loss of focus by calling done if it hasn't been called yet.
    */
@@ -1029,17 +1040,17 @@ InplaceEditor.prototype = {
 
     // Update size if we're autosizing.
     if (this._measurement) {
       this._updateSize();
     }
 
     // Call the user's change handler if available.
     if (this.change) {
-      this.change(this.input.value.trim());
+      this.change(this.currentInputValue);
     }
   },
 
   /**
    * Fire validation callback with current input
    */
   _doValidation: function()
   {
--- a/browser/devtools/shared/profiler/frame-utils.js
+++ b/browser/devtools/shared/profiler/frame-utils.js
@@ -112,24 +112,30 @@ exports.filterPlatformData = function fi
   return result;
 }
 
 /**
  * Helper for getting an nsIURL instance out of a string.
  */
 function nsIURL(url) {
   let cached = gNSURLStore.get(url);
-  if (cached) {
+  // If we cached a valid URI, or `null` in the case
+  // of a failure, return it.
+  if (cached !== void 0) {
     return cached;
   }
   let uri = null;
   try {
     uri = Services.io.newURI(url, null, null).QueryInterface(Ci.nsIURL);
+    // Access the host, because the constructor doesn't necessarily throw
+    // if it's invalid, but accessing the host can throw as well
+    uri.host;
   } catch(e) {
     // The passed url string is invalid.
+    uri = null;
   }
   gNSURLStore.set(url, uri);
   return uri;
 }
 
 /**
  * Takes a `host` string from an nsIURL instance and
  * returns the same string, or null, if it's an invalid host.
--- a/browser/devtools/shared/test/browser.ini
+++ b/browser/devtools/shared/test/browser.ini
@@ -65,17 +65,19 @@ support-files =
 [browser_graphs-09f.js]
 [browser_graphs-10a.js]
 [browser_graphs-10b.js]
 [browser_graphs-11a.js]
 [browser_graphs-11b.js]
 [browser_graphs-12.js]
 [browser_graphs-13.js]
 [browser_graphs-14.js]
-[browser_inplace-editor.js]
+[browser_inplace-editor-01.js]
+[browser_inplace-editor-02.js]
+[browser_graphs-15.js]
 [browser_layoutHelpers.js]
 skip-if = e10s # Layouthelpers test should not run in a content page.
 [browser_layoutHelpers-getBoxQuads.js]
 skip-if = e10s # Layouthelpers test should not run in a content page.
 [browser_mdn-docs-01.js]
 [browser_mdn-docs-02.js]
 [browser_num-l10n.js]
 [browser_observableobject.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/test/browser_graphs-15.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that graph widgets correctly emit mouse input events.
+
+const FAST_FPS = 60;
+const SLOW_FPS = 10;
+// Each element represents a second
+const FRAMES= [FAST_FPS, FAST_FPS, FAST_FPS, SLOW_FPS, FAST_FPS];
+const TEST_DATA = [];
+const INTERVAL = 100;
+const DURATION = 5000; // 5s
+let t = 0;
+for (let frameRate of FRAMES) {
+  for (let i = 0; i < frameRate; i++) {
+    let delta = Math.floor(1000 / frameRate); // Duration between frames at this rate
+    t += delta;
+    TEST_DATA.push(t);
+  }
+}
+
+let {LineGraphWidget} = Cu.import("resource:///modules/devtools/Graphs.jsm", {});
+let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
+
+add_task(function*() {
+  yield promiseTab("about:blank");
+  yield performTest();
+  gBrowser.removeCurrentTab();
+});
+
+function* performTest() {
+  let [host, win, doc] = yield createHost();
+  let graph = new LineGraphWidget(doc.body, "fps");
+
+  yield testGraph(graph);
+
+  yield graph.destroy();
+  host.destroy();
+}
+
+function* testGraph(graph) {
+
+  console.log("test data", TEST_DATA);
+  yield graph.setDataFromTimestamps(TEST_DATA, INTERVAL, DURATION);
+  is(graph._avgTooltip.querySelector("[text=value]").textContent, "50",
+    "The average tooltip displays the correct value.");
+}
rename from browser/devtools/shared/test/browser_inplace-editor.js
rename to browser/devtools/shared/test/browser_inplace-editor-01.js
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/test/browser_inplace-editor-02.js
@@ -0,0 +1,91 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
+let {editableField, getInplaceEditorForSpan: inplaceEditor} = devtools.require("devtools/shared/inplace-editor");
+
+// Test that the trimOutput option for the inplace editor works correctly.
+
+add_task(function*() {
+  yield promiseTab("data:text/html;charset=utf-8,inline editor tests");
+  let [host, win, doc] = yield createHost();
+
+  yield testNonTrimmed(doc);
+  yield testTrimmed(doc);
+
+  host.destroy();
+  gBrowser.removeCurrentTab();
+});
+
+function testNonTrimmed(doc) {
+  info("Testing the trimOutput=false option");
+  let def = promise.defer();
+
+  let initial = "\nMultiple\nLines\n";
+  let changed = " \nMultiple\nLines\n with more whitespace ";
+  createInplaceEditorAndClick({
+    trimOutput: false,
+    multiline: true,
+    initial: initial,
+    start: function(editor) {
+      is(editor.input.value, initial, "Explicit initial value should be used.");
+      editor.input.value = changed;
+      EventUtils.sendKey("return");
+    },
+    done: onDone(changed, true, def)
+  }, doc);
+
+  return def.promise;
+}
+
+function testTrimmed(doc) {
+  info("Testing the trimOutput=true option (default value)");
+  let def = promise.defer();
+
+  let initial = "\nMultiple\nLines\n";
+  let changed = " \nMultiple\nLines\n with more whitespace ";
+  createInplaceEditorAndClick({
+    initial: initial,
+    multiline: true,
+    start: function(editor) {
+      is(editor.input.value, initial, "Explicit initial value should be used.");
+      editor.input.value = changed;
+      EventUtils.sendKey("return");
+    },
+    done: onDone(changed.trim(), true, def)
+  }, doc);
+
+  return def.promise;
+}
+
+function onDone(value, isCommit, def) {
+  return function(actualValue, actualCommit) {
+    info("Inplace-editor's done callback executed, checking its state");
+    is(actualValue, value, "The value is correct");
+    is(actualCommit, isCommit, "The commit boolean is correct");
+    def.resolve();
+  }
+}
+
+function createInplaceEditorAndClick(options, doc) {
+  doc.body.innerHTML = "";
+  let span = options.element = createSpan(doc);
+
+  info("Creating an inplace-editor field");
+  editableField(options);
+
+  info("Clicking on the inplace-editor field to turn to edit mode");
+  span.click();
+}
+
+function createSpan(doc) {
+  info("Creating a new span element");
+  let span = doc.createElement("span");
+  span.setAttribute("tabindex", "0");
+  span.textContent = "Edit Me!";
+  doc.body.appendChild(span);
+  return span;
+}
--- a/browser/devtools/shared/timeline/global.js
+++ b/browser/devtools/shared/timeline/global.js
@@ -16,17 +16,18 @@ const L10N = new ViewHelpers.L10N(STRING
 /**
  * A simple schema for mapping markers to the timeline UI. The keys correspond
  * to marker names, while the values are objects with the following format:
  *   - group: the row index in the timeline overview graph; multiple markers
  *            can be added on the same row. @see <overview.js/buildGraphImage>
  *   - label: the label used in the waterfall to identify the marker
  *   - colorName: the name of the DevTools color used for this marker. If adding
  *                a new color, be sure to check that there's an entry for
- *                `.marker-details-bullet.{COLORNAME}` for the equivilent entry.
+ *                `.marker-details-bullet.{COLORNAME}` for the equivilent entry
+ *                in ./browser/themes/shared/devtools/performance.inc.css
  *                https://developer.mozilla.org/en-US/docs/Tools/DevToolsColors
  *
  * Whenever this is changed, browser_timeline_waterfall-styles.js *must* be
  * updated as well.
  */
 const TIMELINE_BLUEPRINT = {
   "Styles": {
     group: 0,
@@ -63,13 +64,18 @@ const TIMELINE_BLUEPRINT = {
     colorName: "highlight-lightorange",
     label: L10N.getStr("timeline.label.parseXML")
   },
   "ConsoleTime": {
     group: 2,
     colorName: "highlight-bluegrey",
     label: L10N.getStr("timeline.label.consoleTime")
   },
+  "GarbageCollection": {
+    group: 1,
+    colorName: "highlight-red",
+    label: L10N.getStr("timeline.label.garbageCollection")
+  },
 };
 
 // Exported symbols.
 exports.L10N = L10N;
 exports.TIMELINE_BLUEPRINT = TIMELINE_BLUEPRINT;
--- a/browser/devtools/shared/widgets/Graphs.jsm
+++ b/browser/devtools/shared/widgets/Graphs.jsm
@@ -1297,24 +1297,25 @@ LineGraphWidget.prototype = Heritage.ext
    * framerate, for example, from a sequence of timestamps.
    *
    * @param array timestamps
    *        A list of numbers representing time, ordered ascending. For example,
    *        this can be the raw data received from the framerate actor, which
    *        represents the elapsed time on each refresh driver tick.
    * @param number interval
    *        The maximum amount of time to wait between calculations.
+   * @param number duration
+   *        The duration of the recording in milliseconds.
    */
-  setDataFromTimestamps: Task.async(function*(timestamps, interval) {
+  setDataFromTimestamps: Task.async(function*(timestamps, interval, duration) {
     let {
       plottedData,
       plottedMinMaxSum
     } = yield CanvasGraphUtils._performTaskInWorker("plotTimestampsGraph", {
-      timestamps: timestamps,
-      interval: interval
+      timestamps, interval, duration
     });
 
     this._tempMinMaxSum = plottedMinMaxSum;
     this.setData(plottedData);
   }),
 
   /**
    * Renders the graph's data source.
--- a/browser/devtools/shared/widgets/GraphsWorker.js
+++ b/browser/devtools/shared/widgets/GraphsWorker.js
@@ -16,43 +16,48 @@ self.onmessage = e => {
   }
 };
 
 /**
  * @see LineGraphWidget.prototype.setDataFromTimestamps in Graphs.jsm
  * @param number id
  * @param array timestamps
  * @param number interval
+ * @param number duration
  */
 function plotTimestampsGraph(id, args) {
   let plottedData = plotTimestamps(args.timestamps, args.interval);
-  let plottedMinMaxSum = getMinMaxSum(plottedData);
+  let plottedMinMaxSum = getMinMaxAvg(plottedData, args.timestamps, args.duration);
 
   let response = { id, plottedData, plottedMinMaxSum };
   self.postMessage(response);
 }
 
 /**
  * Gets the min, max and average of the values in an array.
  * @param array source
+ * @param array timestamps
+ * @param number duration
  * @return object
  */
-function getMinMaxSum(source) {
+function getMinMaxAvg(source, timestamps, duration) {
   let totalTicks = source.length;
+  let totalFrames = timestamps.length;
   let maxValue = Number.MIN_SAFE_INTEGER;
   let minValue = Number.MAX_SAFE_INTEGER;
-  let avgValue = 0;
-  let sumValues = 0;
+  // Calculate the average by counting how many frames occurred
+  // in the duration of the recording, rather than average the frame points
+  // we have, as that weights higher FPS, as there'll be more timestamps for those
+  // values
+  let avgValue = totalFrames / (duration / 1000);
 
   for (let { value } of source) {
     maxValue = Math.max(value, maxValue);
     minValue = Math.min(value, minValue);
-    sumValues += value;
   }
-  avgValue = sumValues / totalTicks;
 
   return { minValue, maxValue, avgValue };
 }
 
 /**
  * Takes a list of numbers and plots them on a line graph representing
  * the rate of occurences in a specified interval.
  *
--- a/browser/locales/en-US/chrome/browser/devtools/timeline.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/timeline.properties
@@ -39,16 +39,17 @@ timeline.records=RECORDS
 timeline.label.styles2=Recalculate Style
 timeline.label.reflow2=Layout
 timeline.label.paint=Paint
 timeline.label.javascript2=Function Call
 timeline.label.parseHTML=Parse HTML
 timeline.label.parseXML=Parse XML
 timeline.label.domevent=DOM Event
 timeline.label.consoleTime=Console
+timeline.label.garbageCollection=GC Event
 
 # LOCALIZATION NOTE (graphs.memory):
 # This string is displayed in the memory graph of the Performance tool,
 # as the unit used to memory consumption. This label should be kept
 # AS SHORT AS POSSIBLE so it doesn't obstruct important parts of the graph.
 graphs.memory=MB
 
 # LOCALIZATION NOTE (timeline.markerDetailFormat):
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -573,28 +573,28 @@ menuitem:not([type]):not(.menuitem-toolt
 :-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1[open="true"],
 :-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:hover:active {
   padding: 3px;
 }
 
 .findbar-button > .toolbarbutton-text,
 :-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
 :-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
-:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-badge-container > .toolbarbutton-icon,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-badge-container,
 :-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-icon {
   -moz-margin-end: 0;
   padding: 2px 6px;
   border: 1px solid transparent;
   border-radius: 2px;
   transition-property: background-color, border-color;
   transition-duration: 150ms;
 }
 
 :-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-icon,
-:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-badge-container > .toolbarbutton-icon,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-badge-container,
 :-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
   padding: 3px 7px;
 }
 
 /* Help SDK icons fit: */
 toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-icon,
 toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-badge-container > .toolbarbutton-icon {
   width: 16px;
@@ -609,17 +609,17 @@ toolbarbutton[constrain-size="true"][cui
   -moz-padding-start: 7px;
   -moz-padding-end: 5px;
 }
 
 .findbar-button:not(:-moz-any([checked="true"],[disabled="true"])):hover > .toolbarbutton-text,
 :-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1[open]:not([disabled=true]) > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
 :-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):not([open]):hover > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
 :-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):not([open]):hover > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
-:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):hover > .toolbarbutton-badge-container > .toolbarbutton-icon,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):hover > .toolbarbutton-badge-container,
 :-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):hover > .toolbarbutton-icon {
   background: var(--toolbarbutton-hover-background);
   border-width: 1px;
   border-style: solid;
   border-color: var(--toolbarbutton-hover-bordercolor);
   box-shadow: var(--toolbarbutton-hover-boxshadow);
 }
 
@@ -627,17 +627,17 @@ toolbarbutton[constrain-size="true"][cui
 :-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1[open] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
   margin-top: 4px;
   margin-bottom: 4px;
 }
 
 .findbar-button:not([disabled=true]):-moz-any([checked="true"],:hover:active) > .toolbarbutton-text,
 :-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-button:not([disabled=true]):-moz-any(:hover:active, [open="true"]) > .toolbarbutton-icon,
 :-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1[open="true"] > .toolbarbutton-menubutton-dropmarker:not([disabled=true]) > .dropmarker-icon,
-:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):-moz-any([open],[checked],:hover:active) > .toolbarbutton-badge-container > .toolbarbutton-icon,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):-moz-any([open],[checked],:hover:active) > .toolbarbutton-badge-container,
 :-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):-moz-any([open],[checked],:hover:active) > .toolbarbutton-icon {
   background: var(--toolbarbutton-active-background);
   box-shadow: var(--toolbarbutton-active-boxshadow);
   border-width: 1px;
   border-style: solid;
   border-color: var(--toolbarbutton-active-bordercolor);
   transition-duration: 10ms;
 }
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -362,16 +362,17 @@ browser.jar:
   skin/classic/browser/devtools/tool-webconsole.svg         (../shared/devtools/images/tool-webconsole.svg)
   skin/classic/browser/devtools/tool-debugger.svg           (../shared/devtools/images/tool-debugger.svg)
   skin/classic/browser/devtools/tool-debugger-paused.svg    (../shared/devtools/images/tool-debugger-paused.svg)
   skin/classic/browser/devtools/tool-inspector.svg          (../shared/devtools/images/tool-inspector.svg)
   skin/classic/browser/devtools/tool-inspector.svg          (../shared/devtools/images/tool-inspector.svg)
   skin/classic/browser/devtools/tool-styleeditor.svg        (../shared/devtools/images/tool-styleeditor.svg)
   skin/classic/browser/devtools/tool-storage.svg            (../shared/devtools/images/tool-storage.svg)
   skin/classic/browser/devtools/tool-profiler.svg           (../shared/devtools/images/tool-profiler.svg)
+  skin/classic/browser/devtools/tool-profiler-active.svg    (../shared/devtools/images/tool-profiler-active.svg)
   skin/classic/browser/devtools/tool-network.svg            (../shared/devtools/images/tool-network.svg)
   skin/classic/browser/devtools/tool-scratchpad.svg         (../shared/devtools/images/tool-scratchpad.svg)
   skin/classic/browser/devtools/tool-webaudio.svg           (../shared/devtools/images/tool-webaudio.svg)
   skin/classic/browser/devtools/close.png                   (../shared/devtools/images/close.png)
   skin/classic/browser/devtools/close@2x.png                (../shared/devtools/images/close@2x.png)
   skin/classic/browser/devtools/vview-delete.png            (../shared/devtools/images/vview-delete.png)
   skin/classic/browser/devtools/vview-delete@2x.png         (../shared/devtools/images/vview-delete@2x.png)
   skin/classic/browser/devtools/vview-edit.png              (../shared/devtools/images/vview-edit.png)
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -495,16 +495,17 @@ browser.jar:
   skin/classic/browser/devtools/tool-webconsole.svg         (../shared/devtools/images/tool-webconsole.svg)
   skin/classic/browser/devtools/tool-debugger.svg           (../shared/devtools/images/tool-debugger.svg)
   skin/classic/browser/devtools/tool-debugger-paused.svg    (../shared/devtools/images/tool-debugger-paused.svg)
   skin/classic/browser/devtools/tool-inspector.svg          (../shared/devtools/images/tool-inspector.svg)
   skin/classic/browser/devtools/tool-inspector.svg          (../shared/devtools/images/tool-inspector.svg)
   skin/classic/browser/devtools/tool-styleeditor.svg        (../shared/devtools/images/tool-styleeditor.svg)
   skin/classic/browser/devtools/tool-storage.svg            (../shared/devtools/images/tool-storage.svg)
   skin/classic/browser/devtools/tool-profiler.svg           (../shared/devtools/images/tool-profiler.svg)
+  skin/classic/browser/devtools/tool-profiler-active.svg    (../shared/devtools/images/tool-profiler-active.svg)
   skin/classic/browser/devtools/tool-network.svg            (../shared/devtools/images/tool-network.svg)
   skin/classic/browser/devtools/tool-scratchpad.svg         (../shared/devtools/images/tool-scratchpad.svg)
   skin/classic/browser/devtools/tool-webaudio.svg           (../shared/devtools/images/tool-webaudio.svg)
   skin/classic/browser/devtools/close.png                   (../shared/devtools/images/close.png)
   skin/classic/browser/devtools/close@2x.png                (../shared/devtools/images/close@2x.png)
   skin/classic/browser/devtools/vview-delete.png            (../shared/devtools/images/vview-delete.png)
   skin/classic/browser/devtools/vview-delete@2x.png         (../shared/devtools/images/vview-delete@2x.png)
   skin/classic/browser/devtools/vview-edit.png              (../shared/devtools/images/vview-edit.png)
--- a/browser/themes/shared/aboutNetError.css
+++ b/browser/themes/shared/aboutNetError.css
@@ -115,38 +115,34 @@ div#certificateErrorReportingPanel {
 div#certificateErrorReportingPanel:-moz-dir(ltr) {
   left: 34%;
 }
 
 div#certificateErrorReportingPanel:-moz-dir(rtl) {
   right: 0;
 }
 
+#errorStatePanel {
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+  justify-content: space-between;
+  align-content: space-between;
+  align-items: flex-start;
+}
+
 span#hostname {
   font-weight: bold;
 }
 
 #automaticallyReportInFuture {
   cursor: pointer;
 }
 
-#reportingState {
-  padding-left: 150px;
-}
-
 #reportSendingMessage {
-  position: relative;
-  display: none;
+  /* adjust the line-height to match the link */
+  line-height: 22px;
 }
 
 #reportSentMessage {
-  position: relative;
-  display: none;
+  /* adjust the line-height to match the link */
+  line-height: 22px;
 }
-
-button#reportCertificateError {
-  position: relative;
-}
-
-button#reportCertificateErrorRetry {
-  position: relative;
-  display: none;
-}
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/devtools/images/tool-profiler-active.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 16 16">
+<g fill="#71c054" fill-rule="evenodd">
+<path d="m8,1c-3.9,0-7,3.1-7,7s3.1,7 7,7c3.9,0 7-3.1 7-7s-3.1-7-7-7zm-.1,12c-2.8,0-5-2.2-5-5 0-2.8 2.2-5 5-5s5,2.2 5,5c0,2.8-2.2,5-5,5z"/>
+<path d="m8,6.9c.6,0 1.1,.5 1.1,1.1 0,.6-.5,1.1-1.1,1.1-.6,0-1.1-.5-1.1-1.1 0-.6 .5-1.1 1.1-1.1z"/>
+<path d="m11.3,4.6l-3.9,2.5 1.5,1.4 2.4-3.9z"/>
+<path opacity=".4" d="m4.6,10c.7,1.2 2,2 3.4,2 1.5,0 2.7-.8 3.4-2h-6.8z"/>
+<g opacity=".3">
+<path d="m7.1,5.1l-.6-1.3-.9,.4 .7,1.3c.2-.1 .5-.3 .8-.4z"/>
+<path d="m9.8,5.6l.7-1.4-.9-.4-.7,1.3c.3,.2 .6,.3 .9,.5z"/>
+<path d="m10.8,7c.1,.3 .2,.7 .2,1h2v-1h-2.2z"/>
+<path d="m5,8c0-.3 .1-.7 .2-1h-2.2l-.1,1h2.1z"/>
+</g>
+</g>
+</svg>
--- a/browser/themes/shared/devtools/performance.inc.css
+++ b/browser/themes/shared/devtools/performance.inc.css
@@ -442,16 +442,23 @@
   background-color: var(--theme-highlight-green);
 }
 #performance-filter-menupopup > menuitem.highlight-lightorange:before,
 .marker-details-bullet.highlight-lightorange,
 .waterfall-marker-bar.highlight-lightorange,
 .waterfall-marker-bullet.highlight-lightorange {
   background-color: var(--theme-highlight-lightorange);
 }
+#performance-filter-menupopup > menuitem.highlight-red:before,
+.marker-details-bullet.highlight-red,
+.waterfall-marker-bar.highlight-red,
+.waterfall-marker-bullet.highlight-red {
+  background-color: var(--theme-highlight-red);
+}
+
 
 #waterfall-details > * {
   padding-top: 3px;
 }
 
 .marker-details-labelname {
   -moz-padding-end: 4px;
 }
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -37,20 +37,16 @@
   margin: 0;
   padding: 0;
 }
 
 .tabbrowser-tab {
   -moz-box-align: stretch;
 }
 
-.tabbrowser-tab[remote] {
-  text-decoration: underline;
-}
-
 /* The selected tab should appear above adjacent tabs, .tabs-newtab-button and the highlight of #nav-bar */
 .tabbrowser-tab[visuallyselected=true] {
   position: relative;
   z-index: 2;
 }
 
 .tab-background-middle {
   -moz-box-flex: 1;
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -437,16 +437,17 @@ browser.jar:
         skin/classic/browser/devtools/tool-options.svg              (../shared/devtools/images/tool-options.svg)
         skin/classic/browser/devtools/tool-webconsole.svg           (../shared/devtools/images/tool-webconsole.svg)
         skin/classic/browser/devtools/tool-debugger.svg             (../shared/devtools/images/tool-debugger.svg)
         skin/classic/browser/devtools/tool-debugger-paused.svg      (../shared/devtools/images/tool-debugger-paused.svg)
         skin/classic/browser/devtools/tool-inspector.svg            (../shared/devtools/images/tool-inspector.svg)
         skin/classic/browser/devtools/tool-styleeditor.svg          (../shared/devtools/images/tool-styleeditor.svg)
         skin/classic/browser/devtools/tool-storage.svg              (../shared/devtools/images/tool-storage.svg)
         skin/classic/browser/devtools/tool-profiler.svg             (../shared/devtools/images/tool-profiler.svg)
+        skin/classic/browser/devtools/tool-profiler-active.svg      (../shared/devtools/images/tool-profiler-active.svg)
         skin/classic/browser/devtools/tool-network.svg              (../shared/devtools/images/tool-network.svg)
         skin/classic/browser/devtools/tool-scratchpad.svg           (../shared/devtools/images/tool-scratchpad.svg)
         skin/classic/browser/devtools/tool-webaudio.svg             (../shared/devtools/images/tool-webaudio.svg)
         skin/classic/browser/devtools/close.png                     (../shared/devtools/images/close.png)
         skin/classic/browser/devtools/close@2x.png                  (../shared/devtools/images/close@2x.png)
         skin/classic/browser/devtools/vview-delete.png              (../shared/devtools/images/vview-delete.png)
         skin/classic/browser/devtools/vview-delete@2x.png           (../shared/devtools/images/vview-delete@2x.png)
         skin/classic/browser/devtools/vview-edit.png                (../shared/devtools/images/vview-edit.png)
--- a/build/mobile/robocop/AndroidManifest.xml.in
+++ b/build/mobile/robocop/AndroidManifest.xml.in
@@ -1,12 +1,15 @@
 #filter substitution
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="org.mozilla.roboexample.test"
+#ifdef MOZ_ANDROID_SHARED_ID
+    android:sharedUserId="@MOZ_ANDROID_SHARED_ID@"
+#endif
     android:versionCode="1"
     android:versionName="1.0" >
 
     <uses-sdk android:minSdkVersion="8" />
 
     <instrumentation
         android:name="org.mozilla.gecko.FennecInstrumentationTestRunner"
         android:targetPackage="@ANDROID_PACKAGE_NAME@" />
--- a/build/mobile/robocop/Makefile.in
+++ b/build/mobile/robocop/Makefile.in
@@ -34,24 +34,29 @@ ANDROID_ASSETS_DIR := $(TESTPATH)/assets
   $(NULL)
 
 java-harness := $(addprefix $(srcdir)/,$(_JAVA_HARNESS))
 java-tests   := \
   $(wildcard $(TESTPATH)/*.java) \
   $(wildcard $(TESTPATH)/components/*.java) \
   $(wildcard $(TESTPATH)/helpers/*.java)
 
-PP_TARGETS        += manifest
-manifest          := $(srcdir)/AndroidManifest.xml.in
-manifest_TARGET   := AndroidManifest.xml
+PP_TARGETS += manifest
+manifest := $(srcdir)/AndroidManifest.xml.in
+manifest_TARGET := export
+manifest_FLAGS += \
+  -DMOZ_ANDROID_SHARED_ID='$(ANDROID_PACKAGE_NAME).sharedID' \
+  -DMOZ_ANDROID_SHARED_ACCOUNT_TYPE='$(ANDROID_PACKAGE_NAME)_sync' \
+  $(NULL)
+
 ANDROID_MANIFEST_FILE := $(CURDIR)/AndroidManifest.xml
 
 # Install robocop configs and helper
 INSTALL_TARGETS += robocop
-robocop_TARGET  := libs
+robocop_TARGET  := export
 robocop_DEST    := $(CURDIR)
 robocop_FILES   := \
   $(TESTPATH)/robocop.ini \
   $(TESTPATH)/robocop_autophone.ini \
   $(NULL)
 robocop-deps := $(notdir $(robocop_FILES))
 
 ROBOCOP_FILES := \
@@ -63,17 +68,18 @@ ROBOCOP_FILES := \
   $(wildcard $(TESTPATH)/*.xml) \
   $(wildcard $(TESTPATH)/*.ogg) \
   $(wildcard $(TESTPATH)/*.mp4) \
   $(wildcard $(TESTPATH)/*.webm) \
   $(wildcard $(TESTPATH)/*.swf) \
   $(wildcard $(TESTPATH)/reader_mode_pages) \
   $(NULL)
 
-ROBOCOP_DEST = $(DEPTH)/_tests/testing/mochitest/tests/robocop/
+ROBOCOP_DEST := $(DEPTH)/_tests/testing/mochitest/tests/robocop/
+ROBOCOP_TARGET := export
 INSTALL_TARGETS += ROBOCOP
 
 GARBAGE += \
   AndroidManifest.xml \
   $(robocop-deps) \
   $(testconstants-dep) \
   $(NULL)
 
--- a/configure.in
+++ b/configure.in
@@ -1495,19 +1495,21 @@ if test "$GNU_CC"; then
         _WARNINGS_CFLAGS="${_WARNINGS_CFLAGS} -Werror=int-to-pointer-cast"
         _WARNINGS_CFLAGS="${_WARNINGS_CFLAGS} -Werror=multichar"
         _WARNINGS_CFLAGS="${_WARNINGS_CFLAGS} -Werror=nonnull"
         _WARNINGS_CFLAGS="${_WARNINGS_CFLAGS} -Werror=pointer-arith"
         _WARNINGS_CFLAGS="${_WARNINGS_CFLAGS} -Werror=pointer-sign"
         _WARNINGS_CFLAGS="${_WARNINGS_CFLAGS} -Werror=return-type"
         _WARNINGS_CFLAGS="${_WARNINGS_CFLAGS} -Werror=sequence-point"
         _WARNINGS_CFLAGS="${_WARNINGS_CFLAGS} -Werror=trigraphs"
+        _WARNINGS_CFLAGS="${_WARNINGS_CFLAGS} -Werror=uninitialized"
         _WARNINGS_CFLAGS="${_WARNINGS_CFLAGS} -Werror=unknown-pragmas"
 
         MOZ_C_SUPPORTS_WARNING(-Werror=, non-literal-null-conversion, ac_c_has_werror_non_literal_null_conversion)
+        MOZ_C_SUPPORTS_WARNING(-Werror=, sometimes-uninitialized, ac_c_has_sometimes_uninitialized)
     fi
 
     # Turn off the following warnings that -Wall turns on:
     # -Wno-unused - lots of violations in third-party code
     #
     _WARNINGS_CFLAGS="${_WARNINGS_CFLAGS} -Wno-unused"
 
     if test -z "$INTEL_CC" -a -z "$CLANG_CC"; then
@@ -1588,19 +1590,21 @@ if test "$GNU_CXX"; then
         _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Werror=missing-braces"
         _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Werror=parentheses"
         _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Werror=pointer-arith"
         _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Werror=return-type"
         _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Werror=sequence-point"
         _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Werror=switch"
         _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Werror=trigraphs"
         _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Werror=type-limits"
+        _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Werror=uninitialized"
         _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Werror=unused-label"
 
         MOZ_CXX_SUPPORTS_WARNING(-Werror=, non-literal-null-conversion, ac_cxx_has_werror_non_literal_null_conversion)
+        MOZ_CXX_SUPPORTS_WARNING(-Werror=, sometimes-uninitialized, ac_cxx_has_sometimes_uninitialized)
     fi
 
     # Turn off the following warnings that -Wall turns on:
     # -Wno-invalid-offsetof - we use offsetof on non-POD types frequently
     # -Wno-inline-new-delete - we inline 'new' and 'delete' in mozalloc
     #   for performance reasons, and because GCC and clang accept it (though
     #   clang warns about it).
     #
@@ -5187,25 +5191,27 @@ fi;
 
 dnl ========================================================
 dnl = Apple platform decoder support
 dnl ========================================================
 if test "$MOZ_WIDGET_TOOLKIT" = "cocoa"; then
   MOZ_APPLEMEDIA=1
 fi
 
+if test "$COMPILE_ENVIRONMENT"; then
 if test -n "$MOZ_APPLEMEDIA"; then
   AC_DEFINE(MOZ_APPLEMEDIA)
   # hack in frameworks for fmp4 - see bug 1029974
   # We load VideoToolbox and CoreMedia dynamically, so they don't appear here.
   LDFLAGS="$LDFLAGS -framework AudioToolbox"
   dnl Verify CoreMedia is available.
   AC_CHECK_HEADER([CoreMedia/CoreMedia.h], [],
     [AC_MSG_ERROR([MacOS X 10.7 SDK or later is required])])
 fi
+fi # COMPILE_ENVIRONMENT
 
 dnl ========================================================
 dnl = DirectShow support
 dnl ========================================================
 if test "$OS_ARCH" = "WINNT"; then
     dnl Enable DirectShow support by default.
     MOZ_DIRECTSHOW=1
 fi
@@ -6901,24 +6907,21 @@ AC_SUBST(MOZ_STACKWALKING)
 
 dnl ========================================================
 dnl = Disable treating compiler warnings as errors
 dnl ========================================================
 if test -z "$MOZ_ENABLE_WARNINGS_AS_ERRORS"; then
    WARNINGS_AS_ERRORS=''
 elif test "$GNU_CC"; then
     # Prevent the following GCC warnings from being treated as errors:
-    # -Wuninitialized - too many false positives
     # -Wmaybe-uninitialized - too many false positives
     # -Wdeprecated-declarations - we don't want our builds held hostage when a
     #   platform-specific API becomes deprecated.
     # -Wfree-nonheap-object - false positives during PGO
     # -Warray-bounds - false positives depending on optimization
-    MOZ_C_SUPPORTS_WARNING(-W, no-error=uninitialized, ac_c_has_noerror_uninitialized)
-    MOZ_CXX_SUPPORTS_WARNING(-W, no-error=uninitialized, ac_cxx_has_noerror_uninitialized)
     MOZ_C_SUPPORTS_WARNING(-W, no-error=maybe-uninitialized, ac_c_has_noerror_maybe_uninitialized)
     MOZ_CXX_SUPPORTS_WARNING(-W, no-error=maybe-uninitialized, ac_cxx_has_noerror_maybe_uninitialized)
     MOZ_C_SUPPORTS_WARNING(-W, no-error=deprecated-declarations, ac_c_has_noerror_deprecated_declarations)
     MOZ_CXX_SUPPORTS_WARNING(-W, no-error=deprecated-declarations, ac_cxx_has_noerror_deprecated_declarations)
     MOZ_C_SUPPORTS_WARNING(-W, no-error=array-bounds, ac_c_has_noerror_array_bounds)
     MOZ_CXX_SUPPORTS_WARNING(-W, no-error=array-bounds, ac_cxx_has_noerror_array_bounds)
 
     if test -n "$MOZ_PGO"; then
--- a/dom/animation/Animation.cpp
+++ b/dom/animation/Animation.cpp
@@ -34,17 +34,17 @@ Animation::WrapObject(JSContext* aCx, JS
 
 // ---------------------------------------------------------------------------
 //
 // Animation interface:
 //
 // ---------------------------------------------------------------------------
 
 void
-Animation::SetEffect(KeyframeEffectReadonly* aEffect)
+Animation::SetEffect(KeyframeEffectReadOnly* aEffect)
 {
   if (mEffect) {
     mEffect->SetParentTime(Nullable<TimeDuration>());
   }
   mEffect = aEffect;
   if (mEffect) {
     mEffect->SetParentTime(GetCurrentTime());
   }
--- a/dom/animation/Animation.h
+++ b/dom/animation/Animation.h
@@ -7,17 +7,17 @@
 #define mozilla_dom_Animation_h
 
 #include "nsWrapperCache.h"
 #include "nsCycleCollectionParticipant.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/TimeStamp.h" // for TimeStamp, TimeDuration
 #include "mozilla/dom/AnimationBinding.h" // for AnimationPlayState
 #include "mozilla/dom/DocumentTimeline.h" // for DocumentTimeline
-#include "mozilla/dom/KeyframeEffect.h" // for KeyframeEffectReadonly
+#include "mozilla/dom/KeyframeEffect.h" // for KeyframeEffectReadOnly
 #include "mozilla/dom/Promise.h" // for Promise
 #include "nsCSSProperty.h" // for nsCSSProperty
 
 // X11 has a #define for CurrentTime.
 #ifdef CurrentTime
 #undef CurrentTime
 #endif
 
@@ -81,18 +81,18 @@ public:
    */
   enum class LimitBehavior {
     AutoRewind,
     Continue
   };
 
   // Animation interface methods
 
-  KeyframeEffectReadonly* GetEffect() const { return mEffect; }
-  void SetEffect(KeyframeEffectReadonly* aEffect);
+  KeyframeEffectReadOnly* GetEffect() const { return mEffect; }
+  void SetEffect(KeyframeEffectReadOnly* aEffect);
   DocumentTimeline* Timeline() const { return mTimeline; }
   Nullable<TimeDuration> GetStartTime() const { return mStartTime; }
   void SetStartTime(const Nullable<TimeDuration>& aNewStartTime);
   Nullable<TimeDuration> GetCurrentTime() const;
   void SetCurrentTime(const TimeDuration& aNewCurrentTime);
   double PlaybackRate() const { return mPlaybackRate; }
   void SetPlaybackRate(double aPlaybackRate);
   AnimationPlayState PlayState() const;
@@ -313,17 +313,17 @@ protected:
   StickyTimeDuration EffectEnd() const;
 
   nsIDocument* GetRenderedDocument() const;
   nsPresContext* GetPresContext() const;
   virtual css::CommonAnimationManager* GetAnimationManager() const = 0;
   AnimationCollection* GetCollection() const;
 
   nsRefPtr<DocumentTimeline> mTimeline;
-  nsRefPtr<KeyframeEffectReadonly> mEffect;
+  nsRefPtr<KeyframeEffectReadOnly> mEffect;
   // The beginning of the delay period.
   Nullable<TimeDuration> mStartTime; // Timeline timescale
   Nullable<TimeDuration> mHoldTime;  // Animation timescale
   Nullable<TimeDuration> mPendingReadyTime; // Timeline timescale
   Nullable<TimeDuration> mPreviousCurrentTime; // Animation timescale
   double mPlaybackRate;
 
   // A Promise that is replaced on each call to Play() (and in future Pause())
rename from dom/animation/AnimationEffectReadonly.cpp
rename to dom/animation/AnimationEffectReadOnly.cpp
--- a/dom/animation/AnimationEffectReadonly.cpp
+++ b/dom/animation/AnimationEffectReadOnly.cpp
@@ -1,23 +1,23 @@
 /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "mozilla/dom/AnimationEffectReadonly.h"
-#include "mozilla/dom/AnimationEffectReadonlyBinding.h"
+#include "mozilla/dom/AnimationEffectReadOnly.h"
+#include "mozilla/dom/AnimationEffectReadOnlyBinding.h"
 
 namespace mozilla {
 namespace dom {
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(AnimationEffectReadonly, mParent)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(AnimationEffectReadOnly, mParent)
 
-NS_IMPL_CYCLE_COLLECTING_ADDREF(AnimationEffectReadonly)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(AnimationEffectReadonly)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(AnimationEffectReadOnly)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(AnimationEffectReadOnly)
 
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AnimationEffectReadonly)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AnimationEffectReadOnly)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 } // namespace dom
 } // namespace mozilla
rename from dom/animation/AnimationEffectReadonly.h
rename to dom/animation/AnimationEffectReadOnly.h
--- a/dom/animation/AnimationEffectReadonly.h
+++ b/dom/animation/AnimationEffectReadOnly.h
@@ -9,28 +9,28 @@
 #include "nsISupports.h"
 #include "nsWrapperCache.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsCOMPtr.h"
 
 namespace mozilla {
 namespace dom {
 
-class AnimationEffectReadonly
+class AnimationEffectReadOnly
   : public nsISupports
   , public nsWrapperCache
 {
 protected:
-  virtual ~AnimationEffectReadonly() { }
+  virtual ~AnimationEffectReadOnly() { }
 
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AnimationEffectReadonly)
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AnimationEffectReadOnly)
 
-  explicit AnimationEffectReadonly(nsISupports* aParent)
+  explicit AnimationEffectReadOnly(nsISupports* aParent)
     : mParent(aParent)
   {
   }
 
   nsISupports* GetParentObject() const { return mParent; }
 
 protected:
   nsCOMPtr<nsISupports> mParent;
--- a/dom/animation/KeyframeEffect.cpp
+++ b/dom/animation/KeyframeEffect.cpp
@@ -54,45 +54,45 @@ ComputedTimingFunction::GetValue(double 
 }
 
 // In the Web Animations model, the time fraction can be outside the range
 // [0.0, 1.0] but it shouldn't be Infinity.
 const double ComputedTiming::kNullTimeFraction = PositiveInfinity<double>();
 
 namespace dom {
 
-NS_IMPL_CYCLE_COLLECTION_INHERITED(KeyframeEffectReadonly,
-                                   AnimationEffectReadonly,
+NS_IMPL_CYCLE_COLLECTION_INHERITED(KeyframeEffectReadOnly,
+                                   AnimationEffectReadOnly,
                                    mTarget)
 
-NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(KeyframeEffectReadonly,
-                                               AnimationEffectReadonly)
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(KeyframeEffectReadOnly,
+                                               AnimationEffectReadOnly)
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(KeyframeEffectReadonly)
-NS_INTERFACE_MAP_END_INHERITING(AnimationEffectReadonly)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(KeyframeEffectReadOnly)
+NS_INTERFACE_MAP_END_INHERITING(AnimationEffectReadOnly)
 
-NS_IMPL_ADDREF_INHERITED(KeyframeEffectReadonly, AnimationEffectReadonly)
-NS_IMPL_RELEASE_INHERITED(KeyframeEffectReadonly, AnimationEffectReadonly)
+NS_IMPL_ADDREF_INHERITED(KeyframeEffectReadOnly, AnimationEffectReadOnly)
+NS_IMPL_RELEASE_INHERITED(KeyframeEffectReadOnly, AnimationEffectReadOnly)
 
 JSObject*
-KeyframeEffectReadonly::WrapObject(JSContext* aCx,
+KeyframeEffectReadOnly::WrapObject(JSContext* aCx,
                                    JS::Handle<JSObject*> aGivenProto)
 {
-  return KeyframeEffectReadonlyBinding::Wrap(aCx, this, aGivenProto);
+  return KeyframeEffectReadOnlyBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
-KeyframeEffectReadonly::SetParentTime(Nullable<TimeDuration> aParentTime)
+KeyframeEffectReadOnly::SetParentTime(Nullable<TimeDuration> aParentTime)
 {
   mParentTime = aParentTime;
 }
 
 ComputedTiming
-KeyframeEffectReadonly::GetComputedTimingAt(
+KeyframeEffectReadOnly::GetComputedTimingAt(
                           const Nullable<TimeDuration>& aLocalTime,
                           const AnimationTiming& aTiming)
 {
   const TimeDuration zeroDuration;
 
   // Currently we expect negative durations to be picked up during CSS
   // parsing but when we start receiving timing parameters from other sources
   // we will need to clamp negative durations here.
@@ -212,99 +212,99 @@ KeyframeEffectReadonly::GetComputedTimin
   if (thisIterationReverse) {
     result.mTimeFraction = 1.0 - result.mTimeFraction;
   }
 
   return result;
 }
 
 StickyTimeDuration
-KeyframeEffectReadonly::ActiveDuration(const AnimationTiming& aTiming)
+KeyframeEffectReadOnly::ActiveDuration(const AnimationTiming& aTiming)
 {
   if (aTiming.mIterationCount == mozilla::PositiveInfinity<float>()) {
     // An animation that repeats forever has an infinite active duration
     // unless its iteration duration is zero, in which case it has a zero
     // active duration.
     const StickyTimeDuration zeroDuration;
     return aTiming.mIterationDuration == zeroDuration
            ? zeroDuration
            : StickyTimeDuration::Forever();
   }
   return StickyTimeDuration(
     aTiming.mIterationDuration.MultDouble(aTiming.mIterationCount));
 }
 
 // http://w3c.github.io/web-animations/#in-play
 bool
-KeyframeEffectReadonly::IsInPlay(const Animation& aAnimation) const
+KeyframeEffectReadOnly::IsInPlay(const Animation& aAnimation) const
 {
   if (IsFinishedTransition() ||
       aAnimation.PlayState() == AnimationPlayState::Finished) {
     return false;
   }
 
   return GetComputedTiming().mPhase == ComputedTiming::AnimationPhase_Active;
 }
 
 // http://w3c.github.io/web-animations/#current
 bool
-KeyframeEffectReadonly::IsCurrent(const Animation& aAnimation) const
+KeyframeEffectReadOnly::IsCurrent(const Animation& aAnimation) const
 {
   if (IsFinishedTransition() ||
       aAnimation.PlayState() == AnimationPlayState::Finished) {
     return false;
   }
 
   ComputedTiming computedTiming = GetComputedTiming();
   return computedTiming.mPhase == ComputedTiming::AnimationPhase_Before ||
          computedTiming.mPhase == ComputedTiming::AnimationPhase_Active;
 }
 
 bool
-KeyframeEffectReadonly::IsInEffect() const
+KeyframeEffectReadOnly::IsInEffect() const
 {
   if (IsFinishedTransition()) {
     return false;
   }
 
   ComputedTiming computedTiming = GetComputedTiming();
   return computedTiming.mTimeFraction != ComputedTiming::kNullTimeFraction;
 }
 
 const AnimationProperty*
-KeyframeEffectReadonly::GetAnimationOfProperty(nsCSSProperty aProperty) const
+KeyframeEffectReadOnly::GetAnimationOfProperty(nsCSSProperty aProperty) const
 {
   for (size_t propIdx = 0, propEnd = mProperties.Length();
        propIdx != propEnd; ++propIdx) {
     if (aProperty == mProperties[propIdx].mProperty) {
       const AnimationProperty* result = &mProperties[propIdx];
       if (!result->mWinsInCascade) {
         result = nullptr;
       }
       return result;
     }
   }
   return nullptr;
 }
 
 bool
-KeyframeEffectReadonly::HasAnimationOfProperties(
+KeyframeEffectReadOnly::HasAnimationOfProperties(
                           const nsCSSProperty* aProperties,
                           size_t aPropertyCount) const
 {
   for (size_t i = 0; i < aPropertyCount; i++) {
     if (HasAnimationOfProperty(aProperties[i])) {
       return true;
     }
   }
   return false;
 }
 
 void
-KeyframeEffectReadonly::ComposeStyle(
+KeyframeEffectReadOnly::ComposeStyle(
                           nsRefPtr<css::AnimValuesStyleRule>& aStyleRule,
                           nsCSSPropertySet& aSetProperties)
 {
   ComputedTiming computedTiming = GetComputedTiming();
 
   // If the time fraction is null, we don't have fill data for the current
   // time so we shouldn't animate.
   if (computedTiming.mTimeFraction == ComputedTiming::kNullTimeFraction) {
--- a/dom/animation/KeyframeEffect.h
+++ b/dom/animation/KeyframeEffect.h
@@ -10,17 +10,17 @@
 #include "nsCycleCollectionParticipant.h"
 #include "nsCSSPseudoElements.h"
 #include "nsIDocument.h"
 #include "nsWrapperCache.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/StickyTimeDuration.h"
 #include "mozilla/StyleAnimationValue.h"
 #include "mozilla/TimeStamp.h"
-#include "mozilla/dom/AnimationEffectReadonly.h"
+#include "mozilla/dom/AnimationEffectReadOnly.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/Nullable.h"
 #include "nsSMILKeySpline.h"
 #include "nsStyleStruct.h" // for nsTimingFunction
 
 struct JSContext;
 class nsCSSPropertySet;
 
@@ -180,47 +180,47 @@ struct AnimationProperty
     return !(*this == aOther);
   }
 };
 
 struct ElementPropertyTransition;
 
 namespace dom {
 
-class KeyframeEffectReadonly : public AnimationEffectReadonly
+class KeyframeEffectReadOnly : public AnimationEffectReadOnly
 {
 public:
-  KeyframeEffectReadonly(nsIDocument* aDocument,
+  KeyframeEffectReadOnly(nsIDocument* aDocument,
                          Element* aTarget,
                          nsCSSPseudoElements::Type aPseudoType,
                          const AnimationTiming &aTiming,
                          const nsSubstring& aName)
-    : AnimationEffectReadonly(aDocument)
+    : AnimationEffectReadOnly(aDocument)
     , mTarget(aTarget)
     , mTiming(aTiming)
     , mName(aName)
     , mIsFinishedTransition(false)
     , mPseudoType(aPseudoType)
   {
     MOZ_ASSERT(aTarget, "null animation target is not yet supported");
   }
 
   NS_DECL_ISUPPORTS_INHERITED
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(KeyframeEffectReadonly,
-                                                        AnimationEffectReadonly)
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(KeyframeEffectReadOnly,
+                                                        AnimationEffectReadOnly)
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
   virtual ElementPropertyTransition* AsTransition() { return nullptr; }
   virtual const ElementPropertyTransition* AsTransition() const {
     return nullptr;
   }
 
-  // KeyframeEffectReadonly interface
+  // KeyframeEffectReadOnly interface
   Element* GetTarget() const {
     // Currently we only implement Element.getAnimations() which only
     // returns animations targetting Elements so this should never
     // be called for an animation that targets a pseudo-element.
     MOZ_ASSERT(mPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement,
                "Requesting the target of a KeyframeEffect that targets a"
                " pseudo-element is not yet supported.");
     return mTarget;
@@ -327,17 +327,17 @@ public:
   // Updates |aStyleRule| with the animation values produced by this
   // Animation for the current time except any properties already contained
   // in |aSetProperties|.
   // Any updated properties are added to |aSetProperties|.
   void ComposeStyle(nsRefPtr<css::AnimValuesStyleRule>& aStyleRule,
                     nsCSSPropertySet& aSetProperties);
 
 protected:
-  virtual ~KeyframeEffectReadonly() { }
+  virtual ~KeyframeEffectReadOnly() { }
 
   nsCOMPtr<Element> mTarget;
   Nullable<TimeDuration> mParentTime;
 
   AnimationTiming mTiming;
   nsString mName;
   // A flag to mark transitions that have finished and are due to
   // be removed on the next throttle-able cycle.
--- a/dom/animation/moz.build
+++ b/dom/animation/moz.build
@@ -4,30 +4,30 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 MOCHITEST_MANIFESTS += ['test/mochitest.ini']
 MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
 
 EXPORTS.mozilla.dom += [
     'Animation.h',
-    'AnimationEffectReadonly.h',
+    'AnimationEffectReadOnly.h',
     'AnimationTimeline.h',
     'DocumentTimeline.h',
     'KeyframeEffect.h',
 ]
 
 EXPORTS.mozilla += [
     'AnimationUtils.h',
     'PendingAnimationTracker.h',
 ]
 
 UNIFIED_SOURCES += [
     'Animation.cpp',
-    'AnimationEffectReadonly.cpp',
+    'AnimationEffectReadOnly.cpp',
     'AnimationTimeline.cpp',
     'DocumentTimeline.cpp',
     'KeyframeEffect.cpp',
     'PendingAnimationTracker.cpp',
 ]
 
 FAIL_ON_WARNINGS = True
 
--- a/dom/base/nsDOMMutationObserver.cpp
+++ b/dom/base/nsDOMMutationObserver.cpp
@@ -340,17 +340,17 @@ void nsMutationReceiver::NodeWillBeDestr
   NS_ASSERTION(!mParent, "Shouldn't have mParent here!");
   Disconnect(true);
 }
 
 void
 nsAnimationReceiver::RecordAnimationMutation(Animation* aAnimation,
                                              AnimationMutation aMutationType)
 {
-  KeyframeEffectReadonly* effect = aAnimation->GetEffect();
+  KeyframeEffectReadOnly* effect = aAnimation->GetEffect();
   if (!effect) {
     return;
   }
 
   Element* animationTarget = effect->GetTarget();
   if (!animationTarget) {
     return;
   }
--- a/dom/base/nsNodeUtils.cpp
+++ b/dom/base/nsNodeUtils.cpp
@@ -213,17 +213,17 @@ nsNodeUtils::ContentRemoved(nsINode* aCo
   IMPL_MUTATION_NOTIFICATION(ContentRemoved, aContainer,
                              (document, container, aChild, aIndexInContainer,
                               aPreviousSibling));
 }
 
 static inline Element*
 GetTarget(Animation* aAnimation)
 {
-  KeyframeEffectReadonly* effect = aAnimation->GetEffect();
+  KeyframeEffectReadOnly* effect = aAnimation->GetEffect();
   if (!effect) {
     return nullptr;
   }
 
   Element* target;
   nsCSSPseudoElements::Type pseudoType;
   effect->GetTarget(target, pseudoType);
 
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -83,17 +83,17 @@ DOMInterfaces = {
 'MozAbortablePromise': {
     'nativeType': 'mozilla::dom::AbortablePromise',
 },
 
 'AbstractWorker': {
     'concrete': False
 },
 
-'AnimationEffectReadonly': {
+'AnimationEffectReadOnly': {
     'concrete': False
 },
 
 'AnimationTimeline': {
     'concrete': False
 },
 
 'AnonymousContent': {
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/browserElement_AllowEmbedAppsInNestedOOIframe.js
@@ -0,0 +1,38 @@
+/* Any copyright is dedicated to the public domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Bug 1097479 - Allow embed remote apps or widgets in content
+// process if nested-oop is enabled
+
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+browserElementTestHelpers.setEnabledPref(true);
+browserElementTestHelpers.addPermission();
+
+SpecialPowers.setAllAppsLaunchable(true);
+
+function runTest() {
+  var iframe = document.createElement('iframe');
+  iframe.setAttribute('mozbrowser', 'true');
+  iframe.addEventListener('mozbrowsershowmodalprompt', function(e) {
+    is(e.detail.message == 'app', true, e.detail.message);
+    SimpleTest.finish();
+  });
+
+  document.body.appendChild(iframe);
+
+  var context = { 'url': 'http://example.org',
+                  'appId': SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID,
+                  'isInBrowserElement': true };
+  SpecialPowers.pushPermissions([
+    {'type': 'browser', 'allow': 1, 'context': context},
+    {'type': 'embed-apps', 'allow': 1, 'context': context}
+  ], function() {
+    iframe.src = 'http://example.org/tests/dom/browser-element/mochitest/file_browserElement_AllowEmbedAppsInNestedOOIframe.html';
+  });
+}
+
+addEventListener('testready', () => {
+  SpecialPowers.pushPrefEnv({"set": [["dom.ipc.tabs.nested.enabled", true]]}, runTest);
+});
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/file_browserElement_AllowEmbedAppsInNestedOOIframe.html
@@ -0,0 +1,19 @@
+<html>
+<head>
+<script type="text/javascript">
+  addEventListener('load', function(e) {
+    var iframe = document.createElement('iframe');
+    iframe.setAttribute('mozbrowser', 'true');
+    iframe.setAttribute('remote', 'true');
+    iframe.setAttribute('mozapp', 'http://example.org/manifest.webapp');
+    iframe.addEventListener('mozbrowsershowmodalprompt', function(e) {
+      alert(e.detail.message);
+    });
+    document.body.appendChild(iframe);
+    iframe.src = 'http://example.org/tests/dom/browser-element/mochitest/file_browserElement_AppFramePermission.html';
+  });
+</script>
+</head>
+<body>
+</body>
+</html>
--- a/dom/browser-element/mochitest/mochitest-oop.ini
+++ b/dom/browser-element/mochitest/mochitest-oop.ini
@@ -11,16 +11,18 @@ support-files =
 
 [test_browserElement_oop_ThemeColor.html]
 [test_browserElement_inproc_ErrorSecurity.html]
 skip-if = toolkit=='gonk'
 [test_browserElement_inproc_OpenMixedProcess.html]
 skip-if = toolkit=='gonk' || (toolkit == 'gonk' && !debug)
 [test_browserElement_oop_Alert.html]
 [test_browserElement_oop_AlertInFrame.html]
+[test_browserElement_oop_AllowEmbedAppsInNestedOOIframe.html]
+skip-if = toolkit=='gonk'
 [test_browserElement_oop_AppFramePermission.html]
 skip-if = (toolkit == 'gonk' && !debug)
 [test_browserElement_oop_AppWindowNamespace.html]
 skip-if = (toolkit == 'gonk' && !debug)
 [test_browserElement_oop_Auth.html]
 skip-if = (toolkit == 'gonk' && !debug)
 [test_browserElement_oop_BackForward.html]
 [test_browserElement_oop_BadScreenshot.html]
--- a/dom/browser-element/mochitest/mochitest.ini
+++ b/dom/browser-element/mochitest/mochitest.ini
@@ -1,16 +1,17 @@
 [DEFAULT]
 skip-if = buildapp == 'mulet' || (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) || e10s
 support-files =
   ../../../browser/base/content/test/general/audio.ogg
   ../../../dom/media/test/short-video.ogv
   browserElementTestHelpers.js
   browserElement_Alert.js
   browserElement_AlertInFrame.js
+  browserElement_AllowEmbedAppsInNestedOOIframe.js
   browserElement_AppFramePermission.js
   browserElement_AppWindowNamespace.js
   browserElement_Auth.js
   browserElement_BackForward.js
   browserElement_BadScreenshot.js
   browserElement_ThemeColor.js
   browserElement_BrowserWindowNamespace.js
   browserElement_BrowserWindowResize.js
@@ -68,16 +69,17 @@ support-files =
   browserElement_XFrameOptions.js
   browserElement_XFrameOptionsAllowFrom.js
   browserElement_XFrameOptionsDeny.js
   browserElement_XFrameOptionsSameOrigin.js
   browserElement_XFrameOptionsSameOrigin.js
   browserElement_GetContentDimensions.js
   file_browserElement_AlertInFrame.html
   file_browserElement_AlertInFrame_Inner.html
+  file_browserElement_AllowEmbedAppsInNestedOOIframe.html
   file_browserElement_AppFramePermission.html
   file_browserElement_AppWindowNamespace.html
   file_browserElement_ThemeColor.html
   file_browserElement_BrowserWindowNamespace.html
   file_browserElement_CloseApp.html
   file_browserElement_CloseFromOpener.html
   file_browserElement_CookiesNotThirdParty.html
   file_browserElement_DisallowEmbedAppsInOOP.html
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/test_browserElement_oop_AllowEmbedAppsInNestedOOIframe.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for Bug 1097479</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="browserElementTestHelpers.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script type="application/javascript;version=1.7" src="browserElement_AllowEmbedAppsInNestedOOIframe.js">
+</script>
+</body>
+</html>
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -632,16 +632,20 @@ public:
                        dom::ImageData* pixels, ErrorResult& rv);
     // Allow whatever element types the bindings are willing to pass
     // us in TexSubImage2D
     template<class ElementType>
     void TexSubImage2D(GLenum rawTexImageTarget, GLint level, GLint xoffset,
                        GLint yoffset, GLenum format, GLenum type,
                        ElementType& elt, ErrorResult& rv)
     {
+        // TODO: Consolidate all the parameter validation
+        // checks. Instead of spreading out the cheks in multple
+        // places, consolidate into one spot.
+
         if (IsContextLost())
             return;
 
         if (!ValidateTexImageTarget(rawTexImageTarget,
                                     WebGLTexImageFunc::TexSubImage,
                                     WebGLTexDimensions::Tex2D))
         {
             ErrorInvalidEnumInfo("texSubImage2D: target", rawTexImageTarget);
@@ -1303,17 +1307,17 @@ protected:
 
     // If jsArrayType is MaxTypedArrayViewType, it means no array.
     void TexImage2D_base(TexImageTarget texImageTarget, GLint level,
                          GLenum internalFormat, GLsizei width,
                          GLsizei height, GLsizei srcStrideOrZero, GLint border,
                          GLenum format, GLenum type, void* data,
                          uint32_t byteLength, js::Scalar::Type jsArrayType,
                          WebGLTexelFormat srcFormat, bool srcPremultiplied);
-    void TexSubImage2D_base(TexImageTarget texImageTarget, GLint level,
+    void TexSubImage2D_base(GLenum texImageTarget, GLint level,
                             GLint xoffset, GLint yoffset, GLsizei width,
                             GLsizei height, GLsizei srcStrideOrZero,
                             GLenum format, GLenum type, void* pixels,
                             uint32_t byteLength, js::Scalar::Type jsArrayType,
                             WebGLTexelFormat srcFormat, bool srcPremultiplied);
 
     void TexParameter_base(GLenum target, GLenum pname,
                            GLint* const out_intParam,
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -3418,30 +3418,35 @@ WebGLContext::TexImage2D(GLenum rawTarge
     return TexImage2D_base(rawTarget, level, internalformat, pixels->Width(),
                            pixels->Height(), 4*pixels->Width(), 0,
                            format, type, pixelData, pixelDataLength, js::Scalar::MaxTypedArrayViewType,
                            WebGLTexelFormat::RGBA8, false);
 }
 
 
 void
-WebGLContext::TexSubImage2D_base(TexImageTarget texImageTarget, GLint level,
+WebGLContext::TexSubImage2D_base(GLenum rawImageTarget, GLint level,
                                  GLint xoffset, GLint yoffset,
                                  GLsizei width, GLsizei height, GLsizei srcStrideOrZero,
                                  GLenum format, GLenum type,
                                  void* data, uint32_t byteLength,
                                  js::Scalar::Type jsArrayType,
                                  WebGLTexelFormat srcFormat, bool srcPremultiplied)
 {
     const WebGLTexImageFunc func = WebGLTexImageFunc::TexSubImage;
     const WebGLTexDimensions dims = WebGLTexDimensions::Tex2D;
 
     if (type == LOCAL_GL_HALF_FLOAT_OES)
         type = LOCAL_GL_HALF_FLOAT;
 
+    if (!ValidateTexImageTarget(rawImageTarget, func, dims))
+        return;
+
+    TexImageTarget texImageTarget(rawImageTarget);
+
     WebGLTexture* tex = ActiveBoundTextureForTexImageTarget(texImageTarget);
     if (!tex)
         return ErrorInvalidOperation("texSubImage2D: no texture bound on active texture unit");
 
     if (!tex->HasImageInfoAt(texImageTarget, level))
         return ErrorInvalidOperation("texSubImage2D: no previously defined texture image");
 
     const WebGLTexture::ImageInfo& imageInfo = tex->ImageInfoAt(texImageTarget, level);
--- a/dom/canvas/WebGLElementArrayCache.cpp
+++ b/dom/canvas/WebGLElementArrayCache.cpp
@@ -14,17 +14,20 @@
 #include "mozilla/MemoryReporting.h"
 
 namespace mozilla {
 
 static void
 UpdateUpperBound(uint32_t* const out_upperBound, uint32_t newBound)
 {
     MOZ_ASSERT(out_upperBound);
-    *out_upperBound = std::max(*out_upperBound, newBound);
+    // Move *out_upperBound to a local variable to work around a false positive
+    // -Wuninitialized gcc warning about std::max() in PGO builds.
+    uint32_t upperBound = *out_upperBound;
+    *out_upperBound = std::max(upperBound, newBound);
 }
 
 /* WebGLElementArrayCacheTree contains most of the implementation of
  * WebGLElementArrayCache, which performs WebGL element array buffer validation
  * for drawElements.
  *
  * Attention: Here lie nontrivial data structures, bug-prone algorithms, and
  * non-canonical tweaks! Whence the explanatory comments, and compiled unit
--- a/dom/canvas/compiledtest/TestWebGLElementArrayCache.cpp
+++ b/dom/canvas/compiledtest/TestWebGLElementArrayCache.cpp
@@ -110,17 +110,17 @@ void CheckValidateAllTypes(WebGLElementA
 template<typename T>
 void
 CheckSanity()
 {
   const size_t numElems = 64; // should be significantly larger than tree leaf size to
                         // ensure we exercise some nontrivial tree-walking
   T data[numElems] = {1,0,3,1,2,6,5,4}; // intentionally specify only 8 elements for now
   size_t numBytes = numElems * sizeof(T);
-  MOZ_ASSERT(numBytes == sizeof(data));
+  MOZ_RELEASE_ASSERT(numBytes == sizeof(data));
 
   GLenum type = GLType<T>();
 
   WebGLElementArrayCache c;
   c.BufferData(data, numBytes);
   CheckValidate(true,  c, type, 6, 0, 8);
   CheckValidate(false, c, type, 5, 0, 8);
   CheckValidate(true,  c, type, 3, 0, 3);
@@ -134,34 +134,34 @@ CheckSanity()
 
   // now test a somewhat larger size to ensure we exceed the size of a tree leaf
   for(size_t i = 0; i < numElems; i++)
     data[i] = numElems - i;
   c.BufferData(data, numBytes);
   CheckValidate(true,  c, type, numElems,     0, numElems);
   CheckValidate(false, c, type, numElems - 1, 0, numElems);
 
-  MOZ_ASSERT(numElems > 10);
+  MOZ_RELEASE_ASSERT(numElems > 10);
   CheckValidate(true,  c, type, numElems - 10, 10, numElems - 10);
   CheckValidate(false, c, type, numElems - 11, 10, numElems - 10);
 }
 
 template<typename T>
 void
 CheckUintOverflow()
 {
   // This test is only for integer types smaller than uint32_t
   static_assert(sizeof(T) < sizeof(uint32_t), "This test is only for integer types \
                 smaller than uint32_t");
 
   const size_t numElems = 64; // should be significantly larger than tree leaf size to
                               // ensure we exercise some nontrivial tree-walking
   T data[numElems];
   size_t numBytes = numElems * sizeof(T);
-  MOZ_ASSERT(numBytes == sizeof(data));
+  MOZ_RELEASE_ASSERT(numBytes == sizeof(data));
 
   GLenum type = GLType<T>();
 
   WebGLElementArrayCache c;
 
   for(size_t i = 0; i < numElems; i++)
     data[i] = numElems - i;
   c.BufferData(data, numBytes);
--- a/dom/fetch/Response.h
+++ b/dom/fetch/Response.h
@@ -72,16 +72,22 @@ public:
   }
 
   InternalHeaders*
   GetInternalHeaders() const
   {
     return mInternalResponse->Headers();
   }
 
+  const nsCString&
+  GetSecurityInfo() const
+  {
+    return mInternalResponse->GetSecurityInfo();
+  }
+
   Headers* Headers_();
 
   void
   GetBody(nsIInputStream** aStream) { return mInternalResponse->GetBody(aStream); }
 
   static already_AddRefed<Response>
   Error(const GlobalObject& aGlobal);
 
--- a/dom/html/nsGenericHTMLFrameElement.cpp
+++ b/dom/html/nsGenericHTMLFrameElement.cpp
@@ -474,16 +474,30 @@ bool WidgetsEnabled()
     sBoolVarCacheInitialized = true;
     Preferences::AddBoolVarCache(&sMozWidgetsEnabled,
                                  "dom.enable_widgets");
   }
 
   return sMozWidgetsEnabled;
 }
 
+bool NestedEnabled()
+{
+  static bool sMozNestedEnabled = false;
+  static bool sBoolVarCacheInitialized = false;
+
+  if (!sBoolVarCacheInitialized) {
+    sBoolVarCacheInitialized = true;
+    Preferences::AddBoolVarCache(&sMozNestedEnabled,
+                                 "dom.ipc.tabs.nested.enabled");
+  }
+
+  return sMozNestedEnabled;
+}
+
 } // anonymous namespace
 
 /* [infallible] */ NS_IMETHODIMP
 nsGenericHTMLFrameElement::GetReallyIsWidget(bool *aOut)
 {
   *aOut = false;
   if (!WidgetsEnabled()) {
     return NS_OK;
@@ -576,18 +590,22 @@ nsGenericHTMLFrameElement::GetAppManifes
 {
   aOut.Truncate();
 
   // At the moment, you can't be an app without being a browser.
   if (!nsIMozBrowserFrame::GetReallyIsBrowserOrApp()) {
     return NS_OK;
   }
 
-  if (XRE_GetProcessType() != GeckoProcessType_Default) {
-    NS_WARNING("Can't embed-apps. Embed-apps is restricted to in-proc apps, see bug 1059662");
+  // Only allow content process to embed an app when nested content
+  // process is enabled.
+  if (XRE_GetProcessType() != GeckoProcessType_Default &&
+      !(GetBoolAttr(nsGkAtoms::Remote) && NestedEnabled())){
+    NS_WARNING("Can't embed-apps. Embed-apps is restricted to in-proc apps "
+               "or content processes with nested pref enabled, see bug 1097479");
     return NS_OK;
   }
 
   nsAutoString appManifestURL;
   nsAutoString widgetManifestURL;
 
   GetManifestURLByType(nsGkAtoms::mozapp, appManifestURL);
 
--- a/dom/media/eme/MediaKeySystemAccess.cpp
+++ b/dom/media/eme/MediaKeySystemAccess.cpp
@@ -8,24 +8,26 @@
 #include "mozilla/dom/MediaKeySystemAccessBinding.h"
 #include "mozilla/Preferences.h"
 #include "nsContentTypeParser.h"
 #ifdef MOZ_FMP4
 #include "MP4Decoder.h"
 #endif
 #ifdef XP_WIN
 #include "mozilla/WindowsVersion.h"
+#include "WMFDecoderModule.h"
 #endif
 #include "nsContentCID.h"
 #include "nsServiceManagerUtils.h"
 #include "mozIGeckoMediaPluginService.h"
 #include "VideoUtils.h"
 #include "mozilla/Services.h"
 #include "nsIObserverService.h"
 #include "mozilla/EMEUtils.h"
+#include "GMPUtils.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeySystemAccess,
                                       mParent)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeySystemAccess)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeySystemAccess)
@@ -154,16 +156,23 @@ MediaKeySystemAccess::GetKeySystemStatus
        aKeySystem.EqualsLiteral("com.adobe.primetime"))) {
     // Win Vista and later only.
     if (!IsVistaOrLater()) {
       return MediaKeySystemStatus::Cdm_not_supported;
     }
     if (!Preferences::GetBool("media.gmp-eme-adobe.enabled", false)) {
       return MediaKeySystemStatus::Cdm_disabled;
     }
+    if ((!WMFDecoderModule::HasH264() || !WMFDecoderModule::HasAAC()) ||
+        !EMEVoucherFileExists()) {
+      // The system doesn't have the codecs that Adobe EME relies
+      // on installed, or doesn't have a voucher for the plugin-container.
+      // Adobe EME isn't going to work, so don't advertise that it will.
+      return MediaKeySystemStatus::Cdm_not_supported;
+    }
     return EnsureMinCDMVersion(mps, aKeySystem, aMinCdmVersion, true);
   }
 #endif
 
   return MediaKeySystemStatus::Cdm_not_supported;
 }
 
 static bool
--- a/dom/media/fmp4/gonk/GonkVideoDecoderManager.cpp
+++ b/dom/media/fmp4/gonk/GonkVideoDecoderManager.cpp
@@ -389,17 +389,17 @@ GonkVideoDecoderManager::Output(int64_t 
       if (mDecoder->UpdateOutputBuffers()) {
         return Output(aStreamOffset, aOutData);
       }
       GVDM_LOG("Fails to update output buffers!");
       return NS_ERROR_FAILURE;
     }
     case -EAGAIN:
     {
-      GVDM_LOG("Need to try again!");
+//      GVDM_LOG("Need to try again!");
       return NS_ERROR_NOT_AVAILABLE;
     }
     case android::ERROR_END_OF_STREAM:
     {
       GVDM_LOG("Got the EOS frame!");
       nsRefPtr<VideoData> data;
       nsresult rv = CreateVideoData(aStreamOffset, getter_AddRefs(data));
       if (rv == NS_ERROR_NOT_AVAILABLE) {
@@ -487,29 +487,34 @@ GonkVideoDecoderManager::Flush()
 }
 
 void
 GonkVideoDecoderManager::codecReserved()
 {
   GVDM_LOG("codecReserved");
   sp<AMessage> format = new AMessage;
   sp<Surface> surface;
-
+  status_t rv = OK;
   // Fixed values
   GVDM_LOG("Configure mime type: %s, widht:%d, height:%d", mMimeType.get(), mVideoWidth, mVideoHeight);
   format->setString("mime", mMimeType.get());
   format->setInt32("width", mVideoWidth);
   format->setInt32("height", mVideoHeight);
   if (mNativeWindow != nullptr) {
     surface = new Surface(mNativeWindow->getBufferQueue());
   }
   mDecoder->configure(format, surface, nullptr, 0);
   mDecoder->Prepare();
-  status_t rv = mDecoder->Input(mCodecSpecificData->Elements(), mCodecSpecificData->Length(), 0,
-                                android::MediaCodec::BUFFER_FLAG_CODECCONFIG);
+
+  if (mMimeType.EqualsLiteral("video/mp4v-es")) {
+    rv = mDecoder->Input(mCodecSpecificData->Elements(),
+                         mCodecSpecificData->Length(), 0,
+                         android::MediaCodec::BUFFER_FLAG_CODECCONFIG);
+  }
+
   if (rv != OK) {
     GVDM_LOG("Failed to configure codec!!!!");
     mReaderCallback->Error();
   }
 }
 
 void
 GonkVideoDecoderManager::codecCanceled()
copy from dom/media/gmp-plugin/Makefile.in
copy to dom/media/gmp-plugin-openh264/Makefile.in
--- a/dom/media/gmp-plugin/Makefile.in
+++ b/dom/media/gmp-plugin-openh264/Makefile.in
@@ -1,13 +1,12 @@
 #
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-INSTALL_TARGETS += FAKE_GMP_PLUGIN
-FAKE_GMP_PLUGIN_DEST = $(DEPTH)/dist/bin/gmp-fake/1.0
-FAKE_GMP_PLUGIN_FILES = \
-  $(SHARED_LIBRARY) \
-  $(srcdir)/fake.info \
-  $(srcdir)/fake.voucher
+INSTALL_TARGETS += FAKE_GMP_OPENH264_PLUGIN
+FAKE_GMP_OPENH264_PLUGIN_DEST = $(DEPTH)/dist/bin/gmp-fakeopenh264/1.0
+FAKE_GMP_OPENH264_PLUGIN_FILES = \
+  $(srcdir)/fakeopenh264.info \
+  $(srcdir)/fakeopenh264.voucher \
+  $(NULL)
 
-include $(topsrcdir)/config/rules.mk
copy from dom/media/gmp-plugin/fake.info
copy to dom/media/gmp-plugin-openh264/fakeopenh264.info
--- a/dom/media/gmp-plugin/fake.info
+++ b/dom/media/gmp-plugin-openh264/fakeopenh264.info
@@ -1,5 +1,4 @@
-Name: fake
+Name: fakeopenh264
 Description: Fake GMP Plugin
 Version: 1.0
-APIs: encode-video[h264], decode-video[h264], eme-decrypt-v7[fake]
-Libraries: dxva2.dll
+APIs: encode-video[h264], decode-video[h264]
copy from dom/media/gmp-plugin/fake.voucher
copy to dom/media/gmp-plugin-openh264/fakeopenh264.voucher
--- a/dom/media/gmp-plugin/fake.voucher
+++ b/dom/media/gmp-plugin-openh264/fakeopenh264.voucher
@@ -1,1 +1,1 @@
-gmp-fake placeholder voucher
\ No newline at end of file
+gmp-fakeopenh264 placeholder voucher
copy from dom/media/gmp-plugin/moz.build
copy to dom/media/gmp-plugin-openh264/moz.build
--- a/dom/media/gmp-plugin/moz.build
+++ b/dom/media/gmp-plugin-openh264/moz.build
@@ -1,22 +1,22 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-NO_DIST_INSTALL = True
+# largely a copy of dom/media/gmp-fake/moz.build
+
+FINAL_TARGET = 'dist/bin/gmp-fakeopenh264/1.0'
 SOURCES += [
-        'gmp-fake.cpp',
-        'gmp-test-decryptor.cpp',
-        'gmp-test-storage.cpp',
+    '../gmp-plugin/gmp-fake.cpp',
 ]
 
-SharedLibrary("fake")
+SharedLibrary("fakeopenh264")
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     OS_LIBS += [
         'ole32',
     ]
 
 USE_STATIC_LIBS = True
 NO_VISIBILITY_FLAGS = True
--- a/dom/media/gmp-plugin/fake.info
+++ b/dom/media/gmp-plugin/fake.info
@@ -1,5 +1,5 @@
 Name: fake
 Description: Fake GMP Plugin
 Version: 1.0
-APIs: encode-video[h264], decode-video[h264], eme-decrypt-v7[fake]
+APIs: encode-video[h264:fake], decode-video[h264:fake], eme-decrypt-v7[fake]
 Libraries: dxva2.dll
--- a/dom/media/gmp-plugin/gmp-fake.cpp
+++ b/dom/media/gmp-plugin/gmp-fake.cpp
@@ -44,20 +44,22 @@
 #include <limits.h>
 
 #include "gmp-platform.h"
 #include "gmp-video-host.h"
 #include "gmp-video-encode.h"
 #include "gmp-video-decode.h"
 #include "gmp-video-frame-i420.h"
 #include "gmp-video-frame-encoded.h"
+
+#if defined(GMP_FAKE_SUPPORT_DECRYPT)
 #include "gmp-decryption.h"
-
 #include "gmp-test-decryptor.h"
 #include "gmp-test-storage.h"
+#endif
 
 #if defined(_MSC_VER)
 #define PUBLIC_FUNC __declspec(dllexport)
 #else
 #define PUBLIC_FUNC
 #endif
 
 #define BIG_FRAME 10000
@@ -402,22 +404,24 @@ extern "C" {
   PUBLIC_FUNC GMPErr
   GMPGetAPI (const char* aApiName, void* aHostAPI, void** aPluginApi) {
     if (!strcmp (aApiName, GMP_API_VIDEO_DECODER)) {
       *aPluginApi = new FakeVideoDecoder (static_cast<GMPVideoHost*> (aHostAPI));
       return GMPNoErr;
     } else if (!strcmp (aApiName, GMP_API_VIDEO_ENCODER)) {
       *aPluginApi = new FakeVideoEncoder (static_cast<GMPVideoHost*> (aHostAPI));
       return GMPNoErr;
+#if defined(GMP_FAKE_SUPPORT_DECRYPT)
     } else if (!strcmp (aApiName, GMP_API_DECRYPTOR)) {
       *aPluginApi = new FakeDecryptor(static_cast<GMPDecryptorHost*> (aHostAPI));
       return GMPNoErr;
     } else if (!strcmp (aApiName, GMP_API_ASYNC_SHUTDOWN)) {
       *aPluginApi = new TestAsyncShutdown(static_cast<GMPAsyncShutdownHost*> (aHostAPI));
       return GMPNoErr;
+#endif
     }
     return GMPGenericErr;
   }
 
   PUBLIC_FUNC void
   GMPShutdown (void) {
     g_platform_api = NULL;
   }
--- a/dom/media/gmp-plugin/moz.build
+++ b/dom/media/gmp-plugin/moz.build
@@ -6,16 +6,18 @@
 
 NO_DIST_INSTALL = True
 SOURCES += [
         'gmp-fake.cpp',
         'gmp-test-decryptor.cpp',
         'gmp-test-storage.cpp',
 ]
 
+DEFINES['GMP_FAKE_SUPPORT_DECRYPT'] = True
+
 SharedLibrary("fake")
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     OS_LIBS += [
         'ole32',
     ]
 
 USE_STATIC_LIBS = True
--- a/dom/media/gmp/GMPParent.cpp
+++ b/dom/media/gmp/GMPParent.cpp
@@ -50,16 +50,17 @@ extern PRLogModuleInfo* GetGMPLog();
 namespace gmp {
 
 GMPParent::GMPParent()
   : mState(GMPStateNotLoaded)
   , mProcess(nullptr)
   , mDeleteProcessOnlyOnUnload(false)
   , mAbnormalShutdownInProgress(false)
   , mIsBlockingDeletion(false)
+  , mCanDecrypt(false)
   , mGMPContentChildCount(0)
   , mAsyncShutdownRequired(false)
   , mAsyncShutdownInProgress(false)
 #ifdef PR_LOGGING
   , mChildPid(0)
 #endif
 {
   LOGD("GMPParent ctor");
@@ -751,41 +752,50 @@ GMPParent::ReadGMPMetaData()
         nsCCharSeparatedTokenizer tagTokens(ts, ':');
         while (tagTokens.hasMoreTokens()) {
           const nsDependentCSubstring tag(tagTokens.nextToken());
           cap->mAPITags.AppendElement(tag);
         }
       }
     }
 
+    if (cap->mAPIName.EqualsLiteral(GMP_API_DECRYPTOR) ||
+        cap->mAPIName.EqualsLiteral(GMP_API_DECRYPTOR_COMPAT)) {
+      mCanDecrypt = true;
+
 #if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
-    if (cap->mAPIName.EqualsLiteral(GMP_API_DECRYPTOR) &&
-        !mozilla::SandboxInfo::Get().CanSandboxMedia()) {
-      printf_stderr("GMPParent::ReadGMPMetaData: Plugin \"%s\" is an EME CDM"
-                    " but this system can't sandbox it; not loading.\n",
-                    mDisplayName.get());
-      delete cap;
-      return NS_ERROR_FAILURE;
+      if (!mozilla::SandboxInfo::Get().CanSandboxMedia()) {
+        printf_stderr("GMPParent::ReadGMPMetaData: Plugin \"%s\" is an EME CDM"
+                      " but this system can't sandbox it; not loading.\n",
+                      mDisplayName.get());
+        delete cap;
+        return NS_ERROR_FAILURE;
+      }
+#endif
     }
-#endif
 
     mCapabilities.AppendElement(cap);
   }
 
   if (mCapabilities.IsEmpty()) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
 bool
 GMPParent::CanBeSharedCrossNodeIds() const
 {
-  return mNodeId.IsEmpty();
+  return mNodeId.IsEmpty() &&
+    // XXX bug 1159300 hack -- maybe remove after openh264 1.4
+    // We don't want to use CDM decoders for non-encrypted playback
+    // just yet; especially not for WebRTC. Don't allow CDMs to be used
+    // without a node ID.
+    !mCanDecrypt;
 }
 
 bool
 GMPParent::CanBeUsedFrom(const nsACString& aNodeId) const
 {
   return (mNodeId.IsEmpty() && State() == GMPStateNotLoaded) ||
          mNodeId == aNodeId;
 }
--- a/dom/media/gmp/GMPParent.h
+++ b/dom/media/gmp/GMPParent.h
@@ -194,16 +194,18 @@ private:
   nsCString mVersion;
   nsCString mPluginId;
   nsTArray<nsAutoPtr<GMPCapability>> mCapabilities;
   GMPProcessParent* mProcess;
   bool mDeleteProcessOnlyOnUnload;
   bool mAbnormalShutdownInProgress;
   bool mIsBlockingDeletion;
 
+  bool mCanDecrypt;
+
   nsTArray<nsRefPtr<GMPTimerParent>> mTimers;
   nsTArray<nsRefPtr<GMPStorageParent>> mStorage;
   nsCOMPtr<nsIThread> mGMPThread;
   nsCOMPtr<nsITimer> mAsyncShutdownTimeout; // GMP Thread only.
   // NodeId the plugin is assigned to, or empty if the the plugin is not
   // assigned to a NodeId.
   nsAutoCString mNodeId;
   // This is used for GMP content in the parent, there may be more of these in
--- a/dom/media/gmp/GMPProcessParent.cpp
+++ b/dom/media/gmp/GMPProcessParent.cpp
@@ -1,17 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * vim: sw=2 ts=2 et :
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "GMPProcessParent.h"
-#include "nsDirectoryServiceDefs.h"
-#include "nsIFile.h"
+#include "GMPUtils.h"
 
 #include "base/string_util.h"
 #include "base/process_util.h"
 
 #include <string>
 
 using std::vector;
 using std::string;
@@ -40,25 +39,23 @@ GMPProcessParent::GMPProcessParent(const
 GMPProcessParent::~GMPProcessParent()
 {
   MOZ_COUNT_DTOR(GMPProcessParent);
 }
 
 bool
 GMPProcessParent::Launch(int32_t aTimeoutMs)
 {
-  nsCOMPtr<nsIFile> greDir;
-  NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(greDir));
-  if (!greDir) {
-    NS_WARNING("GMPProcessParent can't get NS_GRE_DIR");
+  nsCOMPtr<nsIFile> path;
+  if (!GetEMEVoucherPath(getter_AddRefs(path))) {
+    NS_WARNING("GMPProcessParent can't get EME voucher path!");
     return false;
   }
-  greDir->AppendNative(NS_LITERAL_CSTRING("voucher.bin"));
   nsAutoCString voucherPath;
-  greDir->GetNativePath(voucherPath);
+  path->GetNativePath(voucherPath);
 
   vector<string> args;
   args.push_back(mGMPPath);
   args.push_back(string(voucherPath.BeginReading(), voucherPath.EndReading()));
 
 #if defined(XP_WIN) && defined(MOZ_SANDBOX)
   std::wstring wGMPPath = UTF8ToWide(mGMPPath.c_str());
   mAllowedFilesRead.push_back(wGMPPath + L"\\*");
--- a/dom/media/gmp/GMPServiceParent.cpp
+++ b/dom/media/gmp/GMPServiceParent.cpp
@@ -668,31 +668,33 @@ GeckoMediaPluginServiceParent::ClonePlug
   MutexAutoLock lock(mMutex);
   mPlugins.AppendElement(gmp);
 
   return gmp.get();
 }
 
 class NotifyObserversTask final : public nsRunnable {
 public:
-  explicit NotifyObserversTask(const char* aTopic)
+  explicit NotifyObserversTask(const char* aTopic, nsString aData = EmptyString())
     : mTopic(aTopic)
+    , mData(aData)
   {}
   NS_IMETHOD Run() override {
     MOZ_ASSERT(NS_IsMainThread());
     nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
     MOZ_ASSERT(obsService);
     if (obsService) {
-      obsService->NotifyObservers(nullptr, mTopic, nullptr);
+      obsService->NotifyObservers(nullptr, mTopic, mData.get());
     }
     return NS_OK;
   }
 private:
   ~NotifyObserversTask() {}
   const char* mTopic;
+  const nsString mData;
 };
 
 void
 GeckoMediaPluginServiceParent::AddOnGMPThread(const nsAString& aDirectory)
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
   LOGD(("%s::%s: %s", __CLASS__, __FUNCTION__, NS_LossyConvertUTF16toASCII(aDirectory).get()));
 
@@ -732,46 +734,62 @@ GeckoMediaPluginServiceParent::RemoveOnG
   LOGD(("%s::%s: %s", __CLASS__, __FUNCTION__, NS_LossyConvertUTF16toASCII(aDirectory).get()));
 
   nsCOMPtr<nsIFile> directory;
   nsresult rv = NS_NewLocalFile(aDirectory, false, getter_AddRefs(directory));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
+  // Plugin destruction can modify |mPlugins|. Put them aside for now and
+  // destroy them once we're done with |mPlugins|.
+  nsTArray<nsRefPtr<GMPParent>> deadPlugins;
+
+  bool inUse = false;
   MutexAutoLock lock(mMutex);
   for (size_t i = mPlugins.Length() - 1; i < mPlugins.Length(); i--) {
     nsCOMPtr<nsIFile> pluginpath = mPlugins[i]->GetDirectory();
     bool equals;
     if (NS_FAILED(directory->Equals(pluginpath, &equals)) || !equals) {
       continue;
     }
 
     nsRefPtr<GMPParent> gmp = mPlugins[i];
     if (aDeleteFromDisk && gmp->State() != GMPStateNotLoaded) {
       // We have to wait for the child process to release its lib handle
       // before we can delete the GMP.
+      inUse = true;
       gmp->MarkForDeletion();
 
       if (!mPluginsWaitingForDeletion.Contains(aDirectory)) {
         mPluginsWaitingForDeletion.AppendElement(aDirectory);
       }
     }
 
     if (gmp->State() == GMPStateNotLoaded || !aCanDefer) {
       // GMP not in use or shutdown is being forced; can shut it down now.
-      gmp->AbortAsyncShutdown();
-      gmp->CloseActive(true);
+      deadPlugins.AppendElement(gmp);
       mPlugins.RemoveElementAt(i);
     }
   }
 
-  if (aDeleteFromDisk) {
+  {
+    MutexAutoUnlock unlock(mMutex);
+    for (auto& gmp : deadPlugins) {
+      gmp->AbortAsyncShutdown();
+      gmp->CloseActive(true);
+    }
+  }
+
+  if (aDeleteFromDisk && !inUse) {
     if (NS_SUCCEEDED(directory->Remove(true))) {
       mPluginsWaitingForDeletion.RemoveElement(aDirectory);
+      NS_DispatchToMainThread(new NotifyObserversTask("gmp-directory-deleted",
+                                                      nsString(aDirectory)),
+                              NS_DISPATCH_NORMAL);
     }
   }
 }
 
 // May remove when Bug 1043671 is fixed
 static void Dummy(nsRefPtr<GMPParent>& aOnDeathsDoor)
 {
   // exists solely to do nothing and let the Runnable kill the GMPParent
new file mode 100644
--- /dev/null
+++ b/dom/media/gmp/GMPUtils.cpp
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIFile.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+
+bool
+GetEMEVoucherPath(nsIFile** aPath)
+{
+  nsCOMPtr<nsIFile> path;
+  NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(path));
+  if (!path) {
+    NS_WARNING("GetEMEVoucherPath can't get NS_GRE_DIR!");
+    return false;
+  }
+  path->AppendNative(NS_LITERAL_CSTRING("voucher.bin"));
+  path.forget(aPath);
+  return true;
+}
+
+bool
+EMEVoucherFileExists()
+{
+  nsCOMPtr<nsIFile> path;
+  bool exists;
+  return GetEMEVoucherPath(getter_AddRefs(path)) &&
+         NS_SUCCEEDED(path->Exists(&exists)) &&
+         exists;
+}
+
+} // namespace mozilla
--- a/dom/media/gmp/GMPUtils.h
+++ b/dom/media/gmp/GMPUtils.h
@@ -16,11 +16,15 @@ struct DestroyPolicy
   void operator()(T* aGMPObject) const {
     aGMPObject->Destroy();
   }
 };
 
 template<typename T>
 using GMPUniquePtr = mozilla::UniquePtr<T, DestroyPolicy<T>>;
 
+bool GetEMEVoucherPath(nsIFile** aPath);
+
+bool EMEVoucherFileExists();
+
 } // namespace mozilla
 
 #endif
--- a/dom/media/gmp/moz.build
+++ b/dom/media/gmp/moz.build
@@ -93,16 +93,17 @@ UNIFIED_SOURCES += [
     'GMPService.cpp',
     'GMPServiceChild.cpp',
     'GMPServiceParent.cpp',
     'GMPSharedMemManager.cpp',
     'GMPStorageChild.cpp',
     'GMPStorageParent.cpp',
     'GMPTimerChild.cpp',
     'GMPTimerParent.cpp',
+    'GMPUtils.cpp',
     'GMPVideoDecoderChild.cpp',
     'GMPVideoDecoderParent.cpp',
     'GMPVideoEncodedFrameImpl.cpp',
     'GMPVideoEncoderChild.cpp',
     'GMPVideoEncoderParent.cpp',
     'GMPVideoHost.cpp',
     'GMPVideoi420FrameImpl.cpp',
     'GMPVideoPlaneImpl.cpp',
new file mode 100644
--- /dev/null
+++ b/dom/media/gtest/GMPTestMonitor.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsThreadUtils.h"
+
+#ifndef __GMPTestMonitor_h__
+#define __GMPTestMonitor_h__
+
+class GMPTestMonitor
+{
+public:
+  GMPTestMonitor()
+    : mFinished(false)
+  {
+  }
+
+  void AwaitFinished()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    while (!mFinished) {
+      NS_ProcessNextEvent(nullptr, true);
+    }
+    mFinished = false;
+  }
+
+private:
+  void MarkFinished()
+  {
+    mFinished = true;
+  }
+
+public:
+  void SetFinished()
+  {
+    NS_DispatchToMainThread(NS_NewNonOwningRunnableMethod(this,
+                                                          &GMPTestMonitor::MarkFinished));
+  }
+
+private:
+  bool mFinished;
+};
+
+#endif // __GMPTestMonitor_h__
--- a/dom/media/gtest/TestGMPCrossOrigin.cpp
+++ b/dom/media/gtest/TestGMPCrossOrigin.cpp
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gtest/gtest.h"
 #include "nsIObserverService.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
+#include "GMPTestMonitor.h"
 #include "GMPVideoDecoderProxy.h"
 #include "GMPVideoEncoderProxy.h"
 #include "GMPDecryptorProxy.h"
 #include "GMPServiceParent.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsIFile.h"
 #include "nsISimpleEnumerator.h"
 #include "mozilla/Atomics.h"
@@ -23,50 +24,16 @@
 #include "mozilla/WindowsVersion.h"
 #endif
 
 using namespace std;
 
 using namespace mozilla;
 using namespace mozilla::gmp;
 
-class GMPTestMonitor
-{
-public:
-  GMPTestMonitor()
-    : mFinished(false)
-  {
-  }
-
-  void AwaitFinished()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    while (!mFinished) {
-      NS_ProcessNextEvent(nullptr, true);
-    }
-    mFinished = false;
-  }
-
-private:
-  void MarkFinished()
-  {
-    mFinished = true;
-  }
-
-public:
-  void SetFinished()
-  {
-    NS_DispatchToMainThread(NS_NewNonOwningRunnableMethod(this,
-                                                          &GMPTestMonitor::MarkFinished));
-  }
-
-private:
-  bool mFinished;
-};
-
 struct GMPTestRunner
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPTestRunner)
 
   void DoTest(void (GMPTestRunner::*aTestMethod)(GMPTestMonitor&));
   void RunTestGMPTestCodec1(GMPTestMonitor& aMonitor);
   void RunTestGMPTestCodec2(GMPTestMonitor& aMonitor);
   void RunTestGMPTestCodec3(GMPTestMonitor& aMonitor);
new file mode 100644
--- /dev/null
+++ b/dom/media/gtest/TestGMPRemoveAndDelete.cpp
@@ -0,0 +1,449 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPService.h"
+#include "GMPTestMonitor.h"
+#include "gmp-api/gmp-video-host.h"
+#include "gtest/gtest.h"
+#include "mozilla/Services.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIObserverService.h"
+
+#define GMP_DIR_NAME NS_LITERAL_STRING("gmp-fake")
+#define GMP_OLD_VERSION NS_LITERAL_STRING("1.0")
+#define GMP_NEW_VERSION NS_LITERAL_STRING("1.1")
+
+#define GMP_DELETED_TOPIC "gmp-directory-deleted"
+
+#define EXPECT_OK(X) EXPECT_TRUE(NS_SUCCEEDED(X))
+
+class GMPRemoveTest : public nsIObserver
+                    , public GMPVideoDecoderCallbackProxy
+{
+public:
+  GMPRemoveTest();
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  // Called when a GMP plugin directory has been successfully deleted.
+  // |aData| will contain the directory path.
+  NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+                     const char16_t* aData) override;
+
+  // Create a new GMP plugin directory that we can trash and add it to the GMP
+  // service. Remove the original plugin directory. Original plugin directory
+  // gets re-added at destruction.
+  void Setup();
+
+  bool CreateVideoDecoder(nsCString aNodeId = EmptyCString());
+  void CloseVideoDecoder();
+
+  void DeletePluginDirectory(bool aCanDefer);
+
+  // Decode a dummy frame.
+  GMPErr Decode();
+
+  // Wait until TestMonitor has been signaled.
+  void Wait();
+
+  // Did we get a Terminated() callback from the plugin?
+  bool IsTerminated();
+
+  // From GMPVideoDecoderCallbackProxy
+  // Set mDecodeResult; unblock TestMonitor.
+  virtual void Decoded(GMPVideoi420Frame* aDecodedFrame) override;
+  virtual void Error(GMPErr aError) override;
+
+  // From GMPVideoDecoderCallbackProxy
+  // We expect this to be called when a plugin has been forcibly closed.
+  virtual void Terminated() override;
+
+  // Ignored GMPVideoDecoderCallbackProxy members
+  virtual void ReceivedDecodedReferenceFrame(const uint64_t aPictureId) override {}
+  virtual void ReceivedDecodedFrame(const uint64_t aPictureId) override {}
+  virtual void InputDataExhausted() override {}
+  virtual void DrainComplete() override {}
+  virtual void ResetComplete() override {}
+
+private:
+  virtual ~GMPRemoveTest();
+
+  void gmp_Decode();
+  void gmp_GetVideoDecoder(nsCString aNodeId,
+                           GMPVideoDecoderProxy** aOutDecoder,
+                           GMPVideoHost** aOutHost);
+  void GeneratePlugin();
+
+  GMPTestMonitor mTestMonitor;
+  nsCOMPtr<nsIThread> mGMPThread;
+
+  bool mIsTerminated;
+
+  // Path to the cloned GMP we have created.
+  nsString mTmpPath;
+  nsCOMPtr<nsIFile> mTmpDir;
+
+  // Path to the original GMP. Store so that we can re-add it after we're done
+  // testing.
+  nsString mOriginalPath;
+
+  GMPVideoDecoderProxy* mDecoder;
+  GMPVideoHost* mHost;
+  GMPErr mDecodeResult;
+};
+
+/*
+ * Simple test that the plugin is deleted when forcibly removed and deleted.
+ */
+TEST(GeckoMediaPlugins, RemoveAndDeleteForcedSimple)
+{
+  nsRefPtr<GMPRemoveTest> test(new GMPRemoveTest());
+
+  test->Setup();
+  test->DeletePluginDirectory(false /* force immediate */);
+  test->Wait();
+}
+
+/*
+ * Simple test that the plugin is deleted when deferred deletion is allowed.
+ */
+TEST(GeckoMediaPlugins, RemoveAndDeleteDeferredSimple)
+{
+  nsRefPtr<GMPRemoveTest> test(new GMPRemoveTest());
+
+  test->Setup();
+  test->DeletePluginDirectory(true /* can defer */);
+  test->Wait();
+}
+
+/*
+ * Test that the plugin is unavailable immediately after a forced
+ * RemoveAndDelete, and that the plugin is deleted afterwards.
+ */
+TEST(GeckoMediaPlugins, RemoveAndDeleteForcedInUse)
+{
+  nsRefPtr<GMPRemoveTest> test(new GMPRemoveTest());
+
+  test->Setup();
+  EXPECT_TRUE(test->CreateVideoDecoder(NS_LITERAL_CSTRING("thisOrigin")));
+
+  // Test that we can decode a frame.
+  GMPErr err = test->Decode();
+  EXPECT_EQ(err, GMPNoErr);
+
+  test->DeletePluginDirectory(false /* force immediate */);
+  test->Wait();
+
+  // Test that the VideoDecoder is no longer available.
+  EXPECT_FALSE(test->CreateVideoDecoder(NS_LITERAL_CSTRING("thisOrigin")));
+
+  // Test that we were notified of the plugin's destruction.
+  EXPECT_TRUE(test->IsTerminated());
+}
+
+/*
+ * Test that the plugin is still usable after a deferred RemoveAndDelete, and
+ * that the plugin is deleted afterwards.
+ */
+TEST(GeckoMediaPlugins, RemoveAndDeleteDeferredInUse)
+{
+  nsRefPtr<GMPRemoveTest> test(new GMPRemoveTest());
+
+  test->Setup();
+  EXPECT_TRUE(test->CreateVideoDecoder(NS_LITERAL_CSTRING("thisOrigin")));
+
+  // Make sure decoding works before we do anything.
+  GMPErr err = test->Decode();
+  EXPECT_EQ(err, GMPNoErr);
+
+  test->DeletePluginDirectory(true /* can defer */);
+
+  // Test that decoding still works.
+  err = test->Decode();
+  EXPECT_EQ(err, GMPNoErr);
+
+  // Test that this origin is still able to fetch the video decoder.
+  EXPECT_TRUE(test->CreateVideoDecoder(NS_LITERAL_CSTRING("thisOrigin")));
+
+  test->CloseVideoDecoder();
+  test->Wait();
+}
+
+static StaticRefPtr<GeckoMediaPluginService> gService;
+static StaticRefPtr<GeckoMediaPluginServiceParent> gServiceParent;
+
+static GeckoMediaPluginService*
+GetService()
+{
+  if (!gService) {
+    nsRefPtr<GeckoMediaPluginService> service =
+      GeckoMediaPluginService::GetGeckoMediaPluginService();
+    gService = service;
+  }
+
+  return gService.get();
+}
+
+static GeckoMediaPluginServiceParent*
+GetServiceParent()
+{
+  if (!gServiceParent) {
+    nsRefPtr<GeckoMediaPluginServiceParent> parent =
+      GeckoMediaPluginServiceParent::GetSingleton();
+    gServiceParent = parent;
+  }
+
+  return gServiceParent.get();
+}
+
+NS_IMPL_ISUPPORTS(GMPRemoveTest, nsIObserver)
+
+GMPRemoveTest::GMPRemoveTest()
+  : mIsTerminated(false)
+  , mDecoder(nullptr)
+  , mHost(nullptr)
+{
+}
+
+GMPRemoveTest::~GMPRemoveTest()
+{
+  bool exists;
+  EXPECT_TRUE(NS_SUCCEEDED(mTmpDir->Exists(&exists)) && !exists);
+
+  EXPECT_OK(GetServiceParent()->AddPluginDirectory(mOriginalPath));
+}
+
+void
+GMPRemoveTest::Setup()
+{
+  GeneratePlugin();
+  EXPECT_OK(GetServiceParent()->RemovePluginDirectory(mOriginalPath));
+
+  GetServiceParent()->AddPluginDirectory(mTmpPath);
+
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  obs->AddObserver(this, GMP_DELETED_TOPIC, false /* strong ref */);
+
+  GetService()->GetThread(getter_AddRefs(mGMPThread));
+}
+
+bool
+GMPRemoveTest::CreateVideoDecoder(nsCString aNodeId)
+{
+  GMPVideoHost* host;
+  GMPVideoDecoderProxy* decoder = nullptr;
+
+  mGMPThread->Dispatch(
+    NS_NewNonOwningRunnableMethodWithArgs<nsCString, GMPVideoDecoderProxy**, GMPVideoHost**>(
+      this, &GMPRemoveTest::gmp_GetVideoDecoder, aNodeId, &decoder, &host),
+    NS_DISPATCH_NORMAL);
+
+  mTestMonitor.AwaitFinished();
+
+  if (!decoder) {
+    return false;
+  }
+
+  GMPVideoCodec codec;
+  memset(&codec, 0, sizeof(codec));
+  codec.mGMPApiVersion = 33;
+
+  nsTArray<uint8_t> empty;
+  mGMPThread->Dispatch(
+    NS_NewNonOwningRunnableMethodWithArgs<const GMPVideoCodec&, const nsTArray<uint8_t>&, GMPVideoDecoderCallbackProxy*, int32_t>(
+      decoder, &GMPVideoDecoderProxy::InitDecode,
+      codec, empty, this, 1 /* core count */),
+    NS_DISPATCH_SYNC);
+
+  if (mDecoder) {
+    CloseVideoDecoder();
+  }
+
+  mDecoder = decoder;
+  mHost = host;
+
+  return true;
+}
+
+void
+GMPRemoveTest::gmp_GetVideoDecoder(nsCString aNodeId,
+                                   GMPVideoDecoderProxy** aOutDecoder,
+                                   GMPVideoHost** aOutHost)
+{
+  nsTArray<nsCString> tags;
+  tags.AppendElement(NS_LITERAL_CSTRING("h264"));
+  tags.AppendElement(NS_LITERAL_CSTRING("fake"));
+
+  class Callback : public GetGMPVideoDecoderCallback
+  {
+  public:
+    Callback(GMPTestMonitor* aMonitor, GMPVideoDecoderProxy** aDecoder, GMPVideoHost** aHost)
+      : mMonitor(aMonitor), mDecoder(aDecoder), mHost(aHost) { }
+    virtual void Done(GMPVideoDecoderProxy* aDecoder, GMPVideoHost* aHost) override {
+      *mDecoder = aDecoder;
+      *mHost = aHost;
+      mMonitor->SetFinished();
+    }
+  private:
+    GMPTestMonitor* mMonitor;
+    GMPVideoDecoderProxy** mDecoder;
+    GMPVideoHost** mHost;
+  };
+
+  UniquePtr<GetGMPVideoDecoderCallback>
+    cb(new Callback(&mTestMonitor, aOutDecoder, aOutHost));
+
+  if (NS_FAILED(GetService()->GetGMPVideoDecoder(&tags, aNodeId, Move(cb)))) {
+    mTestMonitor.SetFinished();
+  }
+}
+
+void
+GMPRemoveTest::CloseVideoDecoder()
+{
+  mGMPThread->Dispatch(
+    NS_NewNonOwningRunnableMethod(mDecoder, &GMPVideoDecoderProxy::Close),
+    NS_DISPATCH_SYNC);
+
+  mDecoder = nullptr;
+  mHost = nullptr;
+}
+
+void
+GMPRemoveTest::DeletePluginDirectory(bool aCanDefer)
+{
+  GetServiceParent()->RemoveAndDeletePluginDirectory(mTmpPath, aCanDefer);
+}
+
+GMPErr
+GMPRemoveTest::Decode()
+{
+  mGMPThread->Dispatch(
+    NS_NewNonOwningRunnableMethod(this, &GMPRemoveTest::gmp_Decode),
+    NS_DISPATCH_NORMAL);
+
+  mTestMonitor.AwaitFinished();
+  return mDecodeResult;
+}
+
+void
+GMPRemoveTest::gmp_Decode()
+{
+  // from gmp-fake.cpp
+  struct EncodedFrame {
+    uint32_t length_;
+    uint8_t h264_compat_;
+    uint32_t magic_;
+    uint32_t width_;
+    uint32_t height_;
+    uint8_t y_;
+    uint8_t u_;
+    uint8_t v_;
+    uint32_t timestamp_;
+  };
+
+  GMPVideoFrame* absFrame;
+  GMPErr err = mHost->CreateFrame(kGMPEncodedVideoFrame, &absFrame);
+  EXPECT_EQ(err, GMPNoErr);
+
+  GMPUniquePtr<GMPVideoEncodedFrame>
+    frame(static_cast<GMPVideoEncodedFrame*>(absFrame));
+  err = frame->CreateEmptyFrame(sizeof(EncodedFrame) /* size */);
+  EXPECT_EQ(err, GMPNoErr);
+
+  EncodedFrame* frameData = reinterpret_cast<EncodedFrame*>(frame->Buffer());
+  frameData->magic_ = 0x4652414d;
+  frameData->width_ = frameData->height_ = 16;
+
+  nsTArray<uint8_t> empty;
+  nsresult rv = mDecoder->Decode(Move(frame), false /* aMissingFrames */, empty);
+  EXPECT_OK(rv);
+}
+
+void
+GMPRemoveTest::Wait()
+{
+  mTestMonitor.AwaitFinished();
+}
+
+bool
+GMPRemoveTest::IsTerminated()
+{
+  return mIsTerminated;
+}
+
+// nsIObserver
+NS_IMETHODIMP
+GMPRemoveTest::Observe(nsISupports* aSubject, const char* aTopic,
+                       const char16_t* aData)
+{
+  EXPECT_TRUE(!strcmp(GMP_DELETED_TOPIC, aTopic));
+
+  nsString data(aData);
+  if (mTmpPath.Equals(data)) {
+    mTestMonitor.SetFinished();
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    obs->RemoveObserver(this, GMP_DELETED_TOPIC);
+  }
+
+  return NS_OK;
+}
+
+// GMPVideoDecoderCallbackProxy
+void
+GMPRemoveTest::Decoded(GMPVideoi420Frame* aDecodedFrame)
+{
+  aDecodedFrame->Destroy();
+  mDecodeResult = GMPNoErr;
+  mTestMonitor.SetFinished();
+}
+
+// GMPVideoDecoderCallbackProxy
+void
+GMPRemoveTest::Error(GMPErr aError)
+{
+  mDecodeResult = aError;
+  mTestMonitor.SetFinished();
+}
+
+// GMPVideoDecoderCallbackProxy
+void
+GMPRemoveTest::Terminated()
+{
+  mIsTerminated = true;
+}
+
+void
+GMPRemoveTest::GeneratePlugin()
+{
+  nsresult rv;
+  nsCOMPtr<nsIFile> gmpDir;
+  nsCOMPtr<nsIFile> origDir;
+  nsCOMPtr<nsIFile> tmpDir;
+
+  rv = NS_GetSpecialDirectory(NS_GRE_DIR,
+                              getter_AddRefs(gmpDir));
+  EXPECT_OK(rv);
+  rv = gmpDir->Append(GMP_DIR_NAME);
+  EXPECT_OK(rv);
+
+  rv = gmpDir->Clone(getter_AddRefs(origDir));
+  EXPECT_OK(rv);
+  rv = origDir->Append(GMP_OLD_VERSION);
+  EXPECT_OK(rv);
+
+  rv = origDir->CopyTo(gmpDir, GMP_NEW_VERSION);
+  EXPECT_OK(rv);
+
+  rv = gmpDir->Clone(getter_AddRefs(tmpDir));
+  EXPECT_OK(rv);
+  rv = tmpDir->Append(GMP_NEW_VERSION);
+  EXPECT_OK(rv);
+
+  EXPECT_OK(origDir->GetPath(mOriginalPath));
+  EXPECT_OK(tmpDir->GetPath(mTmpPath));
+  mTmpDir = tmpDir;
+}
--- a/dom/media/gtest/moz.build
+++ b/dom/media/gtest/moz.build
@@ -3,16 +3,17 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 UNIFIED_SOURCES += [
     'MockMediaResource.cpp',
     'TestAudioCompactor.cpp',
     'TestGMPCrossOrigin.cpp',
+    'TestGMPRemoveAndDelete.cpp',
     'TestMP4Demuxer.cpp',
     'TestMP4Reader.cpp',
     'TestTrackEncoder.cpp',
     'TestVideoSegment.cpp',
     'TestWebMBuffered.cpp',
 ]
 
 if CONFIG['MOZ_EME']:
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -18,16 +18,17 @@ with Files('RTC*'):
 component_av = ('Core', 'WebRTC: Audio/Video')
 with Files('GetUserMedia*'):
     BUG_COMPONENT = component_av
 
 DIRS += [
     'encoder',
     'gmp',
     'gmp-plugin',
+    'gmp-plugin-openh264',
     'imagecapture',
     'mediasource',
     'ogg',
     'systemservices',
     'webaudio',
     'webrtc',
     'webspeech',
     'webvtt',
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -1541,17 +1541,17 @@ PeerConnectionWrapper.prototype = {
    *        A promise that resolves when we're receiving the tone from |from|.
    */
   checkReceivingToneFrom : function(from) {
     var inputElem = from.localMediaElements[0];
 
     // As input we use the stream of |from|'s first available audio sender.
     var inputSenderTracks = from._pc.getSenders().map(sn => sn.track);
     var inputAudioStream = from._pc.getLocalStreams()
-      .find(s => s.getAudioTracks().some(t => inputSenderTracks.includes(t)));
+      .find(s => s.getAudioTracks().some(t => inputSenderTracks.some(t2 => t == t2)));
     var inputAnalyser = new AudioStreamAnalyser(inputAudioStream);
 
     // It would have been nice to have a working getReceivers() here, but until
     // we do, let's use what remote streams we have.
     var outputAudioStream = this._pc.getRemoteStreams()
       .find(s => s.getAudioTracks().length > 0);
     var outputAnalyser = new AudioStreamAnalyser(outputAudioStream);
 
--- a/dom/media/webaudio/AnalyserNode.cpp
+++ b/dom/media/webaudio/AnalyserNode.cpp
@@ -11,19 +11,19 @@
 #include "mozilla/Mutex.h"
 #include "mozilla/PodOperations.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_ISUPPORTS_INHERITED0(AnalyserNode, AudioNode)
 
-class AnalyserNodeEngine : public AudioNodeEngine
+class AnalyserNodeEngine final : public AudioNodeEngine
 {
-  class TransferBuffer : public nsRunnable
+  class TransferBuffer final : public nsRunnable
   {
   public:
     TransferBuffer(AudioNodeStream* aStream,
                    const AudioChunk& aChunk)
       : mStream(aStream)
       , mChunk(aChunk)
     {
     }
--- a/dom/media/webaudio/AnalyserNode.h
+++ b/dom/media/webaudio/AnalyserNode.h
@@ -10,17 +10,17 @@
 #include "AudioNode.h"
 #include "FFTBlock.h"
 
 namespace mozilla {
 namespace dom {
 
 class AudioContext;
 
-class AnalyserNode : public AudioNode
+class AnalyserNode final : public AudioNode
 {
 public:
   explicit AnalyserNode(AudioContext* aContext);
 
   NS_DECL_ISUPPORTS_INHERITED
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
--- a/dom/media/webaudio/AudioBufferSourceNode.cpp
+++ b/dom/media/webaudio/AudioBufferSourceNode.cpp
@@ -48,21 +48,21 @@ NS_IMPL_ADDREF_INHERITED(AudioBufferSour
 NS_IMPL_RELEASE_INHERITED(AudioBufferSourceNode, AudioNode)
 
 /**
  * Media-thread playback engine for AudioBufferSourceNode.
  * Nothing is played until a non-null buffer has been set (via
  * AudioNodeStream::SetBuffer) and a non-zero mBufferEnd has been set (via
  * AudioNodeStream::SetInt32Parameter).
  */
-class AudioBufferSourceNodeEngine : public AudioNodeEngine
+class AudioBufferSourceNodeEngine final : public AudioNodeEngine
 {
 public:
-  explicit AudioBufferSourceNodeEngine(AudioNode* aNode,
-                                       AudioDestinationNode* aDestination) :
+  AudioBufferSourceNodeEngine(AudioNode* aNode,
+                              AudioDestinationNode* aDestination) :
     AudioNodeEngine(aNode),
     mStart(0.0), mBeginProcessing(0),
     mStop(STREAM_TIME_MAX),
     mResampler(nullptr), mRemainingResamplerTail(0),
     mBufferEnd(0),
     mLoopStart(0), mLoopEnd(0),
     mBufferSampleRate(0), mBufferPosition(0), mChannels(0),
     mDopplerShift(1.0f),
@@ -708,22 +708,22 @@ AudioBufferSourceNode::Stop(double aWhen
 
   ns->SetStreamTimeParameter(STOP, Context(), std::max(0.0, aWhen));
 }
 
 void
 AudioBufferSourceNode::NotifyMainThreadStateChanged()
 {
   if (mStream->IsFinished()) {
-    class EndedEventDispatcher : public nsRunnable
+    class EndedEventDispatcher final : public nsRunnable
     {
     public:
       explicit EndedEventDispatcher(AudioBufferSourceNode* aNode)
         : mNode(aNode) {}
-      NS_IMETHODIMP Run()
+      NS_IMETHODIMP Run() override
       {
         // If it's not safe to run scripts right now, schedule this to run later
         if (!nsContentUtils::IsSafeToRunScript()) {
           nsContentUtils::AddScriptRunner(this);
           return NS_OK;
         }
 
         mNode->DispatchTrustedEvent(NS_LITERAL_STRING("ended"));
--- a/dom/media/webaudio/AudioBufferSourceNode.h
+++ b/dom/media/webaudio/AudioBufferSourceNode.h
@@ -10,18 +10,18 @@
 #include "AudioNode.h"
 #include "AudioBuffer.h"
 
 namespace mozilla {
 namespace dom {
 
 class AudioParam;
 
-class AudioBufferSourceNode : public AudioNode,
-                              public MainThreadMediaStreamListener
+class AudioBufferSourceNode final : public AudioNode,
+                                    public MainThreadMediaStreamListener
 {
 public:
   explicit AudioBufferSourceNode(AudioContext* aContext);
 
   virtual void DestroyMediaStream() override
   {
     if (mStream) {
       mStream->RemoveMainThreadListener(this);
--- a/dom/media/webaudio/AudioDestinationNode.cpp
+++ b/dom/media/webaudio/AudioDestinationNode.cpp
@@ -25,17 +25,17 @@
 #include "nsWidgetsCID.h"
 #include "mozilla/dom/Promise.h"
 
 namespace mozilla {
 namespace dom {
 
 static uint8_t gWebAudioOutputKey;
 
-class OfflineDestinationNodeEngine : public AudioNodeEngine
+class OfflineDestinationNodeEngine final : public AudioNodeEngine
 {
 public:
   typedef AutoFallibleTArray<nsAutoArrayPtr<float>, 2> InputChannels;
 
   OfflineDestinationNodeEngine(AudioDestinationNode* aNode,
                                uint32_t aNumberOfChannels,
                                uint32_t aLength,
                                float aSampleRate)
@@ -130,17 +130,17 @@ public:
   class OnCompleteTask final : public nsRunnable
   {
   public:
     OnCompleteTask(AudioContext* aAudioContext, AudioBuffer* aRenderedBuffer)
       : mAudioContext(aAudioContext)
       , mRenderedBuffer(aRenderedBuffer)
     {}
 
-    NS_IMETHOD Run()
+    NS_IMETHOD Run() override
     {
       nsRefPtr<OfflineAudioCompletionEvent> event =
           new OfflineAudioCompletionEvent(mAudioContext, nullptr, nullptr);
       event->InitEvent(mRenderedBuffer);
       mAudioContext->DispatchTrustedEvent(event);
 
       return NS_OK;
     }
@@ -205,27 +205,27 @@ private:
   uint32_t mWriteIndex;
   uint32_t mNumberOfChannels;
   // How many frames the OfflineAudioContext intends to produce.
   uint32_t mLength;
   float mSampleRate;
   bool mBufferAllocated;
 };
 
-class InputMutedRunnable : public nsRunnable
+class InputMutedRunnable final : public nsRunnable
 {
 public:
   InputMutedRunnable(AudioNodeStream* aStream,
                      bool aInputMuted)
     : mStream(aStream)
     , mInputMuted(aInputMuted)
   {
   }
 
-  NS_IMETHOD Run()
+  NS_IMETHOD Run() override
   {
     MOZ_ASSERT(NS_IsMainThread());
     nsRefPtr<AudioNode> node = mStream->Engine()->NodeMainThread();
 
     if (node) {
       nsRefPtr<AudioDestinationNode> destinationNode =
         static_cast<AudioDestinationNode*>(node.get());
       destinationNode->InputMuted(mInputMuted);
@@ -233,17 +233,17 @@ public:
     return NS_OK;
   }
 
 private:
   nsRefPtr<AudioNodeStream> mStream;
   bool mInputMuted;
 };
 
-class DestinationNodeEngine : public AudioNodeEngine
+class DestinationNodeEngine final : public AudioNodeEngine
 {
 public:
   explicit DestinationNodeEngine(AudioDestinationNode* aNode)
     : AudioNodeEngine(aNode)
     , mVolume(1.0f)
     , mLastInputMuted(false)
   {
     MOZ_ASSERT(aNode);
--- a/dom/media/webaudio/AudioEventTimeline.h
+++ b/dom/media/webaudio/AudioEventTimeline.h
@@ -16,18 +16,20 @@
 #include "math.h"
 #include "WebAudioUtils.h"
 
 namespace mozilla {
 
 namespace dom {
 
 // This is an internal helper class and should not be used outside of this header.
-struct AudioTimelineEvent {
-  enum Type : uint32_t {
+struct AudioTimelineEvent final
+{
+  enum Type : uint32_t
+  {
     SetValue,
     LinearRamp,
     ExponentialRamp,
     SetTarget,
     SetValueCurve
   };
 
   AudioTimelineEvent(Type aType, double aTime, float aValue, double aTimeConstant = 0.0,
--- a/dom/media/webaudio/AudioNode.cpp
+++ b/dom/media/webaudio/AudioNode.cpp
@@ -320,17 +320,18 @@ AudioNode::Disconnect(uint32_t aOutput, 
     return;
   }
 
   // An upstream node may be starting to play on the graph thread, and the
   // engine for a downstream node may be sending a PlayingRefChangeHandler
   // ADDREF message to this (main) thread.  Wait for a round trip before
   // releasing nodes, to give engines receiving sound now time to keep their
   // nodes alive.
-  class RunnableRelease : public nsRunnable {
+  class RunnableRelease final : public nsRunnable
+  {
   public:
     explicit RunnableRelease(already_AddRefed<AudioNode> aNode)
       : mNode(aNode) {}
 
     NS_IMETHODIMP Run() override
     {
       mNode = nullptr;
       return NS_OK;
--- a/dom/media/webaudio/AudioNode.h
+++ b/dom/media/webaudio/AudioNode.h
@@ -69,17 +69,18 @@ public:
 
   // This should be idempotent (safe to call multiple times).
   virtual void DestroyMediaStream();
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AudioNode,
                                            DOMEventTargetHelper)
 
-  virtual AudioBufferSourceNode* AsAudioBufferSourceNode() {
+  virtual AudioBufferSourceNode* AsAudioBufferSourceNode()
+  {
     return nullptr;
   }
 
   AudioContext* GetParentObject() const
   {
     return mContext;
   }
 
@@ -132,17 +133,18 @@ public:
     return mChannelInterpretation;
   }
   void SetChannelInterpretationValue(ChannelInterpretation aMode)
   {
     mChannelInterpretation = aMode;
     SendChannelMixingParametersToStream();
   }
 
-  struct InputNode {
+  struct InputNode final
+  {
     ~InputNode()
     {
       if (mStreamPort) {
         mStreamPort->Destroy();
       }
     }
 
     size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
--- a/dom/media/webaudio/AudioNodeEngine.h
+++ b/dom/media/webaudio/AudioNodeEngine.h
@@ -22,27 +22,29 @@ class DelayNodeEngine;
 class AudioNodeStream;
 
 /**
  * This class holds onto a set of immutable channel buffers. The storage
  * for the buffers must be malloced, but the buffer pointers and the malloc
  * pointers can be different (e.g. if the buffers are contained inside
  * some malloced object).
  */
-class ThreadSharedFloatArrayBufferList : public ThreadSharedObject {
+class ThreadSharedFloatArrayBufferList final : public ThreadSharedObject
+{
 public:
   /**
    * Construct with null data.
    */
   explicit ThreadSharedFloatArrayBufferList(uint32_t aCount)
   {
     mContents.SetLength(aCount);
   }
 
-  struct Storage {
+  struct Storage final
+  {
     Storage() :
       mDataToFree(nullptr),
       mFree(nullptr),
       mSampleData(nullptr)
     {}
     ~Storage() {
       if (mFree) {
         mFree(mDataToFree);
@@ -229,17 +231,18 @@ AudioBlockPanStereoToStereo(const float 
  */
 float
 AudioBufferSumOfSquares(const float* aInput, uint32_t aLength);
 
 /**
  * All methods of this class and its subclasses are called on the
  * MediaStreamGraph thread.
  */
-class AudioNodeEngine {
+class AudioNodeEngine
+{
 public:
   // This should be compatible with AudioNodeStream::OutputChunks.
   typedef nsAutoTArray<AudioChunk, 1> OutputChunks;
 
   explicit AudioNodeEngine(dom::AudioNode* aNode)
     : mNode(aNode)
     , mNodeMutex("AudioNodeEngine::mNodeMutex")
     , mInputCount(aNode ? aNode->NumberOfInputs() : 1)
--- a/dom/media/webaudio/AudioNodeExternalInputStream.h
+++ b/dom/media/webaudio/AudioNodeExternalInputStream.h
@@ -13,24 +13,27 @@
 namespace mozilla {
 
 /**
  * This is a MediaStream implementation that acts for a Web Audio node but
  * unlike other AudioNodeStreams, supports any kind of MediaStream as an
  * input --- handling any number of audio tracks and handling blocking of
  * the input MediaStream.
  */
-class AudioNodeExternalInputStream : public AudioNodeStream {
+class AudioNodeExternalInputStream final : public AudioNodeStream
+{
 public:
-  AudioNodeExternalInputStream(AudioNodeEngine* aEngine, TrackRate aSampleRate, uint32_t aContextId);
+  AudioNodeExternalInputStream(AudioNodeEngine* aEngine, TrackRate aSampleRate,
+                               uint32_t aContextId);
 protected:
   ~AudioNodeExternalInputStream();
 
 public:
-  virtual void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) override;
+  virtual void ProcessInput(GraphTime aFrom, GraphTime aTo,
+                            uint32_t aFlags) override;
 
 private:
   /**
    * Determines if this is enabled or not.  Disabled nodes produce silence.
    * This node becomes disabled if the document principal does not subsume the
    * DOMMediaStream principal.
    */
   bool IsEnabled();
--- a/dom/media/webaudio/AudioNodeStream.cpp
+++ b/dom/media/webaudio/AudioNodeStream.cpp
@@ -90,23 +90,25 @@ AudioNodeStream::SizeOfAudioNodesIncludi
     mEngine->SizeOfIncludingThis(aMallocSizeOf, aUsage);
   }
 }
 
 void
 AudioNodeStream::SetStreamTimeParameter(uint32_t aIndex, AudioContext* aContext,
                                         double aStreamTime)
 {
-  class Message : public ControlMessage {
+  class Message final : public ControlMessage
+  {
   public:
     Message(AudioNodeStream* aStream, uint32_t aIndex, MediaStream* aRelativeToStream,
             double aStreamTime)
       : ControlMessage(aStream), mStreamTime(aStreamTime),
-        mRelativeToStream(aRelativeToStream), mIndex(aIndex) {}
-    virtual void Run()
+        mRelativeToStream(aRelativeToStream), mIndex(aIndex)
+    {}
+    virtual void Run() override
     {
       static_cast<AudioNodeStream*>(mStream)->
           SetStreamTimeParameterImpl(mIndex, mRelativeToStream, mStreamTime);
     }
     double mStreamTime;
     MediaStream* mRelativeToStream;
     uint32_t mIndex;
   };
@@ -122,151 +124,163 @@ AudioNodeStream::SetStreamTimeParameterI
 {
   StreamTime ticks = TicksFromDestinationTime(aRelativeToStream, aStreamTime);
   mEngine->SetStreamTimeParameter(aIndex, ticks);
 }
 
 void
 AudioNodeStream::SetDoubleParameter(uint32_t aIndex, double aValue)
 {
-  class Message : public ControlMessage {
+  class Message final : public ControlMessage
+  {
   public:
     Message(AudioNodeStream* aStream, uint32_t aIndex, double aValue)
-      : ControlMessage(aStream), mValue(aValue), mIndex(aIndex) {}
-    virtual void Run()
+      : ControlMessage(aStream), mValue(aValue), mIndex(aIndex)
+    {}
+    virtual void Run() override
     {
       static_cast<AudioNodeStream*>(mStream)->Engine()->
           SetDoubleParameter(mIndex, mValue);
     }
     double mValue;
     uint32_t mIndex;
   };
 
   GraphImpl()->AppendMessage(new Message(this, aIndex, aValue));
 }
 
 void
 AudioNodeStream::SetInt32Parameter(uint32_t aIndex, int32_t aValue)
 {
-  class Message : public ControlMessage {
+  class Message final : public ControlMessage
+  {
   public:
     Message(AudioNodeStream* aStream, uint32_t aIndex, int32_t aValue)
-      : ControlMessage(aStream), mValue(aValue), mIndex(aIndex) {}
-    virtual void Run()
+      : ControlMessage(aStream), mValue(aValue), mIndex(aIndex)
+    {}
+    virtual void Run() override
     {
       static_cast<AudioNodeStream*>(mStream)->Engine()->
           SetInt32Parameter(mIndex, mValue);
     }
     int32_t mValue;
     uint32_t mIndex;
   };
 
   GraphImpl()->AppendMessage(new Message(this, aIndex, aValue));
 }
 
 void
 AudioNodeStream::SetTimelineParameter(uint32_t aIndex,
                                       const AudioParamTimeline& aValue)
 {
-  class Message : public ControlMessage {
+  class Message final : public ControlMessage
+  {
   public:
     Message(AudioNodeStream* aStream, uint32_t aIndex,
             const AudioParamTimeline& aValue)
       : ControlMessage(aStream),
         mValue(aValue),
         mSampleRate(aStream->SampleRate()),
-        mIndex(aIndex) {}
-    virtual void Run()
+        mIndex(aIndex)
+    {}
+    virtual void Run() override
     {
       static_cast<AudioNodeStream*>(mStream)->Engine()->
           SetTimelineParameter(mIndex, mValue, mSampleRate);
     }
     AudioParamTimeline mValue;
     TrackRate mSampleRate;
     uint32_t mIndex;
   };
   GraphImpl()->AppendMessage(new Message(this, aIndex, aValue));
 }
 
 void
 AudioNodeStream::SetThreeDPointParameter(uint32_t aIndex, const ThreeDPoint& aValue)
 {
-  class Message : public ControlMessage {
+  class Message final : public ControlMessage
+  {
   public:
     Message(AudioNodeStream* aStream, uint32_t aIndex, const ThreeDPoint& aValue)
-      : ControlMessage(aStream), mValue(aValue), mIndex(aIndex) {}
-    virtual void Run()
+      : ControlMessage(aStream), mValue(aValue), mIndex(aIndex)
+    {}
+    virtual void Run() override
     {
       static_cast<AudioNodeStream*>(mStream)->Engine()->
           SetThreeDPointParameter(mIndex, mValue);
     }
     ThreeDPoint mValue;
     uint32_t mIndex;
   };
 
   GraphImpl()->AppendMessage(new Message(this, aIndex, aValue));
 }
 
 void
 AudioNodeStream::SetBuffer(already_AddRefed<ThreadSharedFloatArrayBufferList>&& aBuffer)
 {
-  class Message : public ControlMessage {
+  class Message final : public ControlMessage
+  {
   public:
     Message(AudioNodeStream* aStream,
             already_AddRefed<ThreadSharedFloatArrayBufferList>& aBuffer)
-      : ControlMessage(aStream), mBuffer(aBuffer) {}
-    virtual void Run()
+      : ControlMessage(aStream), mBuffer(aBuffer)
+    {}
+    virtual void Run() override
     {
       static_cast<AudioNodeStream*>(mStream)->Engine()->
           SetBuffer(mBuffer.forget());
     }
     nsRefPtr<ThreadSharedFloatArrayBufferList> mBuffer;
   };
 
   GraphImpl()->AppendMessage(new Message(this, aBuffer));
 }
 
 void
 AudioNodeStream::SetRawArrayData(nsTArray<float>& aData)
 {
-  class Message : public ControlMessage {
+  class Message final : public ControlMessage
+  {
   public:
     Message(AudioNodeStream* aStream,
             nsTArray<float>& aData)
       : ControlMessage(aStream)
     {
       mData.SwapElements(aData);
     }
-    virtual void Run()
+    virtual void Run() override
     {
       static_cast<AudioNodeStream*>(mStream)->Engine()->SetRawArrayData(mData);
     }
     nsTArray<float> mData;
   };
 
   GraphImpl()->AppendMessage(new Message(this, aData));
 }
 
 void
 AudioNodeStream::SetChannelMixingParameters(uint32_t aNumberOfChannels,
                                             ChannelCountMode aChannelCountMode,
                                             ChannelInterpretation aChannelInterpretation)
 {
-  class Message : public ControlMessage {
+  class Message final : public ControlMessage
+  {
   public:
     Message(AudioNodeStream* aStream,
             uint32_t aNumberOfChannels,
             ChannelCountMode aChannelCountMode,
             ChannelInterpretation aChannelInterpretation)
       : ControlMessage(aStream),
         mNumberOfChannels(aNumberOfChannels),
         mChannelCountMode(aChannelCountMode),
         mChannelInterpretation(aChannelInterpretation)
     {}
-    virtual void Run()
+    virtual void Run() override
     {
       static_cast<AudioNodeStream*>(mStream)->
         SetChannelMixingParametersImpl(mNumberOfChannels, mChannelCountMode,
                                        mChannelInterpretation);
     }
     uint32_t mNumberOfChannels;
     ChannelCountMode mChannelCountMode;
     ChannelInterpretation mChannelInterpretation;
@@ -275,21 +289,23 @@ AudioNodeStream::SetChannelMixingParamet
   GraphImpl()->AppendMessage(new Message(this, aNumberOfChannels,
                                          aChannelCountMode,
                                          aChannelInterpretation));
 }
 
 void
 AudioNodeStream::SetPassThrough(bool aPassThrough)
 {
-  class Message : public ControlMessage {
+  class Message final : public ControlMessage
+  {
   public:
     Message(AudioNodeStream* aStream, bool aPassThrough)
-      : ControlMessage(aStream), mPassThrough(aPassThrough) {}
-    virtual void Run()
+      : ControlMessage(aStream), mPassThrough(aPassThrough)
+    {}
+    virtual void Run() override
     {
       static_cast<AudioNodeStream*>(mStream)->mPassThrough = mPassThrough;
     }
     bool mPassThrough;
   };
 
   GraphImpl()->AppendMessage(new Message(this, aPassThrough));
 }
--- a/dom/media/webaudio/AudioNodeStream.h
+++ b/dom/media/webaudio/AudioNodeStream.h
@@ -26,17 +26,18 @@ class AudioNodeEngine;
  * The start time of the AudioTrack is aligned to the start time of the
  * AudioContext's destination node stream, plus some multiple of BLOCK_SIZE
  * samples.
  *
  * An AudioNodeStream has an AudioNodeEngine plugged into it that does the
  * actual audio processing. AudioNodeStream contains the glue code that
  * integrates audio processing with the MediaStreamGraph.
  */
-class AudioNodeStream : public ProcessedMediaStream {
+class AudioNodeStream : public ProcessedMediaStream
+{
   typedef dom::ChannelCountMode ChannelCountMode;
   typedef dom::ChannelInterpretation ChannelInterpretation;
 
 public:
   typedef mozilla::dom::AudioContext AudioContext;
 
   enum { AUDIO_TRACK = 1 };
 
--- a/dom/media/webaudio/AudioProcessingEvent.h
+++ b/dom/media/webaudio/AudioProcessingEvent.h
@@ -9,17 +9,17 @@
 
 #include "AudioBuffer.h"
 #include "ScriptProcessorNode.h"
 #include "mozilla/dom/Event.h"
 
 namespace mozilla {
 namespace dom {
 
-class AudioProcessingEvent : public Event
+class AudioProcessingEvent final : public Event
 {
 public:
   AudioProcessingEvent(ScriptProcessorNode* aOwner,
                        nsPresContext* aPresContext,
                        WidgetEvent* aEvent);
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_FORWARD_TO_EVENT
--- a/dom/media/webaudio/BiquadFilterNode.cpp
+++ b/dom/media/webaudio/BiquadFilterNode.cpp
@@ -68,17 +68,17 @@ SetParamsOnBiquad(WebCore::Biquad& aBiqu
     aBiquad.setAllpassParams(normalizedFrequency, aQ);
     break;
   default:
     NS_NOTREACHED("We should never see the alternate names here");
     break;
   }
 }
 
-class BiquadFilterNodeEngine : public AudioNodeEngine
+class BiquadFilterNodeEngine final : public AudioNodeEngine
 {
 public:
   BiquadFilterNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination)
     : AudioNodeEngine(aNode)
     , mSource(nullptr)
     , mDestination(static_cast<AudioNodeStream*> (aDestination->Stream()))
     // Keep the default values in sync with the default values in
     // BiquadFilterNode::BiquadFilterNode
--- a/dom/media/webaudio/BiquadFilterNode.h
+++ b/dom/media/webaudio/BiquadFilterNode.h
@@ -11,17 +11,17 @@
 #include "AudioParam.h"
 #include "mozilla/dom/BiquadFilterNodeBinding.h"
 
 namespace mozilla {
 namespace dom {
 
 class AudioContext;
 
-class BiquadFilterNode : public AudioNode
+class BiquadFilterNode final : public AudioNode
 {
 public:
   explicit BiquadFilterNode(AudioContext* aContext);
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BiquadFilterNode, AudioNode)
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
--- a/dom/media/webaudio/BufferDecoder.h
+++ b/dom/media/webaudio/BufferDecoder.h
@@ -13,17 +13,17 @@
 #include "mozilla/ReentrantMonitor.h"
 
 namespace mozilla {
 
 /**
  * This class provides a decoder object which decodes a media file that lives in
  * a memory buffer.
  */
-class BufferDecoder : public AbstractMediaDecoder
+class BufferDecoder final : public AbstractMediaDecoder
 {
 public:
   // This class holds a weak pointer to MediaResource.  It's the responsibility
   // of the caller to manage the memory of the MediaResource object.
   explicit BufferDecoder(MediaResource* aResource);
 
   NS_DECL_THREADSAFE_ISUPPORTS
 
--- a/dom/media/webaudio/ChannelMergerNode.cpp
+++ b/dom/media/webaudio/ChannelMergerNode.cpp
@@ -9,17 +9,17 @@
 #include "AudioNodeEngine.h"
 #include "AudioNodeStream.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_ISUPPORTS_INHERITED0(ChannelMergerNode, AudioNode)
 
-class ChannelMergerNodeEngine : public AudioNodeEngine
+class ChannelMergerNodeEngine final : public AudioNodeEngine
 {
 public:
   explicit ChannelMergerNodeEngine(ChannelMergerNode* aNode)
     : AudioNodeEngine(aNode)
   {
     MOZ_ASSERT(NS_IsMainThread());
   }
 
--- a/dom/media/webaudio/ChannelMergerNode.h
+++ b/dom/media/webaudio/ChannelMergerNode.h
@@ -9,17 +9,17 @@
 
 #include "AudioNode.h"
 
 namespace mozilla {
 namespace dom {
 
 class AudioContext;
 
-class ChannelMergerNode : public AudioNode
+class ChannelMergerNode final : public AudioNode
 {
 public:
   ChannelMergerNode(AudioContext* aContext,
                     uint16_t aInputCount);
 
   NS_DECL_ISUPPORTS_INHERITED
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
--- a/dom/media/webaudio/ChannelSplitterNode.cpp
+++ b/dom/media/webaudio/ChannelSplitterNode.cpp
@@ -9,17 +9,17 @@
 #include "AudioNodeEngine.h"
 #include "AudioNodeStream.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_ISUPPORTS_INHERITED0(ChannelSplitterNode, AudioNode)
 
-class ChannelSplitterNodeEngine : public AudioNodeEngine
+class ChannelSplitterNodeEngine final : public AudioNodeEngine
 {
 public:
   explicit ChannelSplitterNodeEngine(ChannelSplitterNode* aNode)
     : AudioNodeEngine(aNode)
   {
     MOZ_ASSERT(NS_IsMainThread());
   }
 
--- a/dom/media/webaudio/ChannelSplitterNode.h
+++ b/dom/media/webaudio/ChannelSplitterNode.h
@@ -9,17 +9,17 @@
 
 #include "AudioNode.h"
 
 namespace mozilla {
 namespace dom {
 
 class AudioContext;
 
-class ChannelSplitterNode : public AudioNode
+class ChannelSplitterNode final : public AudioNode
 {
 public:
   ChannelSplitterNode(AudioContext* aContext,
                       uint16_t aOutputCount);
 
   NS_DECL_ISUPPORTS_INHERITED
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
--- a/dom/media/webaudio/ConvolverNode.cpp
+++ b/dom/media/webaudio/ConvolverNode.cpp
@@ -17,17 +17,17 @@ namespace dom {
 NS_IMPL_CYCLE_COLLECTION_INHERITED(ConvolverNode, AudioNode, mBuffer)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ConvolverNode)
 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
 
 NS_IMPL_ADDREF_INHERITED(ConvolverNode, AudioNode)
 NS_IMPL_RELEASE_INHERITED(ConvolverNode, AudioNode)
 
-class ConvolverNodeEngine : public AudioNodeEngine
+class ConvolverNodeEngine final : public AudioNodeEngine
 {
   typedef PlayingRefChangeHandler PlayingRefChanged;
 public:
   ConvolverNodeEngine(AudioNode* aNode, bool aNormalize)
     : AudioNodeEngine(aNode)
     , mBufferLength(0)
     , mLeftOverData(INT32_MIN)
     , mSampleRate(0.0f)
--- a/dom/media/webaudio/ConvolverNode.h
+++ b/dom/media/webaudio/ConvolverNode.h
@@ -8,17 +8,17 @@
 #define ConvolverNode_h_
 
 #include "AudioNode.h"
 #include "AudioBuffer.h"
 
 namespace mozilla {
 namespace dom {
 
-class ConvolverNode : public AudioNode
+class ConvolverNode final : public AudioNode
 {
 public:
   explicit ConvolverNode(AudioContext* aContext);
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ConvolverNode, AudioNode);
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
--- a/dom/media/webaudio/DelayBuffer.h
+++ b/dom/media/webaudio/DelayBuffer.h
@@ -8,17 +8,18 @@
 #define DelayBuffer_h_
 
 #include "nsTArray.h"
 #include "AudioSegment.h"
 #include "mozilla/dom/AudioNodeBinding.h" // for ChannelInterpretation
 
 namespace mozilla {
 
-class DelayBuffer {
+class DelayBuffer final
+{
   typedef dom::ChannelInterpretation ChannelInterpretation;
 
 public:
   // See WebAudioUtils::ComputeSmoothingRate() for frame to frame exponential
   // |smoothingRate| multiplier.
   DelayBuffer(double aMaxDelayTicks, double aSmoothingRate)
     : mSmoothingRate(aSmoothingRate)
     , mCurrentDelay(-1.0)
--- a/dom/media/webaudio/DelayNode.cpp
+++ b/dom/media/webaudio/DelayNode.cpp
@@ -20,17 +20,17 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(Delay
                                    mDelay)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DelayNode)
 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
 
 NS_IMPL_ADDREF_INHERITED(DelayNode, AudioNode)
 NS_IMPL_RELEASE_INHERITED(DelayNode, AudioNode)
 
-class DelayNodeEngine : public AudioNodeEngine
+class DelayNodeEngine final : public AudioNodeEngine
 {
   typedef PlayingRefChangeHandler PlayingRefChanged;
 public:
   DelayNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination,
                   double aMaxDelayTicks)
     : AudioNodeEngine(aNode)
     , mSource(nullptr)
     , mDestination(static_cast<AudioNodeStream*> (aDestination->Stream()))
--- a/dom/media/webaudio/DelayNode.h
+++ b/dom/media/webaudio/DelayNode.h
@@ -10,17 +10,17 @@
 #include "AudioNode.h"
 #include "AudioParam.h"
 
 namespace mozilla {
 namespace dom {
 
 class AudioContext;
 
-class DelayNode : public AudioNode
+class DelayNode final : public AudioNode
 {
 public:
   DelayNode(AudioContext* aContext, double aMaxDelay);
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DelayNode, AudioNode)
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
--- a/dom/media/webaudio/DynamicsCompressorNode.cpp
+++ b/dom/media/webaudio/DynamicsCompressorNode.cpp
@@ -25,17 +25,17 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(Dynam
                                    mRelease)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DynamicsCompressorNode)
 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
 
 NS_IMPL_ADDREF_INHERITED(DynamicsCompressorNode, AudioNode)
 NS_IMPL_RELEASE_INHERITED(DynamicsCompressorNode, AudioNode)
 
-class DynamicsCompressorNodeEngine : public AudioNodeEngine
+class DynamicsCompressorNodeEngine final : public AudioNodeEngine
 {
 public:
   explicit DynamicsCompressorNodeEngine(AudioNode* aNode,
                                         AudioDestinationNode* aDestination)
     : AudioNodeEngine(aNode)
     , mSource(nullptr)
     , mDestination(static_cast<AudioNodeStream*> (aDestination->Stream()))
     // Keep the default value in sync with the default value in
@@ -146,26 +146,26 @@ public:
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
 
 private:
   void SendReductionParamToMainThread(AudioNodeStream* aStream, float aReduction)
   {
     MOZ_ASSERT(!NS_IsMainThread());
 
-    class Command : public nsRunnable
+    class Command final : public nsRunnable
     {
     public:
       Command(AudioNodeStream* aStream, float aReduction)
         : mStream(aStream)
         , mReduction(aReduction)
       {
       }
 
-      NS_IMETHODIMP Run()
+      NS_IMETHOD Run() override
       {
         nsRefPtr<DynamicsCompressorNode> node;
         {
           // No need to keep holding the lock for the whole duration of this
           // function, since we're holding a strong reference to it, so if
           // we can obtain the reference, we will hold the node alive in
           // this function.
           MutexAutoLock lock(mStream->Engine()->NodeMutex());
--- a/dom/media/webaudio/DynamicsCompressorNode.h
+++ b/dom/media/webaudio/DynamicsCompressorNode.h
@@ -10,17 +10,17 @@
 #include "AudioNode.h"
 #include "AudioParam.h"
 
 namespace mozilla {
 namespace dom {
 
 class AudioContext;
 
-class DynamicsCompressorNode : public AudioNode
+class DynamicsCompressorNode final : public AudioNode
 {
 public:
   explicit DynamicsCompressorNode(AudioContext* aContext);
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DynamicsCompressorNode, AudioNode)
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
--- a/dom/media/webaudio/FFTBlock.h
+++ b/dom/media/webaudio/FFTBlock.h
@@ -11,17 +11,18 @@
 #include "AudioNodeEngine.h"
 #include "kiss_fft/kiss_fftr.h"
 
 namespace mozilla {
 
 // This class defines an FFT block, loosely modeled after Blink's FFTFrame
 // class to make sharing code with Blink easy.
 // Currently it's implemented on top of KissFFT on all platforms.
-class FFTBlock {
+class FFTBlock final
+{
 public:
   explicit FFTBlock(uint32_t aFFTSize)
     : mFFT(nullptr)
     , mIFFT(nullptr)
     , mFFTSize(aFFTSize)
   {
     MOZ_COUNT_CTOR(FFTBlock);
     mOutputBuffer.SetLength(aFFTSize / 2 + 1);
--- a/dom/media/webaudio/GainNode.cpp
+++ b/dom/media/webaudio/GainNode.cpp
@@ -18,17 +18,17 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(GainN
                                    mGain)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(GainNode)
 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
 
 NS_IMPL_ADDREF_INHERITED(GainNode, AudioNode)
 NS_IMPL_RELEASE_INHERITED(GainNode, AudioNode)
 
-class GainNodeEngine : public AudioNodeEngine
+class GainNodeEngine final : public AudioNodeEngine
 {
 public:
   GainNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination)
     : AudioNodeEngine(aNode)
     , mSource(nullptr)
     , mDestination(static_cast<AudioNodeStream*> (aDestination->Stream()))
     // Keep the default value in sync with the default value in GainNode::GainNode.
     , mGain(1.f)
--- a/dom/media/webaudio/GainNode.h
+++ b/dom/media/webaudio/GainNode.h
@@ -10,17 +10,17 @@
 #include "AudioNode.h"
 #include "AudioParam.h"
 
 namespace mozilla {
 namespace dom {
 
 class AudioContext;
 
-class GainNode : public AudioNode
+class GainNode final : public AudioNode
 {
 public:
   explicit GainNode(AudioContext* aContext);
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(GainNode, AudioNode)
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
--- a/dom/media/webaudio/MediaBufferDecoder.cpp
+++ b/dom/media/webaudio/MediaBufferDecoder.cpp
@@ -45,17 +45,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(WebAudioDecodeJob)
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebAudioDecodeJob, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebAudioDecodeJob, Release)
 
 using namespace dom;
 
-class ReportResultTask : public nsRunnable
+class ReportResultTask final : public nsRunnable
 {
 public:
   ReportResultTask(WebAudioDecodeJob& aDecodeJob,
                    WebAudioDecodeJob::ResultFn aFunction,
                    WebAudioDecodeJob::ErrorCode aErrorCode)
     : mDecodeJob(aDecodeJob)
     , mFunction(aFunction)
     , mErrorCode(aErrorCode)
@@ -77,23 +77,24 @@ private:
   // Therefore, it is not safe to do anything fancy with it in this class.
   // Really, this class is only used because nsRunnableMethod doesn't support
   // methods accepting arguments.
   WebAudioDecodeJob& mDecodeJob;
   WebAudioDecodeJob::ResultFn mFunction;
   WebAudioDecodeJob::ErrorCode mErrorCode;
 };
 
-enum class PhaseEnum : int {
+enum class PhaseEnum : int
+{
   Decode,
   AllocateBuffer,
   Done
 };
 
-class MediaDecodeTask : public nsRunnable
+class MediaDecodeTask final : public nsRunnable
 {
 public:
   MediaDecodeTask(const char* aContentType, uint8_t* aBuffer,
                   uint32_t aLength,
                   WebAudioDecodeJob& aDecodeJob)
     : mContentType(aContentType)
     , mBuffer(aBuffer)
     , mLength(aLength)
@@ -209,17 +210,18 @@ MediaDecodeTask::CreateReader()
 
   if (!mDecoderReader->EnsureTaskQueue()) {
     return false;
   }
 
   return true;
 }
 
-class AutoResampler {
+class AutoResampler final
+{
 public:
   AutoResampler()
     : mResampler(nullptr)
   {}
   ~AutoResampler()
   {
     if (mResampler) {
       speex_resampler_destroy(mResampler);
--- a/dom/media/webaudio/MediaElementAudioSourceNode.h
+++ b/dom/media/webaudio/MediaElementAudioSourceNode.h
@@ -7,17 +7,17 @@
 #ifndef MediaElementAudioSourceNode_h_
 #define MediaElementAudioSourceNode_h_
 
 #include "MediaStreamAudioSourceNode.h"
 
 namespace mozilla {
 namespace dom {
 
-class MediaElementAudioSourceNode : public MediaStreamAudioSourceNode
+class MediaElementAudioSourceNode final : public MediaStreamAudioSourceNode
 {
 public:
   MediaElementAudioSourceNode(AudioContext* aContext,
                               DOMMediaStream* aStream);
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   virtual const char* NodeType() const override
--- a/dom/media/webaudio/MediaStreamAudioDestinationNode.h
+++ b/dom/media/webaudio/MediaStreamAudioDestinationNode.h
@@ -7,17 +7,17 @@
 #ifndef MediaStreamAudioDestinationNode_h_
 #define MediaStreamAudioDestinationNode_h_
 
 #include "AudioNode.h"
 
 namespace mozilla {
 namespace dom {
 
-class MediaStreamAudioDestinationNode : public AudioNode
+class MediaStreamAudioDestinationNode final : public AudioNode
 {
 public:
   explicit MediaStreamAudioDestinationNode(AudioContext* aContext);
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaStreamAudioDestinationNode, AudioNode)
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
--- a/dom/media/webaudio/MediaStreamAudioSourceNode.h
+++ b/dom/media/webaudio/MediaStreamAudioSourceNode.h
@@ -10,17 +10,17 @@
 #include "AudioNode.h"
 #include "DOMMediaStream.h"
 #include "AudioNodeEngine.h"
 
 namespace mozilla {
 
 namespace dom {
 
-class MediaStreamAudioSourceNodeEngine : public AudioNodeEngine
+class MediaStreamAudioSourceNodeEngine final : public AudioNodeEngine
 {
 public:
   explicit MediaStreamAudioSourceNodeEngine(AudioNode* aNode)
     : AudioNodeEngine(aNode), mEnabled(false) {}
 
   bool IsEnabled() const { return mEnabled; }
   enum Parameters {
     ENABLE
--- a/dom/media/webaudio/OfflineAudioCompletionEvent.h
+++ b/dom/media/webaudio/OfflineAudioCompletionEvent.h
@@ -10,17 +10,17 @@
 #include "AudioBuffer.h"
 #include "mozilla/dom/Event.h"
 
 namespace mozilla {
 namespace dom {
 
 class AudioContext;
 
-class OfflineAudioCompletionEvent : public Event
+class OfflineAudioCompletionEvent final : public Event
 {
 public:
   OfflineAudioCompletionEvent(AudioContext* aOwner,
                               nsPresContext* aPresContext,
                               WidgetEvent* aEvent);
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_FORWARD_TO_EVENT
--- a/dom/media/webaudio/OscillatorNode.cpp
+++ b/dom/media/webaudio/OscillatorNode.cpp
@@ -18,17 +18,17 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(Oscil
                                    mPeriodicWave, mFrequency, mDetune)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(OscillatorNode)
 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
 
 NS_IMPL_ADDREF_INHERITED(OscillatorNode, AudioNode)
 NS_IMPL_RELEASE_INHERITED(OscillatorNode, AudioNode)
 
-class OscillatorNodeEngine : public AudioNodeEngine
+class OscillatorNodeEngine final : public AudioNodeEngine
 {
 public:
   OscillatorNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination)
     : AudioNodeEngine(aNode)
     , mSource(nullptr)
     , mDestination(static_cast<AudioNodeStream*> (aDestination->Stream()))
     , mStart(-1)
     , mStop(STREAM_TIME_MAX)
@@ -510,22 +510,22 @@ OscillatorNode::Stop(double aWhen, Error
   ns->SetStreamTimeParameter(OscillatorNodeEngine::STOP,
                              Context(), std::max(0.0, aWhen));
 }
 
 void
 OscillatorNode::NotifyMainThreadStateChanged()
 {
   if (mStream->IsFinished()) {
-    class EndedEventDispatcher : public nsRunnable
+    class EndedEventDispatcher final : public nsRunnable
     {
     public:
       explicit EndedEventDispatcher(OscillatorNode* aNode)
         : mNode(aNode) {}
-      NS_IMETHODIMP Run()
+      NS_IMETHOD Run() override
       {
         // If it's not safe to run scripts right now, schedule this to run later
         if (!nsContentUtils::IsSafeToRunScript()) {
           nsContentUtils::AddScriptRunner(this);
           return NS_OK;
         }
 
         mNode->DispatchTrustedEvent(NS_LITERAL_STRING("ended"));
--- a/dom/media/webaudio/OscillatorNode.h
+++ b/dom/media/webaudio/OscillatorNode.h
@@ -13,18 +13,18 @@
 #include "mozilla/dom/OscillatorNodeBinding.h"
 #include "mozilla/Preferences.h"
 
 namespace mozilla {
 namespace dom {
 
 class AudioContext;
 
-class OscillatorNode : public AudioNode,
-                       public MainThreadMediaStreamListener
+class OscillatorNode final : public AudioNode,
+                             public MainThreadMediaStreamListener
 {
 public:
   explicit OscillatorNode(AudioContext* aContext);
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(OscillatorNode, AudioNode)
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
--- a/dom/media/webaudio/PannerNode.cpp
+++ b/dom/media/webaudio/PannerNode.cpp
@@ -34,17 +34,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(PannerNode)
 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
 
 NS_IMPL_ADDREF_INHERITED(PannerNode, AudioNode)
 NS_IMPL_RELEASE_INHERITED(PannerNode, AudioNode)
 
-class PannerNodeEngine : public AudioNodeEngine
+class PannerNodeEngine final : public AudioNodeEngine
 {
 public:
   explicit PannerNodeEngine(AudioNode* aNode)
     : AudioNodeEngine(aNode)
     // Please keep these default values consistent with PannerNode::PannerNode below.
     , mPanningModelFunction(&PannerNodeEngine::EqualPowerPanningFunction)
     , mDistanceModelFunction(&PannerNodeEngine::InverseGainFunction)
     , mPosition()
--- a/dom/media/webaudio/PannerNode.h
+++ b/dom/media/webaudio/PannerNode.h
@@ -16,18 +16,18 @@
 #include <set>
 
 namespace mozilla {
 namespace dom {
 
 class AudioContext;
 class AudioBufferSourceNode;
 
-class PannerNode : public AudioNode,
-                   public SupportsWeakPtr<PannerNode>
+class PannerNode final : public AudioNode,
+                         public SupportsWeakPtr<PannerNode>
 {
 public:
   MOZ_DECLARE_WEAKREFERENCE_TYPENAME(PannerNode)
   explicit PannerNode(AudioContext* aContext);
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   virtual void DestroyMediaStream() override;
--- a/dom/media/webaudio/PlayingRefChangeHandler.h
+++ b/dom/media/webaudio/PlayingRefChangeHandler.h
@@ -8,17 +8,17 @@
 #define PlayingRefChangeHandler_h__
 
 #include "nsThreadUtils.h"
 #include "AudioNodeStream.h"
 
 namespace mozilla {
 namespace dom {
 
-class PlayingRefChangeHandler : public nsRunnable
+class PlayingRefChangeHandler final : public nsRunnable
 {
 public:
   enum ChangeType { ADDREF, RELEASE };
   PlayingRefChangeHandler(AudioNodeStream* aStream, ChangeType aChange)
     : mStream(aStream)
     , mChange(aChange)
   {
   }
--- a/dom/media/webaudio/ReportDecodeResultTask.h
+++ b/dom/media/webaudio/ReportDecodeResultTask.h
@@ -7,17 +7,17 @@
 #ifndef ReportDecodeResultTask_h_
 #define ReportDecodeResultTask_h_
 
 #include "mozilla/Attributes.h"
 #include "MediaBufferDecoder.h"
 
 namespace mozilla {
 
-class ReportDecodeResultTask : public nsRunnable
+class ReportDecodeResultTask final : public nsRunnable
 {
 public:
   ReportDecodeResultTask(DecodeJob& aDecodeJob,
                          DecodeJob::ResultFn aFunction)
     : mDecodeJob(aDecodeJob)
     , mFunction(aFunction)
   {
     MOZ_ASSERT(aFunction);
--- a/dom/media/webaudio/ScriptProcessorNode.cpp
+++ b/dom/media/webaudio/ScriptProcessorNode.cpp
@@ -23,20 +23,20 @@ namespace dom {
 // The maximum latency, in seconds, that we can live with before dropping
 // buffers.
 static const float MAX_LATENCY_S = 0.5;
 
 NS_IMPL_ISUPPORTS_INHERITED0(ScriptProcessorNode, AudioNode)
 
 // This class manages a queue of output buffers shared between
 // the main thread and the Media Stream Graph thread.
-class SharedBuffers
+class SharedBuffers final
 {
 private:
-  class OutputQueue
+  class OutputQueue final
   {
   public:
     explicit OutputQueue(const char* aName)
       : mMutex(aName)
     {}
 
     size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
     {
@@ -232,17 +232,17 @@ private:
   float mLatency;
   // This is the time at which we last produced a buffer, to detect if the main
   // thread has been blocked.
   TimeStamp mLastEventTime;
   // True if we should be dropping buffers.
   bool mDroppingBuffers;
 };
 
-class ScriptProcessorNodeEngine : public AudioNodeEngine
+class ScriptProcessorNodeEngine final : public AudioNodeEngine
 {
 public:
   typedef nsAutoTArray<nsAutoArrayPtr<float>, 2> InputChannels;
 
   ScriptProcessorNodeEngine(ScriptProcessorNode* aNode,
                             AudioDestinationNode* aDestination,
                             uint32_t aBufferSize,
                             uint32_t aNumberOfInputChannels)
@@ -356,17 +356,17 @@ private:
     // Add the duration of the current sample
     playbackTick += WEBAUDIO_BLOCK_SIZE;
     // Add the delay caused by the main thread
     playbackTick += mSharedBuffers->DelaySoFar();
     // Compute the playback time in the coordinate system of the destination
     double playbackTime =
       mSource->DestinationTimeFromTicks(mDestination, playbackTick);
 
-    class Command : public nsRunnable
+    class Command final : public nsRunnable
     {
     public:
       Command(AudioNodeStream* aStream,
               InputChannels& aInputChannels,
               double aPlaybackTime,
               bool aNullInput)
         : mStream(aStream)
         , mPlaybackTime(aPlaybackTime)
@@ -375,17 +375,17 @@ private:
         mInputChannels.SetLength(aInputChannels.Length());
         if (!aNullInput) {
           for (uint32_t i = 0; i < mInputChannels.Length(); ++i) {
             mInputChannels[i] = aInputChannels[i].forget();
           }
         }
       }
 
-      NS_IMETHODIMP Run()
+      NS_IMETHOD Run() override
       {
         nsRefPtr<ScriptProcessorNode> node = static_cast<ScriptProcessorNode*>
           (mStream->Engine()->NodeMainThread());
         if (!node) {
           return NS_OK;
         }
         AudioContext* context = node->Context();
         if (!context) {
--- a/dom/media/webaudio/ScriptProcessorNode.h
+++ b/dom/media/webaudio/ScriptProcessorNode.h
@@ -11,17 +11,17 @@
 #include "nsAutoPtr.h"
 
 namespace mozilla {
 namespace dom {
 
 class AudioContext;
 class SharedBuffers;
 
-class ScriptProcessorNode : public AudioNode
+class ScriptProcessorNode final : public AudioNode
 {
 public:
   ScriptProcessorNode(AudioContext* aContext,
                       uint32_t aBufferSize,
                       uint32_t aNumberOfInputChannels,
                       uint32_t aNumberOfOutputChannels);
 
   NS_DECL_ISUPPORTS_INHERITED
--- a/dom/media/webaudio/StereoPannerNode.cpp
+++ b/dom/media/webaudio/StereoPannerNode.cpp
@@ -22,17 +22,17 @@ using namespace std;
 NS_IMPL_CYCLE_COLLECTION_INHERITED(StereoPannerNode, AudioNode, mPan)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(StereoPannerNode)
 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
 
 NS_IMPL_ADDREF_INHERITED(StereoPannerNode, AudioNode)
 NS_IMPL_RELEASE_INHERITED(StereoPannerNode, AudioNode)
 
-class StereoPannerNodeEngine : public AudioNodeEngine
+class StereoPannerNodeEngine final : public AudioNodeEngine
 {
 public:
   StereoPannerNodeEngine(AudioNode* aNode,
                          AudioDestinationNode* aDestination)
     : AudioNodeEngine(aNode)
     , mSource(nullptr)
     , mDestination(static_cast<AudioNodeStream*>(aDestination->Stream()))
     // Keep the default value in sync with the default value in
--- a/dom/media/webaudio/StereoPannerNode.h
+++ b/dom/media/webaudio/StereoPannerNode.h
@@ -10,17 +10,17 @@
 #include "AudioNode.h"
 #include "mozilla/dom/StereoPannerNodeBinding.h"
 
 namespace mozilla {
 namespace dom {
 
 class AudioContext;
 
-class StereoPannerNode : public AudioNode
+class StereoPannerNode final : public AudioNode
 {
 public:
   MOZ_DECLARE_REFCOUNTED_TYPENAME(StereoPannerNode)
   explicit StereoPannerNode(AudioContext* aContext);
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   virtual void SetChannelCount(uint32_t aChannelCount, ErrorResult& aRv) override
--- a/dom/media/webaudio/ThreeDPoint.h
+++ b/dom/media/webaudio/ThreeDPoint.h
@@ -9,17 +9,18 @@
 
 #include <cmath>
 #include <algorithm>
 
 namespace mozilla {
 
 namespace dom {
 
-struct ThreeDPoint {
+struct ThreeDPoint final
+{
   ThreeDPoint()
     : x(0.)
     , y(0.)
     , z(0.)
   {
   }
   ThreeDPoint(double aX, double aY, double aZ)
     : x(aX)
--- a/dom/media/webaudio/WaveShaperNode.cpp
+++ b/dom/media/webaudio/WaveShaperNode.cpp
@@ -43,17 +43,17 @@ static uint32_t ValueOf(OverSampleType a
   case OverSampleType::_2x:  return 2;
   case OverSampleType::_4x:  return 4;
   default:
     NS_NOTREACHED("We should never reach here");
     return 1;
   }
 }
 
-class Resampler
+class Resampler final
 {
 public:
   Resampler()
     : mType(OverSampleType::None)
     , mUpSampler(nullptr)
     , mDownSampler(nullptr)
     , mChannels(0)
     , mSampleRate(0)
@@ -156,17 +156,17 @@ private:
   OverSampleType mType;
   SpeexResamplerState* mUpSampler;
   SpeexResamplerState* mDownSampler;
   uint32_t mChannels;
   TrackRate mSampleRate;
   nsTArray<float> mBuffer;
 };
 
-class WaveShaperNodeEngine : public AudioNodeEngine
+class WaveShaperNodeEngine final : public AudioNodeEngine
 {
 public:
   explicit WaveShaperNodeEngine(AudioNode* aNode)
     : AudioNodeEngine(aNode)
     , mType(OverSampleType::None)
   {
   }
 
--- a/dom/media/webaudio/WaveShaperNode.h
+++ b/dom/media/webaudio/WaveShaperNode.h
@@ -11,17 +11,17 @@
 #include "mozilla/dom/WaveShaperNodeBinding.h"
 #include "mozilla/dom/TypedArray.h"
 
 namespace mozilla {
 namespace dom {
 
 class AudioContext;
 
-class WaveShaperNode : public AudioNode
+class WaveShaperNode final : public AudioNode
 {
 public:
   explicit WaveShaperNode(AudioContext *aContext);
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(WaveShaperNode, AudioNode)
 
   virtual JSObject* WrapObject(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -127,17 +127,17 @@ var interfaceNamesInGlobalScope =
     {name: "MozAbortablePromise", pref: "dom.abortablepromise.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "AlarmsManager", pref: "dom.mozAlarms.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "AnalyserNode",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "Animation", pref: "dom.animations-api.core.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "AnimationEffectReadonly", pref: "dom.animations-api.core.enabled"},
+    {name: "AnimationEffectReadOnly", pref: "dom.animations-api.core.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "AnimationEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "AnimationTimeline", pref: "dom.animations-api.core.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Attr",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Audio",
@@ -646,17 +646,17 @@ var interfaceNamesInGlobalScope =
     {name: "InputPortManager", b2g: true, pref: "dom.inputport.enabled", permission: ["inputport"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "InstallTrigger", b2g: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "KeyEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "KeyboardEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "KeyframeEffectReadonly", pref: "dom.animations-api.core.enabled"},
+    {name: "KeyframeEffectReadOnly", pref: "dom.animations-api.core.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "LocalMediaStream",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Location",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MediaDeviceInfo",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MediaDevices",
--- a/dom/webidl/Animation.webidl
+++ b/dom/webidl/Animation.webidl
@@ -11,17 +11,17 @@
  */
 
 enum AnimationPlayState { "idle", "pending", "running", "paused", "finished" };
 
 [Func="nsDocument::IsWebAnimationsEnabled"]
 interface Animation {
   // Bug 1049975: Make 'effect' writeable
   [Pure]
-  readonly attribute AnimationEffectReadonly? effect;
+  readonly attribute AnimationEffectReadOnly? effect;
   readonly attribute AnimationTimeline timeline;
   [BinaryName="startTimeAsDouble"]
   attribute double? startTime;
   [SetterThrows, BinaryName="currentTimeAsDouble"]
   attribute double? currentTime;
 
            attribute double             playbackRate;
   [BinaryName="playStateFromJS"]
rename from dom/webidl/AnimationEffectReadonly.webidl
rename to dom/webidl/AnimationEffectReadOnly.webidl
--- a/dom/webidl/AnimationEffectReadonly.webidl
+++ b/dom/webidl/AnimationEffectReadOnly.webidl
@@ -6,13 +6,13 @@
  * The origin of this IDL file is
  * http://w3c.github.io/web-animations/#animationeffectreadonly
  *
  * Copyright © 2015 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
 [Func="nsDocument::IsWebAnimationsEnabled"]
-interface AnimationEffectReadonly {
+interface AnimationEffectReadOnly {
   // Not yet implemented:
-  // readonly attribute AnimationEffectTimingReadonly timing;
+  // readonly attribute AnimationEffectTimingReadOnly timing;
   // readonly attribute ComputedTimingProperties      computedTiming;
 };
--- a/dom/webidl/KeyframeEffect.webidl
+++ b/dom/webidl/KeyframeEffect.webidl
@@ -7,17 +7,17 @@
  * http://w3c.github.io/web-animations/#the-keyframeeffect-interfaces
  *
  * Copyright © 2015 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
 [HeaderFile="mozilla/dom/KeyframeEffect.h",
  Func="nsDocument::IsWebAnimationsEnabled"]
-interface KeyframeEffectReadonly : AnimationEffectReadonly {
+interface KeyframeEffectReadOnly : AnimationEffectReadOnly {
   readonly attribute Element?  target;
   readonly attribute DOMString name;
   // Not yet implemented:
   // readonly attribute IterationCompositeOperation iterationComposite;
   // readonly attribute CompositeOperation          composite;
   // readonly attribute DOMString                   spacing;
   // KeyframeEffect             clone();
   // sequence<ComputedKeyframe> getFrames ();
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -18,17 +18,17 @@ PREPROCESSED_WEBIDL_FILES = [
 WEBIDL_FILES = [
     'AbortablePromise.webidl',
     'AbstractWorker.webidl',
     'ActivityRequestHandler.webidl',
     'AlarmsManager.webidl',
     'AnalyserNode.webidl',
     'Animatable.webidl',
     'Animation.webidl',
-    'AnimationEffectReadonly.webidl',
+    'AnimationEffectReadOnly.webidl',
     'AnimationEvent.webidl',
     'AnimationTimeline.webidl',
     'AnonymousContent.webidl',
     'AppInfo.webidl',
     'AppNotificationServiceOptions.webidl',
     'Apps.webidl',
     'APZTestData.webidl',
     'ArchiveReader.webidl',
--- a/dom/workers/ScriptLoader.cpp
+++ b/dom/workers/ScriptLoader.cpp
@@ -8,16 +8,17 @@
 #include "nsIChannel.h"
 #include "nsIContentPolicy.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsIHttpChannel.h"
 #include "nsIInputStreamPump.h"
 #include "nsIIOService.h"
 #include "nsIProtocolHandler.h"
 #include "nsIScriptSecurityManager.h"
+#include "nsISerializable.h"
 #include "nsIStreamLoader.h"
 #include "nsIStreamListenerTee.h"
 #include "nsIThreadRetargetableRequest.h"
 #include "nsIURI.h"
 
 #include "jsapi.h"
 #include "nsError.h"
 #include "nsContentPolicyUtils.h"
@@ -415,16 +416,17 @@ private:
 
   ScriptLoadInfo& mLoadInfo;
   uint32_t mIndex;
   nsRefPtr<ScriptLoaderRunnable> mRunnable;
   bool mIsWorkerScript;
   bool mFailed;
   nsCOMPtr<nsIInputStreamPump> mPump;
   nsCOMPtr<nsIURI> mBaseURI;
+  nsCString mSecurityInfo;
 };
 
 NS_IMPL_ISUPPORTS(CacheScriptLoader, nsIStreamLoaderObserver)
 
 class CachePromiseHandler final : public PromiseNativeHandler
 {
 public:
   CachePromiseHandler(ScriptLoaderRunnable* aRunnable,
@@ -452,26 +454,28 @@ private:
 
   nsRefPtr<ScriptLoaderRunnable> mRunnable;
   ScriptLoadInfo& mLoadInfo;
   uint32_t mIndex;
 };
 
 class ScriptLoaderRunnable final : public WorkerFeature,
                                    public nsIRunnable,
-                                   public nsIStreamLoaderObserver
+                                   public nsIStreamLoaderObserver,
+                                   public nsIRequestObserver
 {
   friend class ScriptExecutorRunnable;
   friend class CachePromiseHandler;
   friend class CacheScriptLoader;
 
   WorkerPrivate* mWorkerPrivate;
   nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
   nsTArray<ScriptLoadInfo> mLoadInfos;
   nsRefPtr<CacheCreator> mCacheCreator;
+  nsCOMPtr<nsIInputStream> mReader;
   bool mIsMainScript;
   WorkerScriptType mWorkerScriptType;
   bool mCanceled;
   bool mCanceledMainThread;
 
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
 
@@ -555,16 +559,89 @@ private:
     ScriptLoadInfo& loadInfo = mLoadInfos[index];
 
     nsresult rv = OnStreamCompleteInternal(aLoader, aContext, aStatus,
                                            aStringLen, aString, loadInfo);
     LoadingFinished(index, rv);
     return NS_OK;
   }
 
+  NS_IMETHOD
+  OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
+  {
+    AssertIsOnMainThread();
+
+    nsCOMPtr<nsISupportsPRUint32> indexSupports(do_QueryInterface(aContext));
+    MOZ_ASSERT(indexSupports, "This should never fail!");
+
+    uint32_t index = UINT32_MAX;
+    if (NS_FAILED(indexSupports->GetData(&index)) ||
+        index >= mLoadInfos.Length()) {
+      MOZ_CRASH("Bad index!");
+    }
+
+    ScriptLoadInfo& loadInfo = mLoadInfos[index];
+
+    nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+    MOZ_ASSERT(channel == loadInfo.mChannel);
+
+    // We synthesize the result code, but its never exposed to content.
+    nsRefPtr<InternalResponse> ir =
+      new InternalResponse(200, NS_LITERAL_CSTRING("OK"));
+    ir->SetBody(mReader);
+
+    // Set the security info of the channel on the response so that it's
+    // saved in the cache.
+    nsCOMPtr<nsISupports> infoObj;
+    channel->GetSecurityInfo(getter_AddRefs(infoObj));
+    if (infoObj) {
+      nsCOMPtr<nsISerializable> serializable = do_QueryInterface(infoObj);
+      if (serializable) {
+        ir->SetSecurityInfo(serializable);
+        MOZ_ASSERT(!ir->GetSecurityInfo().IsEmpty());
+      } else {
+        NS_WARNING("A non-serializable object was obtained from nsIChannel::GetSecurityInfo()!");
+      }
+    }
+
+    nsRefPtr<Response> response = new Response(mCacheCreator->Global(), ir);
+
+    RequestOrUSVString request;
+
+    MOZ_ASSERT(!loadInfo.mFullURL.IsEmpty());
+    request.SetAsUSVString().Rebind(loadInfo.mFullURL.Data(),
+                                    loadInfo.mFullURL.Length());
+
+    ErrorResult error;
+    nsRefPtr<Promise> cachePromise =
+      mCacheCreator->Cache_()->Put(request, *response, error);
+    if (NS_WARN_IF(error.Failed())) {
+      nsresult rv = error.StealNSResult();
+      channel->Cancel(rv);
+      return rv;
+    }
+
+    nsRefPtr<CachePromiseHandler> promiseHandler =
+      new CachePromiseHandler(this, loadInfo, index);
+    cachePromise->AppendNativeHandler(promiseHandler);
+
+    loadInfo.mCachePromise.swap(cachePromise);
+    loadInfo.mCacheStatus = ScriptLoadInfo::WritingToCache;
+
+    return NS_OK;
+  }
+
+  NS_IMETHOD
+  OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
+                nsresult aStatusCode)
+  {
+    // Nothing to do here!
+    return NS_OK;
+  }
+
   virtual bool
   Notify(JSContext* aCx, Status aStatus) override
   {
     mWorkerPrivate->AssertIsOnWorkerThread();
 
     if (aStatus >= Terminating && !mCanceled) {
       mCanceled = true;
 
@@ -768,69 +845,39 @@ private:
     }
 
     if (loadInfo.mCacheStatus != ScriptLoadInfo::ToBeCached) {
       rv = channel->AsyncOpen(loader, indexSupports);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     } else {
-      nsCOMPtr<nsIInputStream> reader;
       nsCOMPtr<nsIOutputStream> writer;
 
       // In case we return early.
       loadInfo.mCacheStatus = ScriptLoadInfo::Cancel;
 
-      rv = NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer), 0,
+      rv = NS_NewPipe(getter_AddRefs(mReader), getter_AddRefs(writer), 0,
                       UINT32_MAX, // unlimited size to avoid writer WOULD_BLOCK case
                       true, false); // non-blocking reader, blocking writer
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
-      // We synthesize the result code, but its never exposed to content.
-      nsRefPtr<InternalResponse> ir =
-        new InternalResponse(200, NS_LITERAL_CSTRING("OK"));
-      ir->SetBody(reader);
-
       nsCOMPtr<nsIStreamListenerTee> tee =
         do_CreateInstance(NS_STREAMLISTENERTEE_CONTRACTID);
-      rv = tee->Init(loader, writer, nullptr);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-
-      rv = channel->AsyncOpen(tee, indexSupports);
+      rv = tee->Init(loader, writer, this);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
-      nsRefPtr<Response> response = new Response(mCacheCreator->Global(), ir);
-
-      RequestOrUSVString request;
-
-      MOZ_ASSERT(!loadInfo.mFullURL.IsEmpty());
-      request.SetAsUSVString().Rebind(loadInfo.mFullURL.Data(),
-                                      loadInfo.mFullURL.Length());
-
-      ErrorResult error;
-      nsRefPtr<Promise> cachePromise =
-        mCacheCreator->Cache_()->Put(request, *response, error);
-      if (NS_WARN_IF(error.Failed())) {
-        nsresult rv = error.StealNSResult();
-        channel->Cancel(rv);
+      nsresult rv = channel->AsyncOpen(tee, indexSupports);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
-
-      nsRefPtr<CachePromiseHandler> promiseHandler =
-        new CachePromiseHandler(this, loadInfo, aIndex);
-      cachePromise->AppendNativeHandler(promiseHandler);
-
-      loadInfo.mCachePromise.swap(cachePromise);
-      loadInfo.mCacheStatus = ScriptLoadInfo::WritingToCache;
     }
 
     loadInfo.mChannel.swap(channel);
 
     return NS_OK;
   }
 
   nsresult
@@ -912,16 +959,30 @@ private:
     }
 
     // Update the principal of the worker and its base URI if we just loaded the
     // worker's primary script.
     if (IsMainWorkerScript()) {
       // Take care of the base URI first.
       mWorkerPrivate->SetBaseURI(finalURI);
 
+      // Store the security info if needed.
+      if (mWorkerPrivate->IsServiceWorker()) {
+        nsCOMPtr<nsISupports> infoObj;
+        channel->GetSecurityInfo(getter_AddRefs(infoObj));
+        if (infoObj) {
+          nsCOMPtr<nsISerializable> serializable = do_QueryInterface(infoObj);
+          if (serializable) {
+            mWorkerPrivate->SetSecurityInfo(serializable);
+          } else {
+            NS_WARNING("A non-serializable object was obtained from nsIChannel::GetSecurityInfo()!");
+          }
+        }
+      }
+
       // Now to figure out which principal to give this worker.
       WorkerPrivate* parent = mWorkerPrivate->GetParent();
 
       NS_ASSERTION(mWorkerPrivate->GetPrincipal() || parent,
                    "Must have one of these!");
 
       nsCOMPtr<nsIPrincipal> loadPrincipal = mWorkerPrivate->GetPrincipal() ?
                                              mWorkerPrivate->GetPrincipal() :
@@ -985,17 +1046,17 @@ private:
     }
 
     DataReceived();
     return NS_OK;
   }
 
   void
   DataReceivedFromCache(uint32_t aIndex, const uint8_t* aString,
-                        uint32_t aStringLen)
+                        uint32_t aStringLen, const nsCString& aSecurityInfo)
   {
     AssertIsOnMainThread();
     MOZ_ASSERT(aIndex < mLoadInfos.Length());
     ScriptLoadInfo& loadInfo = mLoadInfos[aIndex];
     MOZ_ASSERT(loadInfo.mCacheStatus == ScriptLoadInfo::Cached);
 
     // May be null.
     nsIDocument* parentDoc = mWorkerPrivate->GetDocument();
@@ -1013,16 +1074,17 @@ private:
       if (NS_SUCCEEDED(rv)) {
         mWorkerPrivate->SetBaseURI(finalURI);
       }
 
       nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
       MOZ_ASSERT(principal);
       nsILoadGroup* loadGroup = mWorkerPrivate->GetLoadGroup();
       MOZ_ASSERT(loadGroup);
+      mWorkerPrivate->SetSecurityInfo(aSecurityInfo);
       // Needed to initialize the principal info. This is fine because
       // the cache principal cannot change, unlike the channel principal.
       mWorkerPrivate->SetPrincipal(principal, loadGroup);
     }
 
     if (NS_SUCCEEDED(rv)) {
       DataReceived();
     }
@@ -1096,17 +1158,19 @@ private:
                                    firstIndex, lastIndex);
       if (!runnable->Dispatch(nullptr)) {
         MOZ_ASSERT(false, "This should never fail!");
       }
     }
   }
 };
 
-NS_IMPL_ISUPPORTS(ScriptLoaderRunnable, nsIRunnable, nsIStreamLoaderObserver)
+NS_IMPL_ISUPPORTS(ScriptLoaderRunnable, nsIRunnable,
+                                        nsIStreamLoaderObserver,
+                                        nsIRequestObserver)
 
 void
 CachePromiseHandler::ResolvedCallback(JSContext* aCx,
                                       JS::Handle<JS::Value> aValue)
 {
   AssertIsOnMainThread();
   // May already have been canceled by CacheScriptLoader::Fail from
   // CancelMainThread.
@@ -1364,20 +1428,21 @@ CacheScriptLoader::ResolvedCallback(JSCo
   nsresult rv = UNWRAP_OBJECT(Response, obj, response);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     Fail(rv);
     return;
   }
 
   nsCOMPtr<nsIInputStream> inputStream;
   response->GetBody(getter_AddRefs(inputStream));
+  mSecurityInfo = response->GetSecurityInfo();
 
   if (!inputStream) {
     mLoadInfo.mCacheStatus = ScriptLoadInfo::Cached;
-    mRunnable->DataReceivedFromCache(mIndex, (uint8_t*)"", 0);
+    mRunnable->DataReceivedFromCache(mIndex, (uint8_t*)"", 0, mSecurityInfo);
     return;
   }
 
   MOZ_ASSERT(!mPump);
   rv = NS_NewInputStreamPump(getter_AddRefs(mPump), inputStream);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     Fail(rv);
     return;
@@ -1423,17 +1488,17 @@ CacheScriptLoader::OnStreamComplete(nsIS
 
   if (NS_FAILED(aStatus)) {
     Fail(aStatus);
     return NS_OK;
   }
 
   mLoadInfo.mCacheStatus = ScriptLoadInfo::Cached;
 
-  mRunnable->DataReceivedFromCache(mIndex, aString, aStringLen);
+  mRunnable->DataReceivedFromCache(mIndex, aString, aStringLen, mSecurityInfo);
   return NS_OK;
 }
 
  class ChannelGetterRunnable final : public nsRunnable
 {
   WorkerPrivate* mParentWorker;
   nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
   const nsAString& mScriptURL;
--- a/dom/workers/ServiceWorkerEvents.cpp
+++ b/dom/workers/ServiceWorkerEvents.cpp
@@ -13,16 +13,17 @@
 #include "nsContentUtils.h"
 #include "nsComponentManagerUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "nsStreamUtils.h"
 #include "nsNetCID.h"
 #include "nsSerializationHelper.h"
 #include "nsQueryObject.h"
 
+#include "mozilla/Preferences.h"
 #include "mozilla/dom/FetchEventBinding.h"
 #include "mozilla/dom/PromiseNativeHandler.h"
 #include "mozilla/dom/Request.h"
 #include "mozilla/dom/Response.h"
 #include "mozilla/dom/WorkerScope.h"
 #include "mozilla/dom/workers/bindings/ServiceWorker.h"
 
 #include "WorkerPrivate.h"
@@ -92,31 +93,40 @@ public:
     return NS_OK;
   }
 };
 
 class FinishResponse final : public nsRunnable
 {
   nsMainThreadPtrHandle<nsIInterceptedChannel> mChannel;
   nsRefPtr<InternalResponse> mInternalResponse;
+  nsCString mWorkerSecurityInfo;
 public:
   FinishResponse(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
-                 InternalResponse* aInternalResponse)
+                 InternalResponse* aInternalResponse,
+                 const nsCString& aWorkerSecurityInfo)
     : mChannel(aChannel)
     , mInternalResponse(aInternalResponse)
+    , mWorkerSecurityInfo(aWorkerSecurityInfo)
   {
   }
 
   NS_IMETHOD
   Run()
   {
     AssertIsOnMainThread();
 
     nsCOMPtr<nsISupports> infoObj;
-    nsresult rv = NS_DeserializeObject(mInternalResponse->GetSecurityInfo(), getter_AddRefs(infoObj));
+    nsAutoCString securityInfo(mInternalResponse->GetSecurityInfo());
+    if (securityInfo.IsEmpty()) {
+      // We are dealing with a synthesized response here, so fall back to the
+      // security info for the worker script.
+      securityInfo = mWorkerSecurityInfo;
+    }
+    nsresult rv = NS_DeserializeObject(securityInfo, getter_AddRefs(infoObj));
     if (NS_SUCCEEDED(rv)) {
       rv = mChannel->SetSecurityInfo(infoObj);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     }
 
     mChannel->SynthesizeStatus(mInternalResponse->GetStatus(), mInternalResponse->GetStatusText());
@@ -154,31 +164,36 @@ public:
 
   void CancelRequest();
 };
 
 struct RespondWithClosure
 {
   nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
   nsRefPtr<InternalResponse> mInternalResponse;
+  nsCString mWorkerSecurityInfo;
 
   RespondWithClosure(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
-                     InternalResponse* aInternalResponse)
+                     InternalResponse* aInternalResponse,
+                     const nsCString& aWorkerSecurityInfo)
     : mInterceptedChannel(aChannel)
     , mInternalResponse(aInternalResponse)
+    , mWorkerSecurityInfo(aWorkerSecurityInfo)
   {
   }
 };
 
 void RespondWithCopyComplete(void* aClosure, nsresult aStatus)
 {
   nsAutoPtr<RespondWithClosure> data(static_cast<RespondWithClosure*>(aClosure));
   nsCOMPtr<nsIRunnable> event;
   if (NS_SUCCEEDED(aStatus)) {
-    event = new FinishResponse(data->mInterceptedChannel, data->mInternalResponse);
+    event = new FinishResponse(data->mInterceptedChannel,
+                               data->mInternalResponse,
+                               data->mWorkerSecurityInfo);
   } else {
     event = new CancelChannelRunnable(data->mInterceptedChannel);
   }
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(event)));
 }
 
 class MOZ_STACK_CLASS AutoCancel
 {
@@ -229,17 +244,22 @@ RespondWithHandler::ResolvedCallback(JSC
     return;
   }
 
   nsRefPtr<InternalResponse> ir = response->GetInternalResponse();
   if (NS_WARN_IF(!ir)) {
     return;
   }
 
-  nsAutoPtr<RespondWithClosure> closure(new RespondWithClosure(mInterceptedChannel, ir));
+  WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+  MOZ_ASSERT(worker);
+  worker->AssertIsOnWorkerThread();
+
+  nsAutoPtr<RespondWithClosure> closure(
+      new RespondWithClosure(mInterceptedChannel, ir, worker->GetSecurityInfo()));
   nsCOMPtr<nsIInputStream> body;
   response->GetBody(getter_AddRefs(body));
   // Errors and redirects may not have a body.
   if (body) {
     response->SetBodyUsed();
 
     nsCOMPtr<nsIOutputStream> responseBody;
     rv = mInterceptedChannel->GetResponseBody(getter_AddRefs(responseBody));
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -27,16 +27,17 @@
 #include "nsITimer.h"
 #include "nsIURI.h"
 #include "nsIURL.h"
 #include "nsIWeakReferenceUtils.h"
 #include "nsIWorkerDebugger.h"
 #include "nsIXPConnect.h"
 #include "nsPerformance.h"
 #include "nsPIDOMWindow.h"
+#include "nsSerializationHelper.h"
 
 #include <algorithm>
 #include "jsfriendapi.h"
 #include "js/MemoryMetrics.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ContentEvents.h"
 #include "mozilla/EventDispatcher.h"
@@ -4077,16 +4078,27 @@ WorkerPrivateParent<Derived>::SetPrincip
 
   mLoadInfo.mPrincipalInfo = new PrincipalInfo();
 
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
     PrincipalToPrincipalInfo(aPrincipal, mLoadInfo.mPrincipalInfo)));
 }
 
 template <class Derived>
+void
+WorkerPrivateParent<Derived>::SetSecurityInfo(nsISerializable* aSerializable)
+{
+  MOZ_ASSERT(IsServiceWorker());
+  AssertIsOnMainThread();
+  nsAutoCString securityInfo;
+  NS_SerializeToString(aSerializable, securityInfo);
+  SetSecurityInfo(securityInfo);
+}
+
+template <class Derived>
 JSContext*
 WorkerPrivateParent<Derived>::ParentJSContext() const
 {
   AssertIsOnParentThread();
 
   if (mParent) {
     return mParent->GetJSContext();
   }
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -32,16 +32,17 @@
 #include "WorkerFeature.h"
 
 class JSAutoStructuredCloneBuffer;
 class nsIChannel;
 class nsIDocument;
 class nsIEventTarget;
 class nsIPrincipal;
 class nsIScriptContext;
+class nsISerializable;
 class nsIThread;
 class nsIThreadInternal;
 class nsITimer;
 class nsIURI;
 
 namespace JS {
 struct RuntimeStats;
 }
@@ -486,16 +487,35 @@ public:
   const nsString&
   ServiceWorkerCacheName() const
   {
     MOZ_ASSERT(IsServiceWorker());
     AssertIsOnMainThread();
     return mLoadInfo.mServiceWorkerCacheName;
   }
 
+  const nsCString&
+  GetSecurityInfo() const
+  {
+    MOZ_ASSERT(IsServiceWorker());
+    return mLoadInfo.mSecurityInfo;
+  }
+
+  void
+  SetSecurityInfo(const nsCString& aSecurityInfo)
+  {
+    MOZ_ASSERT(IsServiceWorker());
+    AssertIsOnMainThread();
+    MOZ_ASSERT(mLoadInfo.mSecurityInfo.IsEmpty());
+    mLoadInfo.mSecurityInfo = aSecurityInfo;
+  }
+
+  void
+  SetSecurityInfo(nsISerializable* aSerializable);
+
   // This is used to handle importScripts(). When the worker is first loaded
   // and executed, it happens in a sync loop. At this point it sets
   // mLoadingWorkerScript to true. importScripts() calls that occur during the
   // execution run in nested sync loops and so this continues to return true,
   // leading to these scripts being cached offline.
   // mLoadingWorkerScript is set to false when the top level loop ends.
   // importScripts() in function calls or event handlers are always fetched
   // from the network.
--- a/dom/workers/Workers.h
+++ b/dom/workers/Workers.h
@@ -238,16 +238,18 @@ struct WorkerLoadInfo
   // Only set if we have a custom overriden load group
   nsRefPtr<InterfaceRequestor> mInterfaceRequestor;
 
   nsAutoPtr<mozilla::ipc::PrincipalInfo> mPrincipalInfo;
   nsCString mDomain;
 
   nsString mServiceWorkerCacheName;
 
+  nsCString mSecurityInfo;
+
   uint64_t mWindowID;
 
   bool mFromWindow;
   bool mEvalAllowed;
   bool mReportCSPViolations;
   bool mXHRParamsAllowed;
   bool mPrincipalIsSystem;
   bool mIsInPrivilegedApp;
--- a/dom/workers/test/serviceworkers/fetch/https/https_test.js
+++ b/dom/workers/test/serviceworkers/fetch/https/https_test.js
@@ -2,10 +2,13 @@ self.addEventListener("install", functio
   event.waitUntil(caches.open("cache").then(function(cache) {
     return cache.add("index.html");
   }));
 });
 
 self.addEventListener("fetch", function(event) {
   if (event.request.url.indexOf("index.html") >= 0) {
     event.respondWith(caches.match(event.request));
+  } else if (event.request.url.indexOf("synth.html") >= 0) {
+    event.respondWith(new Response('<!DOCTYPE html><script>window.parent.postMessage({status: "done-synth"}, "*");</script>',
+                                   {headers:{"Content-Type": "text/html"}}));
   }
 });
--- a/dom/workers/test/serviceworkers/mochitest.ini
+++ b/dom/workers/test/serviceworkers/mochitest.ini
@@ -77,16 +77,17 @@ support-files =
   periodic/register.html
   periodic/unregister.html
 
 [test_unregister.html]
 [test_installation_simple.html]
 [test_fetch_event.html]
 [test_https_fetch.html]
 [test_https_fetch_cloned_response.html]
+[test_https_synth_fetch_from_cached_sw.html]
 [test_match_all.html]
 [test_match_all_advanced.html]
 [test_install_event.html]
 [test_navigator.html]
 [test_scopes.html]
 [test_controller.html]
 [test_workerUnregister.html]
 [test_workerUpdate.html]
--- a/dom/workers/test/serviceworkers/test_https_fetch.html
+++ b/dom/workers/test/serviceworkers/test_https_fetch.html
@@ -26,16 +26,18 @@
       if (e.data.status == "ok") {
         ok(e.data.result, e.data.message);
       } else if (e.data.status == "registrationdone") {
         ios = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]
                            .getService(SpecialPowers.Ci.nsIIOService);
         ios.offline = true;
         iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/https/index.html";
       } else if (e.data.status == "done") {
+        iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/https/synth.html";
+      } else if (e.data.status == "done-synth") {
         ios.offline = false;
         iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/https/unregister.html";
       } else if (e.data.status == "unregistrationdone") {
         window.onmessage = null;
         ok(true, "Test finished successfully");
         SimpleTest.finish();
       }
     };
copy from dom/workers/test/serviceworkers/test_https_fetch.html
copy to dom/workers/test/serviceworkers/test_https_synth_fetch_from_cached_sw.html
--- a/dom/workers/test/serviceworkers/test_https_fetch.html
+++ b/dom/workers/test/serviceworkers/test_https_synth_fetch_from_cached_sw.html
@@ -1,22 +1,22 @@
 <!--
   Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/
 -->
 <!DOCTYPE HTML>
 <html>
 <head>
-  <title>Bug 1133763 - test fetch event in HTTPS origins</title>
+  <title>Bug 1156847 - test fetch event generating a synthesized response in HTTPS origins from a cached SW</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
-<div id="content" style="display: none">
+<div id="content" tyle="display: none">
 <iframe></iframe>
 </div>
 <pre id="test"></pre>
 <script class="testbody" type="text/javascript">
 
   var iframe;
   function runTest() {
     iframe = document.querySelector("iframe");
@@ -24,18 +24,31 @@
     var ios;
     window.onmessage = function(e) {
       if (e.data.status == "ok") {
         ok(e.data.result, e.data.message);
       } else if (e.data.status == "registrationdone") {
         ios = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]
                            .getService(SpecialPowers.Ci.nsIIOService);
         ios.offline = true;
-        iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/https/index.html";
-      } else if (e.data.status == "done") {
+
+        // In order to load synth.html from a cached service worker, we first
+        // remove the existing window that is keeping the service worker alive,
+        // and do a GC to ensure that the SW is destroyed.  This way, when we
+        // load synth.html for the second time, we will first recreate the
+        // service worker from the cache.  This is intended to test that we
+        // properly store and retrieve the security info from the cache.
+        iframe.parentNode.removeChild(iframe);
+        iframe = null;
+        SpecialPowers.exactGC(window, function() {
+          iframe = document.createElement("iframe");
+          iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/https/synth.html";
+          document.body.appendChild(iframe);
+        });
+      } else if (e.data.status == "done-synth") {
         ios.offline = false;
         iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/https/unregister.html";
       } else if (e.data.status == "unregistrationdone") {
         window.onmessage = null;
         ok(true, "Test finished successfully");
         SimpleTest.finish();
       }
     };
--- a/gfx/2d/Logging.h
+++ b/gfx/2d/Logging.h
@@ -215,16 +215,19 @@ public:
 };
 
 class NoLog
 {
 public:
   NoLog() {}
   ~NoLog() {}
 
+  // No-op
+  MOZ_IMPLICIT NoLog(const NoLog&) {}
+
   template<typename T>
   NoLog &operator <<(const T &aLogText) { return *this; }
 };
 
 enum class LogOptions : int {
   NoNewline = 0x01,
   AutoPrefix = 0x02,
   AssertOnCall = 0x04
@@ -248,45 +251,31 @@ public:
     return (int(LogOptions::AutoPrefix) |
             (aWithAssert ? int(LogOptions::AssertOnCall) : 0));
   }
 
   // Note that we're calling BasicLogger::ShouldOutputMessage, rather than
   // Logger::ShouldOutputMessage.  Since we currently don't have a different
   // version of that method for different loggers, this is OK. Once we do,
   // change BasicLogger::ShouldOutputMessage to Logger::ShouldOutputMessage.
-  explicit Log(int aOptions = Log::DefaultOptions(L == LOG_CRITICAL))
-    : mOptions(aOptions)
-    , mLogIt(BasicLogger::ShouldOutputMessage(L))
-  {
-    if (mLogIt && AutoPrefix()) {
-      if (mOptions & int(LogOptions::AssertOnCall)) {
-        mMessage << "[GFX" << L << "]: ";
-      } else {
-        mMessage << "[GFX" << L << "-]: ";
-      }
-    }
+  explicit Log(int aOptions = Log::DefaultOptions(L == LOG_CRITICAL)) {
+    Init(aOptions, BasicLogger::ShouldOutputMessage(L));
   }
+
   ~Log() {
     Flush();
   }
 
   void Flush() {
     if (MOZ_LIKELY(!LogIt())) return;
 
     std::string str = mMessage.str();
     if (!str.empty()) {
       WriteLog(str);
     }
-    if (AutoPrefix()) {
-      mMessage.str("[GFX");
-      mMessage << L << "]: ";
-    } else {
-      mMessage.str("");
-    }
     mMessage.clear();
   }
 
   Log &operator <<(char aChar) {
     if (MOZ_UNLIKELY(LogIt())) {
       mMessage << aChar;
     }
     return *this;
@@ -473,18 +462,34 @@ public:
     }
     return *this;
   }
 
   inline bool LogIt() const { return mLogIt; }
   inline bool NoNewline() const { return mOptions & int(LogOptions::NoNewline); }
   inline bool AutoPrefix() const { return mOptions & int(LogOptions::AutoPrefix); }
 
+  // We do not want this version to do any work, and stringstream can't be
+  // copied anyway.  It does come in handy for the "Once" macro defined below.
+  MOZ_IMPLICIT Log(const Log& log) { Init(log.mOptions, false); }
 
 private:
+  // Initialization common to two constructors
+  void Init(int aOptions, bool aLogIt) {
+    mOptions = aOptions;
+    mLogIt = aLogIt;
+    if (mLogIt && AutoPrefix()) {
+      if (mOptions & int(LogOptions::AssertOnCall)) {
+        mMessage << "[GFX" << L << "]: ";
+      } else {
+        mMessage << "[GFX" << L << "-]: ";
+      }
+    }
+  }
+
   void WriteLog(const std::string &aString) {
     if (MOZ_UNLIKELY(LogIt())) {
       Logger::OutputMessage(aString, L, NoNewline());
       if (mOptions & int(LogOptions::AssertOnCall)) {
         MOZ_ASSERT(false, "An assert from the graphics logger");
       }
     }
   }
@@ -493,30 +498,50 @@ private:
   int mOptions;
   bool mLogIt;
 };
 
 typedef Log<LOG_DEBUG> DebugLog;
 typedef Log<LOG_WARNING> WarningLog;
 typedef Log<LOG_CRITICAL, CriticalLogger> CriticalLog;
 
+// Macro to glue names to get us less chance of name clashing.
+#if defined GFX_LOGGING_GLUE1 || defined GFX_LOGGING_GLUE
+#error "Clash of the macro GFX_LOGGING_GLUE1 or GFX_LOGGING_GLUE"
+#endif
+#define GFX_LOGGING_GLUE1(x, y)  x##y
+#define GFX_LOGGING_GLUE(x, y)   GFX_LOGGING_GLUE1(x, y)
+
+// This log goes into crash reports, use with care.
+#define gfxCriticalError mozilla::gfx::CriticalLog
+#define gfxCriticalErrorOnce static gfxCriticalError GFX_LOGGING_GLUE(sOnceAtLine,__LINE__) = gfxCriticalError
+
+// The "once" versions will only trigger the first time through. You can do this:
+// gfxCriticalErrorOnce() << "This message only shows up once;
+// instead of the usual:
+// static bool firstTime = true;
+// if (firstTime) {
+//   firstTime = false;
+//   gfxCriticalError() << "This message only shows up once;
+// }
 #ifdef GFX_LOG_DEBUG
 #define gfxDebug mozilla::gfx::DebugLog
+#define gfxDebugOnce static gfxDebug GFX_LOGGING_GLUE(sOnceAtLine,__LINE__) = gfxDebug
 #else
 #define gfxDebug if (1) ; else mozilla::gfx::NoLog
+#define gfxDebugOnce if (1) ; else mozilla::gfx::NoLog
 #endif
 #ifdef GFX_LOG_WARNING
 #define gfxWarning mozilla::gfx::WarningLog
+#define gfxWarningOnce static gfxWarning GFX_LOGGING_GLUE(sOnceAtLine,__LINE__) = gfxWarning
 #else
 #define gfxWarning if (1) ; else mozilla::gfx::NoLog
+#define gfxWarningOnce if (1) ; else mozilla::gfx::NoLog
 #endif
 
-// This log goes into crash reports, use with care.
-#define gfxCriticalError mozilla::gfx::CriticalLog
-
 // See nsDebug.h and the NS_WARN_IF macro
 
 #ifdef __cplusplus
  // For now, have MOZ2D_ERROR_IF available in debug and non-debug builds
 inline bool MOZ2D_error_if_impl(bool aCondition, const char* aExpr,
                                 const char* aFile, int32_t aLine)
 {
   if (MOZ_UNLIKELY(aCondition)) {
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -8,17 +8,17 @@
 #include <stdint.h>                     // for uint32_t
 #include "apz/src/AsyncPanZoomController.h"
 #include "FrameMetrics.h"               // for FrameMetrics
 #include "LayerManagerComposite.h"      // for LayerManagerComposite, etc
 #include "Layers.h"                     // for Layer, ContainerLayer, etc
 #include "gfxPoint.h"                   // for gfxPoint, gfxSize
 #include "mozilla/StyleAnimationValue.h" // for StyleAnimationValue, etc
 #include "mozilla/WidgetUtils.h"        // for ComputeTransformForRotation
-#include "mozilla/dom/KeyframeEffect.h" // for KeyframeEffectReadonly
+#include "mozilla/dom/KeyframeEffect.h" // for KeyframeEffectReadOnly
 #include "mozilla/gfx/BaseRect.h"       // for BaseRect
 #include "mozilla/gfx/Point.h"          // for RoundedToInt, PointTyped
 #include "mozilla/gfx/Rect.h"           // for RoundedToInt, RectTyped
 #include "mozilla/gfx/ScaleFactor.h"    // for ScaleFactor
 #include "mozilla/layers/Compositor.h"  // for Compositor
 #include "mozilla/layers/CompositorParent.h" // for CompositorParent, etc
 #include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper
 #include "nsCoord.h"                    // for NSAppUnitsToFloatPixels, etc
@@ -469,17 +469,17 @@ SampleAnimations(Layer* aLayer, TimeStam
     timing.mDirection = animation.direction();
     // Animations typically only run on the compositor during their active
     // interval but if we end up sampling them outside that range (for
     // example, while they are waiting to be removed) we currently just
     // assume that we should fill.
     timing.mFillMode = NS_STYLE_ANIMATION_FILL_MODE_BOTH;
 
     ComputedTiming computedTiming =
-      dom::KeyframeEffectReadonly::GetComputedTimingAt(
+      dom::KeyframeEffectReadOnly::GetComputedTimingAt(
         Nullable<TimeDuration>(elapsedDuration), timing);
 
     MOZ_ASSERT(0.0 <= computedTiming.mTimeFraction &&
                computedTiming.mTimeFraction <= 1.0,
                "time fraction should be in [0-1]");
 
     int segmentIndex = 0;
     AnimationSegment* segment = animation.segments().Elements();
--- a/hal/gonk/GonkHal.cpp
+++ b/hal/gonk/GonkHal.cpp
@@ -981,17 +981,18 @@ EnableScreenConfigurationNotifications()
 void
 DisableScreenConfigurationNotifications()
 {
 }
 
 void
 GetCurrentScreenConfiguration(hal::ScreenConfiguration* aScreenConfiguration)
 {
-  *aScreenConfiguration = nsScreenGonk::GetConfiguration();
+  nsRefPtr<nsScreenGonk> screen = nsScreenManagerGonk::GetPrimaryScreen();
+  *aScreenConfiguration = screen->GetConfiguration();
 }
 
 bool
 LockScreenOrientation(const dom::ScreenOrientation& aOrientation)
 {
   return OrientationObserver::GetInstance()->LockScreenOrientation(aOrientation);
 }
 
--- a/image/src/DynamicImage.cpp
+++ b/image/src/DynamicImage.cpp
@@ -19,22 +19,16 @@ using namespace mozilla::gfx;
 using mozilla::layers::LayerManager;
 using mozilla::layers::ImageContainer;
 
 namespace mozilla {
 namespace image {
 
 // Inherited methods from Image.
 
-nsresult
-DynamicImage::Init(const char* aMimeType, uint32_t aFlags)
-{
-  return NS_OK;
-}
-
 already_AddRefed<ProgressTracker>
 DynamicImage::GetProgressTracker()
 {
   return nullptr;
 }
 
 size_t
 DynamicImage::SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const
--- a/image/src/DynamicImage.h
+++ b/image/src/DynamicImage.h
@@ -26,18 +26,16 @@ public:
 
   explicit DynamicImage(gfxDrawable* aDrawable)
     : mDrawable(aDrawable)
   {
     MOZ_ASSERT(aDrawable, "Must have a gfxDrawable to wrap");
   }
 
   // Inherited methods from Image.
-  virtual nsresult Init(const char* aMimeType, uint32_t aFlags) override;
-
   virtual already_AddRefed<ProgressTracker> GetProgressTracker() override;
   virtual size_t SizeOfSourceWithComputedFallback(
                                  MallocSizeOf aMallocSizeOf) const override;
   virtual void CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
                                      MallocSizeOf aMallocSizeOf) const override;
 
   virtual void IncrementAnimationConsumers() override;
   virtual void DecrementAnimationConsumers() override;
--- a/image/src/Image.cpp
+++ b/image/src/Image.cpp
@@ -58,16 +58,22 @@ ImageResource::ImageResource(ImageURL* a
   mInnerWindowId(0),
   mAnimationConsumers(0),
   mAnimationMode(kNormalAnimMode),
   mInitialized(false),
   mAnimating(false),
   mError(false)
 { }
 
+ImageResource::~ImageResource()
+{
+  // Ask our ProgressTracker to drop its weak reference to us.
+  mProgressTracker->ResetImage();
+}
+
 // Translates a mimetype into a concrete decoder
 Image::eDecoderType
 Image::GetDecoderType(const char* aMimeType)
 {
   // By default we don't know
   eDecoderType rv = eDecoderType_unknown;
 
   // PNG
--- a/image/src/Image.h
+++ b/image/src/Image.h
@@ -168,25 +168,16 @@ public:
    */
   static const uint32_t INIT_FLAG_NONE                     = 0x0;
   static const uint32_t INIT_FLAG_DISCARDABLE              = 0x1;
   static const uint32_t INIT_FLAG_DECODE_ONLY_ON_DRAW      = 0x2;
   static const uint32_t INIT_FLAG_DECODE_IMMEDIATELY       = 0x4;
   static const uint32_t INIT_FLAG_TRANSIENT                = 0x8;
   static const uint32_t INIT_FLAG_DOWNSCALE_DURING_DECODE  = 0x10;
 
-  /**
-   * Creates a new image container.
-   *
-   * @param aMimeType The mimetype of the image.
-   * @param aFlags Initialization flags of the INIT_FLAG_* variety.
-   */
-  virtual nsresult Init(const char* aMimeType,
-                        uint32_t aFlags) = 0;
-
   virtual already_AddRefed<ProgressTracker> GetProgressTracker() = 0;
   virtual void SetProgressTracker(ProgressTracker* aProgressTracker) {}
 
   /**
    * The size, in bytes, occupied by the compressed source data of the image.
    * If MallocSizeOf does not work on this platform, uses a fallback approach to
    * ensure that something reasonable is always returned.
    */
@@ -293,16 +284,17 @@ public:
   /*
    * Returns a non-AddRefed pointer to the URI associated with this image.
    * Illegal to use off-main-thread.
    */
   virtual ImageURL* GetURI() override { return mURI.get(); }
 
 protected:
   explicit ImageResource(ImageURL* aURI);
+  ~ImageResource();
 
   // Shared functionality for implementors of imgIContainer. Every
   // implementation of attribute animationMode should forward here.
   nsresult GetAnimationModeInternal(uint16_t* aAnimationMode);
   nsresult SetAnimationModeInternal(uint16_t aAnimationMode);
 
   /**
    * Helper for RequestRefresh.
--- a/image/src/ImageFactory.cpp
+++ b/image/src/ImageFactory.cpp
@@ -9,16 +9,17 @@
 #include "mozilla/Likely.h"
 
 #include "nsIHttpChannel.h"
 #include "nsIFileChannel.h"
 #include "nsIFile.h"
 #include "nsMimeTypes.h"
 #include "nsIRequest.h"
 
+#include "MultipartImage.h"
 #include "RasterImage.h"
 #include "VectorImage.h"
 #include "Image.h"
 #include "nsMediaFragmentURIParser.h"
 #include "nsContentUtils.h"
 #include "nsIScriptSecurityManager.h"
 
 #include "ImageFactory.h"
@@ -144,22 +145,42 @@ BadImage(nsRefPtr<T>& image)
 
 /* static */ already_AddRefed<Image>
 ImageFactory::CreateAnonymousImage(const nsCString& aMimeType)
 {
   nsresult rv;
 
   nsRefPtr<RasterImage> newImage = new RasterImage();
 
+  nsRefPtr<ProgressTracker> newTracker = new ProgressTracker();
+  newTracker->SetImage(newImage);
+  newImage->SetProgressTracker(newTracker);
+
   rv = newImage->Init(aMimeType.get(), Image::INIT_FLAG_NONE);
   NS_ENSURE_SUCCESS(rv, BadImage(newImage));
 
   return newImage.forget();
 }
 
+/* static */ already_AddRefed<MultipartImage>
+ImageFactory::CreateMultipartImage(Image* aFirstPart,
+                                   ProgressTracker* aProgressTracker)
+{
+  MOZ_ASSERT(aFirstPart);
+  MOZ_ASSERT(aProgressTracker);
+
+  nsRefPtr<MultipartImage> newImage = new MultipartImage(aFirstPart);
+  aProgressTracker->SetImage(newImage);
+  newImage->SetProgressTracker(aProgressTracker);
+
+  newImage->Init();
+
+  return newImage.forget();
+}
+
 int32_t
 SaturateToInt32(int64_t val)
 {
   if (val > INT_MAX) {
     return INT_MAX;
   }
   if (val < INT_MIN) {
     return INT_MIN;
@@ -201,19 +222,23 @@ GetContentSize(nsIRequest* aRequest)
 /* static */ already_AddRefed<Image>
 ImageFactory::CreateRasterImage(nsIRequest* aRequest,
                                 ProgressTracker* aProgressTracker,
                                 const nsCString& aMimeType,
                                 ImageURL* aURI,
                                 uint32_t aImageFlags,
                                 uint32_t aInnerWindowId)
 {
+  MOZ_ASSERT(aProgressTracker);
+
   nsresult rv;
 
-  nsRefPtr<RasterImage> newImage = new RasterImage(aProgressTracker, aURI);
+  nsRefPtr<RasterImage> newImage = new RasterImage(aURI);
+  aProgressTracker->SetImage(newImage);
+  newImage->SetProgressTracker(aProgressTracker);
 
   rv = newImage->Init(aMimeType.get(), aImageFlags);
   NS_ENSURE_SUCCESS(rv, BadImage(newImage));
 
   newImage->SetInnerWindowID(aInnerWindowId);
 
   uint32_t len = GetContentSize(aRequest);
 
@@ -263,19 +288,23 @@ ImageFactory::CreateRasterImage(nsIReque
 /* static */ already_AddRefed<Image>
 ImageFactory::CreateVectorImage(nsIRequest* aRequest,
                                 ProgressTracker* aProgressTracker,
                                 const nsCString& aMimeType,
                                 ImageURL* aURI,
                                 uint32_t aImageFlags,
                                 uint32_t aInnerWindowId)
 {
+  MOZ_ASSERT(aProgressTracker);
+
   nsresult rv;
 
-  nsRefPtr<VectorImage> newImage = new VectorImage(aProgressTracker, aURI);
+  nsRefPtr<VectorImage> newImage = new VectorImage(aURI);
+  aProgressTracker->SetImage(newImage);
+  newImage->SetProgressTracker(aProgressTracker);
 
   rv = newImage->Init(aMimeType.get(), aImageFlags);
   NS_ENSURE_SUCCESS(rv, BadImage(newImage));
 
   newImage->SetInnerWindowID(aInnerWindowId);
 
   rv = newImage->OnStartRequest(aRequest, nullptr);
   NS_ENSURE_SUCCESS(rv, BadImage(newImage));
--- a/image/src/ImageFactory.h
+++ b/image/src/ImageFactory.h
@@ -13,16 +13,17 @@
 class nsCString;
 class nsIRequest;
 
 namespace mozilla {
 namespace image {
 
 class Image;
 class ImageURL;
+class MultipartImage;
 class ProgressTracker;
 
 class ImageFactory
 {
 public:
   /**
    * Registers vars with Preferences. Should only be called on the main thread.
    */
@@ -49,16 +50,28 @@ public:
    * Creates a new image which isn't associated with a URI or loaded through
    * the usual image loading mechanism.
    *
    * @param aMimeType      The mimetype of the image.
    */
   static already_AddRefed<Image>
   CreateAnonymousImage(const nsCString& aMimeType);
 
+  /**
+   * Creates a new multipart/x-mixed-replace image wrapper, and initializes it
+   * with the first part. Subsequent parts should be passed to the existing
+   * MultipartImage via MultipartImage::BeginTransitionToPart().
+   *
+   * @param aFirstPart       An image containing the first part of the multipart
+   *                         stream.
+   * @param aProgressTracker A progress tracker for the multipart image.
+   */
+  static already_AddRefed<MultipartImage>
+  CreateMultipartImage(Image* aFirstPart, ProgressTracker* aProgressTracker);
+
 private:
   // Factory functions that create specific types of image containers.
   static already_AddRefed<Image>
   CreateRasterImage(nsIRequest* aRequest,
                     ProgressTracker* aProgressTracker,
                     const nsCString& aMimeType,
                     ImageURL* aURI,
                     uint32_t aImageFlags,
--- a/image/src/ImageWrapper.cpp
+++ b/image/src/ImageWrapper.cpp
@@ -16,22 +16,16 @@ using gfx::DataSourceSurface;
 using gfx::SourceSurface;
 using layers::LayerManager;
 using layers::ImageContainer;
 
 namespace image {
 
 // Inherited methods from Image.
 
-nsresult
-ImageWrapper::Init(const char* aMimeType, uint32_t aFlags)
-{
-  return mInnerImage->Init(aMimeType, aFlags);
-}
-
 already_AddRefed<ProgressTracker>
 ImageWrapper::GetProgressTracker()
 {
   return mInnerImage->GetProgressTracker();
 }
 
 size_t
 ImageWrapper::SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const
--- a/image/src/ImageWrapper.h
+++ b/image/src/ImageWrapper.h
@@ -17,18 +17,16 @@ namespace image {
  */
 class ImageWrapper : public Image
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_IMGICONTAINER
 
   // Inherited methods from Image.
-  virtual nsresult Init(const char* aMimeType, uint32_t aFlags) override;
-
   virtual already_AddRefed<ProgressTracker> GetProgressTracker() override;
 
   virtual size_t
     SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const override;
   virtual void CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
                                      MallocSizeOf aMallocSizeOf) const override;
 
   virtual void IncrementAnimationConsumers() override;
--- a/image/src/MultipartImage.cpp
+++ b/image/src/MultipartImage.cpp
@@ -98,33 +98,42 @@ private:
   nsRefPtr<Image> mImage;
 };
 
 
 ///////////////////////////////////////////////////////////////////////////////
 // Implementation
 ///////////////////////////////////////////////////////////////////////////////
 
-MultipartImage::MultipartImage(Image* aImage, ProgressTracker* aTracker)
-  : ImageWrapper(aImage)
+MultipartImage::MultipartImage(Image* aFirstPart)
+  : ImageWrapper(aFirstPart)
   , mDeferNotifications(false)
 {
-  MOZ_ASSERT(aTracker);
-  mProgressTrackerInit = new ProgressTrackerInit(this, aTracker);
   mNextPartObserver = new NextPartObserver(this);
+}
+
+void
+MultipartImage::Init()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mTracker, "Should've called SetProgressTracker() by now");
 
   // Start observing the first part.
   nsRefPtr<ProgressTracker> firstPartTracker =
     InnerImage()->GetProgressTracker();
   firstPartTracker->AddObserver(this);
   InnerImage()->RequestDecode();
   InnerImage()->IncrementAnimationConsumers();
 }
 
-MultipartImage::~MultipartImage() { }
+MultipartImage::~MultipartImage()
+{
+  // Ask our ProgressTracker to drop its weak reference to us.
+  mTracker->ResetImage();
+}
 
 NS_IMPL_QUERY_INTERFACE_INHERITED0(MultipartImage, ImageWrapper)
 NS_IMPL_ADDREF(MultipartImage)
 NS_IMPL_RELEASE(MultipartImage)
 
 void
 MultipartImage::BeginTransitionToPart(Image* aNextPart)
 {
@@ -141,32 +150,42 @@ MultipartImage::BeginTransitionToPart(Im
 
   // Start observing the next part; we'll complete the transition when
   // NextPartObserver calls FinishTransition.
   mNextPartObserver->BeginObserving(mNextPart);
   mNextPart->RequestDecode();
   mNextPart->IncrementAnimationConsumers();
 }
 
-void MultipartImage::FinishTransition()
+static Progress
+FilterProgress(Progress aProgress)
+{
+  // Filter out onload blocking notifications, since we don't want to block
+  // onload for multipart images.
+  return aProgress & ~(FLAG_ONLOAD_BLOCKED | FLAG_ONLOAD_UNBLOCKED);
+}
+
+void
+MultipartImage::FinishTransition()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mNextPart, "Should have a next part here");
 
   nsRefPtr<ProgressTracker> newCurrentPartTracker =
     mNextPart->GetProgressTracker();
   if (newCurrentPartTracker->GetProgress() & FLAG_HAS_ERROR) {
     // This frame has an error; drop it.
     mNextPart = nullptr;
 
     // We still need to notify, though.
     mTracker->ResetForNewRequest();
     nsRefPtr<ProgressTracker> currentPartTracker =
       InnerImage()->GetProgressTracker();
-    mTracker->SyncNotifyProgress(currentPartTracker->GetProgress());
+    mTracker
+      ->SyncNotifyProgress(FilterProgress(currentPartTracker->GetProgress()));
 
     return;
   }
 
   // Stop observing the current part.
   {
     nsRefPtr<ProgressTracker> currentPartTracker =
       InnerImage()->GetProgressTracker();
@@ -176,18 +195,19 @@ void MultipartImage::FinishTransition()
   // Make the next part become the current part.
   mTracker->ResetForNewRequest();
   SetInnerImage(mNextPart);
   mNextPart = nullptr;
   newCurrentPartTracker->AddObserver(this);
 
   // Finally, send all the notifications for the new current part and send a
   // FRAME_UPDATE notification so that observers know to redraw.
-  mTracker->SyncNotifyProgress(newCurrentPartTracker->GetProgress(),
-                               GetMaxSizedIntRect());
+  mTracker
+    ->SyncNotifyProgress(FilterProgress(newCurrentPartTracker->GetProgress()),
+                         GetMaxSizedIntRect());
 }
 
 already_AddRefed<imgIContainer>
 MultipartImage::Unwrap()
 {
   // Although we wrap another image, we don't allow callers to unwrap as. As far
   // as external code is concerned, MultipartImage is atomic.
   nsCOMPtr<imgIContainer> image = this;
--- a/image/src/MultipartImage.h
+++ b/image/src/MultipartImage.h
@@ -22,18 +22,16 @@ class NextPartObserver;
 class MultipartImage
   : public ImageWrapper
   , public IProgressObserver
 {
 public:
   MOZ_DECLARE_REFCOUNTED_TYPENAME(MultipartImage)
   NS_DECL_ISUPPORTS
 
-  MultipartImage(Image* aImage, ProgressTracker* aTracker);
-
   void BeginTransitionToPart(Image* aNextPart);
 
   // Overridden ImageWrapper methods:
   virtual already_AddRefed<imgIContainer> Unwrap() override;
   virtual already_AddRefed<ProgressTracker> GetProgressTracker() override;
   virtual void SetProgressTracker(ProgressTracker* aTracker) override;
   virtual nsresult OnImageDataAvailable(nsIRequest* aRequest,
                                         nsISupports* aContext,
@@ -68,22 +66,25 @@ public:
   // methods to do nothing.
   virtual void BlockOnload() override { }
   virtual void UnblockOnload() override { }
 
 protected:
   virtual ~MultipartImage();
 
 private:
+  friend class ImageFactory;
   friend class NextPartObserver;
 
+  explicit MultipartImage(Image* aFirstPart);
+  void Init();
+
   void FinishTransition();
 
   nsRefPtr<ProgressTracker> mTracker;
-  nsAutoPtr<ProgressTrackerInit> mProgressTrackerInit;
   nsRefPtr<NextPartObserver> mNextPartObserver;
   nsRefPtr<Image> mNextPart;
   bool mDeferNotifications : 1;
 };
 
 } // namespace image
 } // namespace mozilla
 
--- a/image/src/ProgressTracker.cpp
+++ b/image/src/ProgressTracker.cpp
@@ -17,36 +17,16 @@
 #include "mozilla/Assertions.h"
 #include "mozilla/Services.h"
 
 using mozilla::WeakPtr;
 
 namespace mozilla {
 namespace image {
 
-ProgressTrackerInit::ProgressTrackerInit(Image* aImage,
-                                         ProgressTracker* aTracker)
-{
-  MOZ_ASSERT(aImage);
-
-  if (aTracker) {
-    mTracker = aTracker;
-  } else {
-    mTracker = new ProgressTracker();
-  }
-  mTracker->SetImage(aImage);
-  aImage->SetProgressTracker(mTracker);
-  MOZ_ASSERT(mTracker);
-}
-
-ProgressTrackerInit::~ProgressTrackerInit()
-{
-  mTracker->ResetImage();
-}
-
 static void
 CheckProgressConsistency(Progress aProgress)
 {
   // Check preconditions for every progress bit.
 
   if (aProgress & FLAG_SIZE_AVAILABLE) {
     // No preconditions.
   }
--- a/image/src/ProgressTracker.h
+++ b/image/src/ProgressTracker.h
@@ -153,32 +153,31 @@ public:
     return mObservers.Length();
   }
 
   // This is intentionally non-general because its sole purpose is to support
   // some obscure network priority logic in imgRequest. That stuff could
   // probably be improved, but it's too scary to mess with at the moment.
   bool FirstObserverIs(IProgressObserver* aObserver);
 
+  // Resets our weak reference to our image. Image subclasses should call this
+  // in their destructor.
+  void ResetImage();
+
 private:
   typedef nsTObserverArray<mozilla::WeakPtr<IProgressObserver>> ObserverArray;
   friend class AsyncNotifyRunnable;
   friend class AsyncNotifyCurrentStateRunnable;
-  friend class ProgressTrackerInit;
+  friend class ImageFactory;
 
   ProgressTracker(const ProgressTracker& aOther) = delete;
 
-  // This method should only be called once, and only on an ProgressTracker
-  // that was initialized without an image. ProgressTrackerInit automates this.
+  // Sets our weak reference to our image. Only ImageFactory should call this.
   void SetImage(Image* aImage);
 
-  // Resets our weak reference to our image, for when mImage is about to go out
-  // of scope.  ProgressTrackerInit automates this.
-  void ResetImage();
-
   // Send some notifications that would be necessary to make |aObserver| believe
   // the request is finished downloading and decoding.  We only send
   // FLAG_LOAD_COMPLETE and FLAG_ONLOAD_UNBLOCKED, and only if necessary.
   void EmulateRequestFinished(IProgressObserver* aObserver);
 
   // Main thread only because it deals with the observer service.
   void FireFailureNotification();
 
@@ -196,21 +195,12 @@ private:
   // List of observers attached to the image. Each observer represents a
   // consumer using the image. Array and/or individual elements should only be
   // accessed on the main thread.
   ObserverArray mObservers;
 
   Progress mProgress;
 };
 
-class ProgressTrackerInit
-{
-public:
-  ProgressTrackerInit(Image* aImage, ProgressTracker* aTracker);
-  ~ProgressTrackerInit();
-private:
-  ProgressTracker* mTracker;
-};
-
 } // namespace image
 } // namespace mozilla
 
 #endif // mozilla_image_src_ProgressTracker_h
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -245,18 +245,17 @@ static nsCOMPtr<nsIThread> sScaleWorkerT
 #ifndef DEBUG
 NS_IMPL_ISUPPORTS(RasterImage, imgIContainer, nsIProperties)
 #else
 NS_IMPL_ISUPPORTS(RasterImage, imgIContainer, nsIProperties,
                   imgIContainerDebug)
 #endif
 
 //******************************************************************************
-RasterImage::RasterImage(ProgressTracker* aProgressTracker,
-                         ImageURL* aURI /* = nullptr */) :
+RasterImage::RasterImage(ImageURL* aURI /* = nullptr */) :
   ImageResource(aURI), // invoke superclass's constructor
   mSize(0,0),
   mLockCount(0),
   mDecodeCount(0),
   mRequestedSampleSize(0),
   mLastImageContainerDrawResult(DrawResult::NOT_READY),
 #ifdef DEBUG
   mFramesNotified(0),
@@ -268,18 +267,16 @@ RasterImage::RasterImage(ProgressTracker
   mTransient(false),
   mDiscardable(false),
   mHasSourceData(false),
   mHasBeenDecoded(false),
   mPendingAnimation(false),
   mAnimationFinished(false),
   mWantFullDecode(false)
 {
-  mProgressTrackerInit = new ProgressTrackerInit(this, aProgressTracker);
-
   Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Add(0);
 }
 
 //******************************************************************************
 RasterImage::~RasterImage()
 {
   // Make sure our SourceBuffer is marked as complete. This will ensure that any
   // outstanding decoders terminate.
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -161,19 +161,16 @@ public:
 #ifdef DEBUG
   NS_DECL_IMGICONTAINERDEBUG
 #endif
 
   virtual nsresult StartAnimation() override;
   virtual nsresult StopAnimation() override;
 
   // Methods inherited from Image
-  nsresult Init(const char* aMimeType,
-                uint32_t aFlags) override;
-
   virtual void OnSurfaceDiscarded() override;
 
   // Raster-specific methods
   static NS_METHOD WriteToSourceBuffer(nsIInputStream* aIn, void* aClosure,
                                        const char* aFromRawSegment,
                                        uint32_t aToOffset, uint32_t aCount,
                                        uint32_t* aWriteCount);
 
@@ -283,16 +280,18 @@ public:
     nsCString spec;
     if (GetURI()) {
       GetURI()->GetSpec(spec);
     }
     return spec;
   }
 
 private:
+  nsresult Init(const char* aMimeType, uint32_t aFlags);
+
   DrawResult DrawWithPreDownscaleIfNeeded(DrawableFrameRef&& aFrameRef,
                                           gfxContext* aContext,
                                           const nsIntSize& aSize,
                                           const ImageRegion& aRegion,
                                           GraphicsFilter aFilter,
                                           uint32_t aFlags);
 
   TemporaryRef<gfx::SourceSurface> CopyFrame(uint32_t aWhichFrame,
@@ -420,19 +419,16 @@ private: // data
   bool                       mAnimationFinished:1;
 
   // Whether, once we are done doing a size decode, we should immediately kick
   // off a full decode.
   bool                       mWantFullDecode:1;
 
   TimeStamp mDrawStartTime;
 
-  // Initializes ProgressTracker and resets it on RasterImage destruction.
-  nsAutoPtr<ProgressTrackerInit> mProgressTrackerInit;
-
 
   //////////////////////////////////////////////////////////////////////////////
   // Scaling.