merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 05 Nov 2015 11:58:55 +0100
changeset 305368 59c648a3f95524cb1ee42f2306c1db2698d35258
parent 305278 ea4d83de7022764833919ed84bef9e6cf817eed1 (current diff)
parent 305367 ae33441d7f863eec8258466951a5b6f7ffca8522 (diff)
child 305409 61dcc13d0848230382d5c85cdcf6721a05ee37c6
child 305429 bad16f7ef2ce7b0d2e87c384dd0cf9ef314156f5
child 305498 4657cb34d1e3e99e854f89cc033872fac6f22805
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone45.0a1
first release with
nightly linux32
59c648a3f955 / 45.0a1 / 20151105030433 / files
nightly linux64
59c648a3f955 / 45.0a1 / 20151105030433 / files
nightly mac
59c648a3f955 / 45.0a1 / 20151105030433 / files
nightly win32
59c648a3f955 / 45.0a1 / 20151105030433 / files
nightly win64
59c648a3f955 / 45.0a1 / 20151105030433 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
browser/base/content/browser.xul
devtools/client/locales/en-US/memory.properties
devtools/client/memory/constants.js
devtools/shared/heapsnapshot/tests/unit/xpcshell.ini
dom/browser-element/BrowserElementParent.js
dom/ipc/ContentParent.cpp
layout/svg/nsSVGPaintServerFrame.cpp
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -114,17 +114,17 @@
       <menuitem id="context_reloadAllTabs" label="&reloadAllTabs.label;" accesskey="&reloadAllTabs.accesskey;"
                 tbattr="tabbrowser-multiple-visible"
                 oncommand="gBrowser.reloadAllTabs();"/>
       <menuitem id="context_bookmarkAllTabs"
                 label="&bookmarkAllTabs.label;"
                 accesskey="&bookmarkAllTabs.accesskey;"
                 command="Browser:BookmarkAllTabs"/>
       <menuitem id="context_closeTabsToTheEnd" label="&closeTabsToTheEnd.label;" accesskey="&closeTabsToTheEnd.accesskey;"
-                oncommand="gBrowser.removeTabsToTheEndFrom(TabContextMenu.contextTab);"/>
+                oncommand="gBrowser.removeTabsToTheEndFrom(TabContextMenu.contextTab, {animate: true});"/>
       <menuitem id="context_closeOtherTabs" label="&closeOtherTabs.label;" accesskey="&closeOtherTabs.accesskey;"
                 oncommand="gBrowser.removeAllTabsBut(TabContextMenu.contextTab);"/>
       <menuseparator/>
       <menuitem id="context_undoCloseTab"
                 label="&undoCloseTab.label;"
                 accesskey="&undoCloseTab.accesskey;"
                 observes="History:UndoCloseTab"/>
       <menuitem id="context_closeTab" label="&closeTab.label;" accesskey="&closeTab.accesskey;"
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -2044,22 +2044,23 @@
             }
             return tabsToEnd.reverse();
           ]]>
         </body>
       </method>
 
       <method name="removeTabsToTheEndFrom">
         <parameter name="aTab"/>
+        <parameter name="aParams"/>
         <body>
           <![CDATA[
             if (this.warnAboutClosingTabs(this.closingTabsEnum.TO_END, aTab)) {
               let tabs = this.getTabsToTheEndFrom(aTab);
               for (let i = tabs.length - 1; i >= 0; --i) {
-                this.removeTab(tabs[i], {animate: true});
+                this.removeTab(tabs[i], aParams);
               }
             }
           ]]>
         </body>
       </method>
 
       <method name="removeAllTabsBut">
         <parameter name="aTab"/>
--- a/browser/base/content/test/general/browser_URLBarSetURI.js
+++ b/browser/base/content/test/general/browser_URLBarSetURI.js
@@ -65,18 +65,14 @@ var tests = [
     });
   }
 ];
 
 function loadTabInWindow(win, callback) {
   info("Loading tab");
   let url = "http://user:pass@example.com/";
   let tab = win.gBrowser.selectedTab = win.gBrowser.addTab(url);
-  tab.linkedBrowser.addEventListener("load", function listener() {
+  BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, url).then(() => {
     info("Tab loaded");
-    if (tab.linkedBrowser.currentURI.spec != url)
-      return;
-    tab.linkedBrowser.removeEventListener("load", listener, true);
-
     is(win.gURLBar.textValue, "example.com", "URL bar had user/pass stripped initially");
     callback(tab);
   }, true);
 }
--- a/browser/base/content/test/general/browser_bug329212.js
+++ b/browser/base/content/test/general/browser_bug329212.js
@@ -1,15 +1,13 @@
 function test () {
 
   waitForExplicitFinish();
   gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function () {
-    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
-
+  BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => {
     let doc = gBrowser.contentDocument;
     let tooltip = document.getElementById("aHTMLTooltip");
 
     ok(tooltip.fillInPageTooltip(doc.getElementById("svg1")), "should get title");
     is(tooltip.getAttribute("label"), "This is a non-root SVG element title");
 
     ok(tooltip.fillInPageTooltip(doc.getElementById("text1")), "should get title");
     is(tooltip.getAttribute("label"), "\n\n\n    This            is a title\n\n    ");
@@ -30,14 +28,15 @@ function test () {
 
     ok(tooltip.fillInPageTooltip(doc.getElementById("link4")), "should get title");
     is(tooltip.getAttribute("label"), "This is an xlink:title attribute");
 
     ok(!tooltip.fillInPageTooltip(doc.getElementById("text5")), "should not get title");
 
     gBrowser.removeCurrentTab();
     finish();
-  }, true);
+  });
 
-  content.location = 
-    "http://mochi.test:8888/browser/browser/base/content/test/general/title_test.svg";
+  gBrowser.loadURI(
+    "http://mochi.test:8888/browser/browser/base/content/test/general/title_test.svg"
+  );
 }
 
--- a/browser/base/content/test/general/browser_bug331772_xul_tooltiptext_in_html.js
+++ b/browser/base/content/test/general/browser_bug331772_xul_tooltiptext_in_html.js
@@ -1,23 +1,22 @@
 /**
  * Tests that the tooltiptext attribute is used for XUL elements in an HTML doc.
  */
 function test () {
   waitForExplicitFinish();
   gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function () {
-    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
-
+  BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => {
     let doc = gBrowser.contentDocument;
     let tooltip = document.getElementById("aHTMLTooltip");
 
     ok(tooltip.fillInPageTooltip(doc.getElementById("xulToolbarButton")), "should get tooltiptext");
     is(tooltip.getAttribute("label"), "XUL tooltiptext");
 
     gBrowser.removeCurrentTab();
     finish();
-  }, true);
+  });
 
-  content.location = 
-    "http://mochi.test:8888/browser/browser/base/content/test/general/xul_tooltiptext.xhtml";
+  gBrowser.loadURI(
+    "http://mochi.test:8888/browser/browser/base/content/test/general/xul_tooltiptext.xhtml"
+  );
 }
 
--- a/browser/base/content/test/general/browser_bug406216.js
+++ b/browser/base/content/test/general/browser_bug406216.js
@@ -17,17 +17,17 @@ const URIS = ["about:config",
 function test() {
   waitForExplicitFinish();
   URIS.forEach(addTab);
 }
 
 function addTab(aURI, aIndex) {
   var tab = gBrowser.addTab(aURI);
   if (aIndex == 0)
-    gBrowser.removeTab(gBrowser.tabs[0]);
+    gBrowser.removeTab(gBrowser.tabs[0], {skipPermitUnload: true});
 
   tab.linkedBrowser.addEventListener("load", function (event) {
     event.currentTarget.removeEventListener("load", arguments.callee, true);
     if (++count == URIS.length)
       executeSoon(doTabsTest);
   }, true);
 }
 
@@ -36,19 +36,19 @@ function doTabsTest() {
 
   // sample of "close related tabs" feature
   gBrowser.tabContainer.addEventListener("TabClose", function (event) {
     event.currentTarget.removeEventListener("TabClose", arguments.callee, true);
     var closedTab = event.originalTarget;
     var scheme = closedTab.linkedBrowser.currentURI.scheme;
     Array.slice(gBrowser.tabs).forEach(function (aTab) {
       if (aTab != closedTab && aTab.linkedBrowser.currentURI.scheme == scheme)
-        gBrowser.removeTab(aTab);
+        gBrowser.removeTab(aTab, {skipPermitUnload: true});
     });
   }, true);
 
-  gBrowser.removeTab(gBrowser.tabs[0]);
+  gBrowser.removeTab(gBrowser.tabs[0], {skipPermitUnload: true});
   is(gBrowser.tabs.length, 1, "Related tabs are not closed unexpectedly");
 
   gBrowser.addTab("about:blank");
-  gBrowser.removeTab(gBrowser.tabs[0]);
+  gBrowser.removeTab(gBrowser.tabs[0], {skipPermitUnload: true});
   finish();
 }
--- a/browser/base/content/test/general/browser_bug431826.js
+++ b/browser/base/content/test/general/browser_bug431826.js
@@ -1,42 +1,63 @@
-function test() {
-  waitForExplicitFinish();
-
-  gBrowser.selectedTab = gBrowser.addTab();
-
-  // Navigate to a site with a broken cert
-  window.addEventListener("DOMContentLoaded", testBrokenCert, true);
-  content.location = "https://nocert.example.com/";
+function remote(task) {
+  return ContentTask.spawn(gBrowser.selectedBrowser, null, task);
 }
 
-function testBrokenCert() {
-  if (gBrowser.contentDocument.documentURI === "about:blank")
-    return;
-  window.removeEventListener("DOMContentLoaded", testBrokenCert, true);
+add_task(function* () {
+  gBrowser.selectedTab = gBrowser.addTab();
+
+  let promise = remote(function () {
+    return ContentTaskUtils.waitForEvent(this, "DOMContentLoaded", true, event => {
+      return content.document.documentURI != "about:blank";
+    }).then(() => 0); // don't want to send the event to the chrome process
+  });
+  gBrowser.loadURI("https://nocert.example.com/");
+  yield promise;
+
+  let uri = yield remote(() => {
+    return content.document.documentURI;
+  });
 
   // Confirm that we are displaying the contributed error page, not the default
-  ok(gBrowser.contentDocument.documentURI.startsWith("about:certerror"), "Broken page should go to about:certerror, not about:neterror");
+  ok(uri.startsWith("about:certerror"), "Broken page should go to about:certerror, not about:neterror");
+
+  let advancedDiv, advancedDivVisibility, technicalDivCollapsed;
+
+  [advancedDiv, advancedDivVisibility] = yield remote(() => {
+    let div = content.document.getElementById("advancedPanel");
+    if (div) {
+      return [true, div.ownerDocument.defaultView.getComputedStyle(div, "").visibility];
+    } else {
+      return [null, null];
+    }
+  });
 
   // Confirm that the expert section is collapsed
-  var advancedDiv = gBrowser.contentDocument.getElementById("advancedPanel");
   ok(advancedDiv, "Advanced content div should exist");
-  is_element_hidden(advancedDiv, "Advanced content should not be visible by default");
+  is(advancedDivVisibility, "hidden", "Advanced content should not be visible by default");
 
   // Tweak the expert mode pref
   gPrefService.setBoolPref("browser.xul.error_pages.expert_bad_cert", true);
 
-  window.addEventListener("DOMContentLoaded", testExpertPref, true);
+  promise = remote(function () {
+    return ContentTaskUtils.waitForEvent(this, "DOMContentLoaded", true);
+  });
   gBrowser.reload();
-}
+  yield promise;
 
-function testExpertPref() {
-  window.removeEventListener("DOMContentLoaded", testExpertPref, true);
-  var advancedDiv = gBrowser.contentDocument.getElementById("advancedPanel");
+  [advancedDiv, advancedDivVisibility] = yield remote(() => {
+    let div = content.document.getElementById("advancedPanel");
+    if (div) {
+      return [true, div.ownerDocument.defaultView.getComputedStyle(div, "").visibility];
+    } else {
+      return [null, null];
+    }
+  });
+
   ok(advancedDiv, "Advanced content div should exist");
-  is_element_visible(advancedDiv, "Advanced content should be visible by default");
+  is(advancedDivVisibility, "visible", "Advanced content should be visible by default");
 
   // Clean up
   gBrowser.removeCurrentTab();
   if (gPrefService.prefHasUserValue("browser.xul.error_pages.expert_bad_cert"))
     gPrefService.clearUserPref("browser.xul.error_pages.expert_bad_cert");
-  finish();
-}
+});
--- a/browser/base/content/test/general/browser_bug432599.js
+++ b/browser/base/content/test/general/browser_bug432599.js
@@ -94,17 +94,17 @@ function checkBookmarksPanel(invoker, ph
       popupElement.removeEventListener("popuphidden", arguments.callee, false);
       if (phase < 4) {
         checkBookmarksPanel(invoker, phase + 1);
       } else {
         ++currentInvoker;
         if (currentInvoker < invokers.length) {
           checkBookmarksPanel(invokers[currentInvoker], 1);
         } else {
-          gBrowser.removeCurrentTab();
+          gBrowser.removeTab(gBrowser.selectedTab, {skipPermitUnload: true});
           PlacesUtils.bookmarks.removeItem(bookmarkId);
           executeSoon(finish);
         }
       }
     }
   };
 
   switch (phase) {
--- a/browser/base/content/test/general/browser_bug435035.js
+++ b/browser/base/content/test/general/browser_bug435035.js
@@ -1,16 +1,17 @@
 function test() {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function () {
-    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+  BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => {
     is(document.getElementById("identity-box").className,
        "unknownIdentity mixedDisplayContent",
        "identity box has class name for mixed content");
 
     gBrowser.removeCurrentTab();
     finish();
-  }, true);
+  });
 
-  content.location = "https://example.com/browser/browser/base/content/test/general/test_bug435035.html";
+  gBrowser.loadURI(
+    "https://example.com/browser/browser/base/content/test/general/test_bug435035.html"
+  );
 }
--- a/browser/base/content/test/general/browser_bug441778.js
+++ b/browser/base/content/test/general/browser_bug441778.js
@@ -25,22 +25,22 @@ function test() {
     // Change the zoom level and then save it so we can compare it to the level
     // after loading the sub-document.
     FullZoom.enlarge();
     var zoomLevel = ZoomManager.zoom;
 
     // Start the sub-document load.
     let deferred = Promise.defer();
     executeSoon(function () {
-      testBrowser.addEventListener("load", function (e) {
-        testBrowser.removeEventListener("load", arguments.callee, true);
-
-        is(e.target.defaultView.location, TEST_IFRAME_URL, "got the load event for the iframe");
+      BrowserTestUtils.browserLoaded(testBrowser, true).then(url => {
+        is(url, TEST_IFRAME_URL, "got the load event for the iframe");
         is(ZoomManager.zoom, zoomLevel, "zoom is retained after sub-document load");
 
         FullZoomHelper.removeTabAndWaitForLocationChange().
           then(() => deferred.resolve());
-      }, true);
-      content.document.querySelector("iframe").src = TEST_IFRAME_URL;
+      });
+      ContentTask.spawn(testBrowser, TEST_IFRAME_URL, url => {
+        content.document.querySelector("iframe").src = url;
+      });
     });
     yield deferred.promise;
   }).then(finish, FullZoomHelper.failAndContinue(finish));
 }
--- a/browser/base/content/test/general/browser_bug455852.js
+++ b/browser/base/content/test/general/browser_bug455852.js
@@ -1,16 +1,20 @@
-function test() {
+add_task(function*() {
   is(gBrowser.tabs.length, 1, "one tab is open");
 
   gBrowser.selectedBrowser.focus();
   isnot(document.activeElement, gURLBar.inputField, "location bar is not focused");
 
   var tab = gBrowser.selectedTab;
   gPrefService.setBoolPref("browser.tabs.closeWindowWithLastTab", false);
+
+  let tabClosedPromise = BrowserTestUtils.removeTab(tab, {dontRemove: true});
   EventUtils.synthesizeKey("w", { accelKey: true });
+  yield tabClosedPromise;
+
   is(tab.parentNode, null, "ctrl+w removes the tab");
   is(gBrowser.tabs.length, 1, "a new tab has been opened");
   is(document.activeElement, gURLBar.inputField, "location bar is focused for the new tab");
 
   if (gPrefService.prefHasUserValue("browser.tabs.closeWindowWithLastTab"))
     gPrefService.clearUserPref("browser.tabs.closeWindowWithLastTab");
-}
+});
--- a/browser/base/content/test/general/browser_bug521216.js
+++ b/browser/base/content/test/general/browser_bug521216.js
@@ -13,20 +13,23 @@ function test() {
 
 function record(aName) {
   info("got " + aName);
   if (actual.indexOf(aName) == -1)
     actual.push(aName);
   if (actual.length == expected.length) {
     is(actual.toString(), expected.toString(),
        "got events and progress notifications in expected order");
-    gBrowser.removeTab(tab);
-    gBrowser.removeTabsProgressListener(progressListener);
-    gBrowser.tabContainer.removeEventListener("TabOpen", TabOpen, false);
-    finish();
+
+    executeSoon(function(tab) {
+      gBrowser.removeTab(tab);
+      gBrowser.removeTabsProgressListener(progressListener);
+      gBrowser.tabContainer.removeEventListener("TabOpen", TabOpen, false);
+      finish();
+    }.bind(null, tab));
   }
 }
 
 function TabOpen(aEvent) {
   if (aEvent.target == tab)
     record(arguments.callee.name);
 }
 
--- a/browser/base/content/test/general/browser_bug533232.js
+++ b/browser/base/content/test/general/browser_bug533232.js
@@ -1,36 +1,36 @@
 function test() {
   var tab1 = gBrowser.selectedTab;
   var tab2 = gBrowser.addTab();
   var childTab1;
   var childTab2;
 
   childTab1 = gBrowser.addTab("about:blank", { relatedToCurrent: true });
   gBrowser.selectedTab = childTab1;
-  gBrowser.removeCurrentTab();
+  gBrowser.removeTab(gBrowser.selectedTab, { skipPermitUnload: true });
   is(idx(gBrowser.selectedTab), idx(tab1),
      "closing a tab next to its parent selects the parent");
 
   childTab1 = gBrowser.addTab("about:blank", { relatedToCurrent: true });
   gBrowser.selectedTab = tab2;
   gBrowser.selectedTab = childTab1;
-  gBrowser.removeCurrentTab();
+  gBrowser.removeTab(gBrowser.selectedTab, { skipPermitUnload: true });
   is(idx(gBrowser.selectedTab), idx(tab2),
      "closing a tab next to its parent doesn't select the parent if another tab had been selected ad interim");
 
   gBrowser.selectedTab = tab1;
   childTab1 = gBrowser.addTab("about:blank", { relatedToCurrent: true });
   childTab2 = gBrowser.addTab("about:blank", { relatedToCurrent: true });
   gBrowser.selectedTab = childTab1;
-  gBrowser.removeCurrentTab();
+  gBrowser.removeTab(gBrowser.selectedTab, { skipPermitUnload: true });
   is(idx(gBrowser.selectedTab), idx(childTab2),
      "closing a tab next to its parent selects the next tab with the same parent");
-  gBrowser.removeCurrentTab();
+  gBrowser.removeTab(gBrowser.selectedTab, { skipPermitUnload: true });
   is(idx(gBrowser.selectedTab), idx(tab2),
      "closing the last tab in a set of child tabs doesn't go back to the parent");
 
-  gBrowser.removeTab(tab2);
+  gBrowser.removeTab(tab2, { skipPermitUnload: true });
 }
 
 function idx(tab) {
   return Array.indexOf(gBrowser.tabs, tab);
 }
--- a/browser/base/content/test/general/browser_bug553455.js
+++ b/browser/base/content/test/general/browser_bug553455.js
@@ -798,39 +798,36 @@ function test_urlbar() {
     gURLBar.value = TESTROOT + "unsigned.xpi";
     gURLBar.focus();
     EventUtils.synthesizeKey("VK_RETURN", {});
   });
 },
 
 function test_wronghost() {
   gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.addEventListener("load", function() {
-    if (gBrowser.currentURI.spec != TESTROOT2 + "enabled.html")
-      return;
 
-    gBrowser.removeEventListener("load", arguments.callee, true);
-
+  let requestedUrl = TESTROOT2 + "enabled.html";
+  BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, requestedUrl).then(() => {
     // Wait for the progress notification
     wait_for_progress_notification(function(aPanel) {
       // Wait for the complete notification
       wait_for_notification("addon-install-failed", function(aPanel) {
         let notification = aPanel.childNodes[0];
         is(notification.getAttribute("label"),
            "The add-on downloaded from this site could not be installed " +
            "because it appears to be corrupt.",
            "Should have seen the right message");
 
         wait_for_notification_close(runNextTest);
         gBrowser.removeTab(gBrowser.selectedTab);
       });
     });
 
     gBrowser.loadURI(TESTROOT + "corrupt.xpi");
-  }, true);
+  });
   gBrowser.loadURI(TESTROOT2 + "enabled.html");
 },
 
 function test_reload() {
   // Wait for the progress notification
   wait_for_progress_notification(function(aPanel) {
     // Wait for the install confirmation dialog
     wait_for_install_dialog(function() {
@@ -843,33 +840,29 @@ function test_reload() {
            "Should have seen the right message");
 
         function test_fail() {
           ok(false, "Reloading should not have hidden the notification");
         }
 
         PopupNotifications.panel.addEventListener("popuphiding", test_fail, false);
 
-        gBrowser.addEventListener("load", function() {
-          if (gBrowser.currentURI.spec != TESTROOT2 + "enabled.html")
-            return;
-
-          gBrowser.removeEventListener("load", arguments.callee, true);
-
+        let requestedUrl = TESTROOT2 + "enabled.html";
+        BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, requestedUrl).then(() => {
           PopupNotifications.panel.removeEventListener("popuphiding", test_fail, false);
 
           AddonManager.getAllInstalls(function(aInstalls) {
             is(aInstalls.length, 1, "Should be one pending install");
             aInstalls[0].cancel();
 
             Services.perms.remove(makeURI("http://example.com/"), "install");
             wait_for_notification_close(runNextTest);
             gBrowser.removeTab(gBrowser.selectedTab);
           });
-        }, true);
+        });
         gBrowser.loadURI(TESTROOT2 + "enabled.html");
       });
 
       accept_install_dialog();
     });
   });
 
   var pm = Services.perms;
--- a/browser/base/content/test/general/browser_bug561623.js
+++ b/browser/base/content/test/general/browser_bug561623.js
@@ -1,14 +1,12 @@
 function test() {
   waitForExplicitFinish();
   gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function () {
-    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
-
+  BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => {
     let doc = gBrowser.contentDocument;
     let tooltip = document.getElementById("aHTMLTooltip");
     let i = doc.getElementById("i");
 
     ok(!tooltip.fillInPageTooltip(i),
        "No tooltip should be shown when @title is null");
 
     i.title = "foo";
@@ -16,14 +14,15 @@ function test() {
        "A tooltip should be shown when @title is not the empty string");
 
     i.pattern = "bar";
     ok(tooltip.fillInPageTooltip(i),
        "A tooltip should be shown when @title is not the empty string");
 
     gBrowser.removeCurrentTab();
     finish();
-  }, true);
+  });
 
-  content.location = 
-    "data:text/html,<!DOCTYPE html><html><body><input id='i'></body></html>";
+  gBrowser.loadURI(
+    "data:text/html,<!DOCTYPE html><html><body><input id='i'></body></html>"
+  );
 }
 
--- a/browser/base/content/test/general/browser_bug562649.js
+++ b/browser/base/content/test/general/browser_bug562649.js
@@ -9,18 +9,16 @@ function test() {
   is(gURLBar.value, URI, "location bar value matches test URI");
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.removeCurrentTab();
   is(gBrowser.userTypedValue, URI, "userTypedValue matches test URI after switching tabs");
   is(gURLBar.value, URI, "location bar value matches test URI after switching tabs");
 
   waitForExplicitFinish();
-  gBrowser.selectedBrowser.addEventListener("load", function () {
-    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
-
+  BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => {
     is(gBrowser.userTypedValue, null, "userTypedValue is null as the page has loaded");
     is(gURLBar.value, URI, "location bar value matches test URI as the page has loaded");
 
     gBrowser.removeCurrentTab();
     finish();
-  }, true);
+  });
 }
--- a/browser/base/content/test/general/browser_bug578534.js
+++ b/browser/base/content/test/general/browser_bug578534.js
@@ -3,27 +3,26 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 function test() {
   let uriString = "http://example.com/";
   let cookieBehavior = "network.cookie.cookieBehavior";
   let uriObj = Services.io.newURI(uriString, null, null)
   let cp = Components.classes["@mozilla.org/cookie/permission;1"]
                      .getService(Components.interfaces.nsICookiePermission);
-  
+
   Services.prefs.setIntPref(cookieBehavior, 2);
 
   cp.setAccess(uriObj, cp.ACCESS_ALLOW);
   gBrowser.selectedTab = gBrowser.addTab(uriString);
   waitForExplicitFinish();
-  gBrowser.selectedBrowser.addEventListener("load", onTabLoaded, true);
-  
+  BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(onTabLoaded);
+
   function onTabLoaded() {
     is(gBrowser.selectedBrowser.contentWindow.navigator.cookieEnabled, true,
        "navigator.cookieEnabled should be true");
     // Clean up
-    gBrowser.selectedBrowser.removeEventListener("load", onTabLoaded, true);
     gBrowser.removeTab(gBrowser.selectedTab);
     Services.prefs.setIntPref(cookieBehavior, 0);
     cp.setAccess(uriObj, cp.ACCESS_DEFAULT);
     finish();
   }
 }
--- a/browser/base/content/test/general/browser_bug579872.js
+++ b/browser/base/content/test/general/browser_bug579872.js
@@ -1,29 +1,28 @@
 /* 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/. */
 
 function test() {
   let newTab = gBrowser.addTab();
   waitForExplicitFinish();
-  newTab.linkedBrowser.addEventListener("load", mainPart, true);
-  
+  BrowserTestUtils.browserLoaded(newTab.linkedBrowser).then(mainPart);
+
   function mainPart() {
     gBrowser.pinTab(newTab);
     gBrowser.selectedTab = newTab;
     
     openUILinkIn("javascript:var x=0;", "current");
     is(gBrowser.tabs.length, 2, "Should open in current tab");
     
     openUILinkIn("http://example.com/1", "current");
     is(gBrowser.tabs.length, 2, "Should open in current tab");
     
     openUILinkIn("http://example.org/", "current");
     is(gBrowser.tabs.length, 3, "Should open in new tab");
     
-    newTab.removeEventListener("load", mainPart, true);
     gBrowser.removeTab(newTab);
     gBrowser.removeTab(gBrowser.tabs[1]); // example.org tab
     finish();
   }
   newTab.linkedBrowser.loadURI("http://example.com");
 }
--- a/browser/base/content/test/general/browser_bug580956.js
+++ b/browser/base/content/test/general/browser_bug580956.js
@@ -12,17 +12,15 @@ function test() {
 
   gPrefService.setIntPref("browser.sessionstore.max_tabs_undo", 0);
   gPrefService.clearUserPref("browser.sessionstore.max_tabs_undo");
   is(numClosedTabs(), 0, "There should be 0 closed tabs.");
   ok(!isUndoCloseEnabled(), "Undo Close Tab should be disabled.");
 
   var tab = gBrowser.addTab("http://mochi.test:8888/");
   var browser = gBrowser.getBrowserForTab(tab);
-  browser.addEventListener("load", function() {
-    browser.removeEventListener("load", arguments.callee, true);
-
+  BrowserTestUtils.browserLoaded(browser).then(() => {
     BrowserTestUtils.removeTab(tab).then(() => {
       ok(isUndoCloseEnabled(), "Undo Close Tab should be enabled.");
       finish();
     });
-  }, true);
+  });
 }
--- a/browser/base/content/test/general/browser_bug581947.js
+++ b/browser/base/content/test/general/browser_bug581947.js
@@ -53,19 +53,17 @@ function todo_check(aElementName, aBarre
   todo(!cought, "setCustomValidity should exist for " + aElementName);
 
   content.removeChild(e);
 }
 
 function test () {
   waitForExplicitFinish();
   gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function () {
-    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
-
+  BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => {
     let testData = [
     /* element name, barred */
       [ 'input',    false,  null],
       [ 'textarea', false,  null],
       [ 'button',   true,  'button'],
       [ 'button',   false, 'submit'],
       [ 'select',   false,  null],
       [ 'output',   true,   null],
@@ -82,14 +80,15 @@ function test () {
     ];
 
     for (let data of todo_testData) {
       todo_check(data[0], data[1]);
     }
 
     gBrowser.removeCurrentTab();
     finish();
-  }, true);
+  });
 
-  content.location = 
-    "data:text/html,<!DOCTYPE html><html><body><form id='content'></form></body></html>";
+  gBrowser.loadURI(
+    "data:text/html,<!DOCTYPE html><html><body><form id='content'></form></body></html>"
+  );
 }
 
--- a/browser/base/content/test/general/browser_bug585558.js
+++ b/browser/base/content/test/general/browser_bug585558.js
@@ -9,16 +9,23 @@ function addTab(aURL) {
 }
 
 function testAttrib(elem, attrib, attribValue, msg) {
   is(elem.hasAttribute(attrib), attribValue, msg);
 }
 
 function test() {
   waitForExplicitFinish();
+
+  // Ensure TabView has been initialized already. Otherwise it could
+  // activate at an unexpected time and show/hide tabs.
+  TabView._initFrame(runTest);
+}
+
+function runTest() {
   is(gBrowser.tabs.length, 1, "one tab is open initially");
 
   // Add several new tabs in sequence, hiding some, to ensure that the
   // correct attributes get set
 
   addTab("http://mochi.test:8888/#0");
   addTab("http://mochi.test:8888/#1");
   addTab("http://mochi.test:8888/#2");
--- a/browser/base/content/test/general/browser_bug594131.js
+++ b/browser/base/content/test/general/browser_bug594131.js
@@ -1,20 +1,18 @@
 /* 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/. */
 
 function test() {
   let newTab = gBrowser.addTab("http://example.com");
   waitForExplicitFinish();
-  newTab.linkedBrowser.addEventListener("load", mainPart, true);
+  BrowserTestUtils.browserLoaded(newTab.linkedBrowser).then(mainPart);
 
   function mainPart() {
-    newTab.linkedBrowser.removeEventListener("load", mainPart, true);
-
     gBrowser.pinTab(newTab);
     gBrowser.selectedTab = newTab;
 
     openUILinkIn("http://example.org/", "current", { inBackground: true });
     isnot(gBrowser.selectedTab, newTab, "shouldn't load in background");
 
     gBrowser.removeTab(newTab);
     gBrowser.removeTab(gBrowser.tabs[1]); // example.org tab
--- a/browser/base/content/test/general/browser_bug597218.js
+++ b/browser/base/content/test/general/browser_bug597218.js
@@ -1,15 +1,21 @@
 /* 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/. */
 
 function test() {
   waitForExplicitFinish();
 
+  // Ensure TabView has been initialized already. Otherwise it could
+  // activate at an unexpected time and show/hide tabs.
+  TabView._initFrame(runTest);
+}
+
+function runTest() {
   // establish initial state
   is(gBrowser.tabs.length, 1, "we start with one tab");
   
   // create a tab
   let tab = gBrowser.loadOneTab("about:blank");
   ok(!tab.hidden, "tab starts out not hidden");
   is(gBrowser.tabs.length, 2, "we now have two tabs");
 
--- a/browser/base/content/test/general/browser_bug624734.js
+++ b/browser/base/content/test/general/browser_bug624734.js
@@ -12,20 +12,18 @@ function finishTest() {
   gBrowser.removeCurrentTab();
   finish();
 }
 
 function test() {
   waitForExplicitFinish();
 
   let tab = gBrowser.selectedTab = gBrowser.addTab();
-  tab.linkedBrowser.addEventListener("load", (function(event) {
-    tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
-
+  BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(() => {
     if (BookmarkingUI.status == BookmarkingUI.STATUS_UPDATING) {
       waitForCondition(() => BookmarkingUI.status != BookmarkingUI.STATUS_UPDATING, finishTest, "BookmarkingUI was updating for too long");
     } else {
       finishTest();
     }
-  }), true);
+  });
 
   tab.linkedBrowser.loadURI("http://example.com/browser/browser/base/content/test/general/dummy_page.html");
 }
--- a/browser/base/content/test/general/browser_bug633691.js
+++ b/browser/base/content/test/general/browser_bug633691.js
@@ -1,25 +1,20 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 function test() {
   waitForExplicitFinish();
-  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedTab = gBrowser.addTab("data:text/html,<iframe width='700' height='700' src='about:certerror'></iframe>");
   // Open a html page with about:certerror in an iframe
-  gBrowser.selectedBrowser.addEventListener("load", testIframeCert, true);
-  content.location = "data:text/html,<iframe width='700' height='700' src='about:certerror'></iframe>";
+  BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(testIframeCert);
 }
 
 function testIframeCert(e) {
-  if (e.target.location.href == "about:blank") {
-    return;
-  }
-  gBrowser.selectedBrowser.removeEventListener("load", testIframeCert, true);
   // Confirm that the expert section is hidden
   var doc = gBrowser.contentDocument.getElementsByTagName('iframe')[0].contentDocument;
   var aP = doc.getElementById("advancedPanel");
   ok(aP, "Advanced content should exist");
   is_element_hidden(aP, "Advanced content should not be visible by default")
 
   // Clean up
   gBrowser.removeCurrentTab();
--- a/browser/base/content/test/general/browser_bug676619.js
+++ b/browser/base/content/test/general/browser_bug676619.js
@@ -1,63 +1,69 @@
 function test () {
+  requestLongerTimeout(2);
   waitForExplicitFinish();
 
   var isHTTPS = false;
 
-  gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function () {
-    if (isHTTPS) {
-      gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
-    }
-    let doc = gBrowser.contentDocument;
-
-
+  function loadListener() {
     function testLocation(link, url, next) {
       var tabOpenListener = new TabOpenListener(url, function () {
           gBrowser.removeTab(this.tab);
       }, function () {
         next();
       });
 
-      doc.getElementById(link).click();
+      ContentTask.spawn(gBrowser.selectedBrowser, link, link => {
+        content.document.getElementById(link).click();
+      });
     }
 
     function testLink(link, name, next) {
-        addWindowListener("chrome://mozapps/content/downloads/unknownContentType.xul", function (win) {
-            is(doc.getElementById("unload-flag").textContent, "Okay", "beforeunload shouldn't have fired");
-            is(win.document.getElementById("location").value, name, "file name should match");
-            win.close();
-            next();
+      addWindowListener("chrome://mozapps/content/downloads/unknownContentType.xul", function (win) {
+        ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
+          return content.document.getElementById("unload-flag").textContent;
+        }).then(unloadFlag => {
+          is(unloadFlag, "Okay", "beforeunload shouldn't have fired");
+          is(win.document.getElementById("location").value, name, "file name should match");
+          win.close();
+          next();
         });
+      });
 
-        doc.getElementById(link).click();
+      ContentTask.spawn(gBrowser.selectedBrowser, link, link => {
+        content.document.getElementById(link).click();
+      });
     }
 
     testLink("link1", "test.txt",
       testLink.bind(null, "link2", "video.ogg",
         testLink.bind(null, "link3", "just some video",
           testLink.bind(null, "link4", "with-target.txt",
             testLink.bind(null, "link5", "javascript.txt",
               testLink.bind(null, "link6", "test.blob",
                 testLocation.bind(null, "link7", "http://example.com/",
                   function () {
                     if (isHTTPS) {
-                      gBrowser.removeCurrentTab();
                       finish();
                     } else {
                       // same test again with https:
                       isHTTPS = true;
-                      content.location = "https://example.com:443/browser/browser/base/content/test/general/download_page.html";
+                      gBrowser.loadURI("https://example.com:443/browser/browser/base/content/test/general/download_page.html");
                     }
                   })))))));
 
-  }, true);
+  }
 
-  content.location = "http://mochi.test:8888/browser/browser/base/content/test/general/download_page.html";
+  BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => {
+    loadListener();
+    BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(loadListener);
+  });
+
+  gBrowser.loadURI("http://mochi.test:8888/browser/browser/base/content/test/general/download_page.html");
 }
 
 
 function addWindowListener(aURL, aCallback) {
   Services.wm.addListener({
     onOpenWindow: function(aXULWindow) {
       info("window opened, waiting for focus");
       Services.wm.removeListener(this);
@@ -92,25 +98,22 @@ TabOpenListener.prototype = {
   tab: null,
   browser: null,
 
   handleEvent: function(event) {
     if (event.type == "TabOpen") {
       gBrowser.tabContainer.removeEventListener("TabOpen", this, false);
       this.tab = event.originalTarget;
       this.browser = this.tab.linkedBrowser;
-      gBrowser.addEventListener("pageshow", this, false);
-    } else if (event.type == "pageshow") {
-      if (event.target.location.href != this.url)
-        return;
-      gBrowser.removeEventListener("pageshow", this, false);
-      this.tab.addEventListener("TabClose", this, false);
-      var url = this.browser.contentDocument.location.href;
-      is(url, this.url, "Should have opened the correct tab");
-      this.opencallback(this.tab, this.browser.contentWindow);
+      BrowserTestUtils.browserLoaded(this.browser, false, this.url).then(() => {
+        this.tab.addEventListener("TabClose", this, false);
+        var url = this.browser.currentURI.spec;
+        is(url, this.url, "Should have opened the correct tab");
+        this.opencallback();
+      });
     } else if (event.type == "TabClose") {
       if (event.originalTarget != this.tab)
         return;
       this.tab.removeEventListener("TabClose", this, false);
       this.opencallback = null;
       this.tab = null;
       this.browser = null;
       // Let the window close complete
--- a/browser/base/content/test/general/browser_bug902156.js
+++ b/browser/base/content/test/general/browser_bug902156.js
@@ -40,131 +40,115 @@ function cleanUpAfterTests() {
   gBrowser.removeCurrentTab();
   window.focus();
   finish();
 }
 
 //------------------------ Test 1 ------------------------------
 
 function test1A() {
-  // Removing EventListener because we have to register a new
-  // one once the page is loaded with mixed content blocker disabled
-  gTestBrowser.removeEventListener("load", test1A, true);
-  gTestBrowser.addEventListener("load", test1B, true);
+  BrowserTestUtils.browserLoaded(gTestBrowser).then(test1B);
 
   assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
 
   // Disable Mixed Content Protection for the page (and reload)
   let {gIdentityHandler} = gTestBrowser.ownerGlobal;
   gIdentityHandler.disableMixedContentProtection();
 }
 
 function test1B() {
   var expected = "Mixed Content Blocker disabled";
   waitForCondition(
     () => content.document.getElementById('mctestdiv').innerHTML == expected,
     test1C, "Error: Waited too long for mixed script to run in Test 1B");
 }
 
 function test1C() {
-  gTestBrowser.removeEventListener("load", test1B, true);
   var actual = content.document.getElementById('mctestdiv').innerHTML;
   is(actual, "Mixed Content Blocker disabled", "OK: Executed mixed script in Test 1C");
 
   // The Script loaded after we disabled the page, now we are going to reload the
   // page and see if our decision is persistent
-  gTestBrowser.addEventListener("load", test1D, true);
+  BrowserTestUtils.browserLoaded(gTestBrowser).then(test1D);
 
   var url = gHttpTestRoot1 + "file_bug902156_2.html";
-  gTestBrowser.contentWindow.location = url;
+  gTestBrowser.loadURI(url);
 }
 
 function test1D() {
-  gTestBrowser.removeEventListener("load", test1D, true);
-
   // The Control Center button should appear but isMixedContentBlocked should be NOT true,
   // because our decision of disabling the mixed content blocker is persistent.
   assertMixedContentBlockingState(gTestBrowser, {activeLoaded: true, activeBlocked: false, passiveLoaded: false});
 
   var actual = content.document.getElementById('mctestdiv').innerHTML;
   is(actual, "Mixed Content Blocker disabled", "OK: Executed mixed script in Test 1D");
 
   // move on to Test 2
   test2();
 }
 
 //------------------------ Test 2 ------------------------------
 
 function test2() {
-  gTestBrowser.addEventListener("load", test2A, true);
+  BrowserTestUtils.browserLoaded(gTestBrowser).then(test2A);
   var url = gHttpTestRoot2 + "file_bug902156_2.html";
-  gTestBrowser.contentWindow.location = url;
+  gTestBrowser.loadURI(url);
 }
 
 function test2A() {
-  // Removing EventListener because we have to register a new
-  // one once the page is loaded with mixed content blocker disabled
-  gTestBrowser.removeEventListener("load", test2A, true);
-  gTestBrowser.addEventListener("load", test2B, true);
+  BrowserTestUtils.browserLoaded(gTestBrowser).then(test2B);
 
   assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
 
   // Disable Mixed Content Protection for the page (and reload)
   let {gIdentityHandler} = gTestBrowser.ownerGlobal;
   gIdentityHandler.disableMixedContentProtection();
 }
 
 function test2B() {
   var expected = "Mixed Content Blocker disabled";
   waitForCondition(
     () => content.document.getElementById('mctestdiv').innerHTML == expected,
     test2C, "Error: Waited too long for mixed script to run in Test 2B");
 }
 
 function test2C() {
-  gTestBrowser.removeEventListener("load", test2B, true);
   var actual = content.document.getElementById('mctestdiv').innerHTML;
   is(actual, "Mixed Content Blocker disabled", "OK: Executed mixed script in Test 2C");
 
   // The Script loaded after we disabled the page, now we are going to reload the
   // page and see if our decision is persistent
-  gTestBrowser.addEventListener("load", test2D, true);
+  BrowserTestUtils.browserLoaded(gTestBrowser).then(test2D);
 
   // reload the page using the provided link in the html file
   var mctestlink = content.document.getElementById("mctestlink");
   mctestlink.click();
 }
 
 function test2D() {
-  gTestBrowser.removeEventListener("load", test2D, true);
-
   // The Control Center button should appear but isMixedContentBlocked should be NOT true,
   // because our decision of disabling the mixed content blocker is persistent.
   assertMixedContentBlockingState(gTestBrowser, {activeLoaded: true, activeBlocked: false, passiveLoaded: false});
 
   var actual = content.document.getElementById('mctestdiv').innerHTML;
   is(actual, "Mixed Content Blocker disabled", "OK: Executed mixed script in Test 2D");
 
   // move on to Test 3
   test3();
 }
 
 //------------------------ Test 3 ------------------------------
 
 function test3() {
-  gTestBrowser.addEventListener("load", test3A, true);
+  BrowserTestUtils.browserLoaded(gTestBrowser).then(test3A);
   var url = gHttpTestRoot1 + "file_bug902156_3.html";
-  gTestBrowser.contentWindow.location = url;
+  gTestBrowser.loadURI(url);
 }
 
 function test3A() {
-  // Removing EventListener because we have to register a new
-  // one once the page is loaded with mixed content blocker disabled
-  gTestBrowser.removeEventListener("load", test3A, true);
-
   assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
 
   // We are done with tests, clean up
   cleanUpAfterTests();
 }
 
 //------------------------------------------------------
 
@@ -179,12 +163,12 @@ function test() {
 
   // Not really sure what this is doing
   var newTab = gBrowser.addTab();
   gBrowser.selectedTab = newTab;
   gTestBrowser = gBrowser.selectedBrowser;
   newTab.linkedBrowser.stop()
 
   // Starting Test Number 1:
-  gTestBrowser.addEventListener("load", test1A, true);
+  BrowserTestUtils.browserLoaded(gTestBrowser).then(test1A);
   var url = gHttpTestRoot1 + "file_bug902156_1.html";
-  gTestBrowser.contentWindow.location = url;
+  gTestBrowser.loadURI(url);
 }
--- a/browser/base/content/test/general/browser_findbarClose.js
+++ b/browser/base/content/test/general/browser_findbarClose.js
@@ -1,40 +1,35 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests find bar auto-close behavior
 
-var newTab, iframe;
+var newTab;
 
-function test() {
+add_task(function* findbar_test() {
   waitForExplicitFinish();
   newTab = gBrowser.addTab("about:blank");
-  newTab.linkedBrowser.addEventListener("DOMContentLoaded",
-    prepareTestFindBarStaysOpenOnSubdocumentLocationChange, false);
-  newTab.linkedBrowser.contentWindow.location = "http://example.com/browser/" +
-    "browser/base/content/test/general/test_bug628179.html";
-}
 
-function prepareTestFindBarStaysOpenOnSubdocumentLocationChange() {
-  newTab.linkedBrowser.removeEventListener("DOMContentLoaded",
-    prepareTestFindBarStaysOpenOnSubdocumentLocationChange, false);
+  let promise = ContentTask.spawn(newTab.linkedBrowser, null, function* () {
+    yield ContentTaskUtils.waitForEvent(this, "DOMContentLoaded", false);
+  });
+  newTab.linkedBrowser.loadURI("http://example.com/browser/" +
+    "browser/base/content/test/general/test_bug628179.html");
+  yield promise;
 
   gFindBar.open();
 
-  iframe = newTab.linkedBrowser.contentDocument.getElementById("iframe");
-  iframe.addEventListener("load",
-    testFindBarStaysOpenOnSubdocumentLocationChange, false);
-  iframe.src = "http://example.org/";
-}
-
-function testFindBarStaysOpenOnSubdocumentLocationChange() {
-  iframe.removeEventListener("load",
-    testFindBarStaysOpenOnSubdocumentLocationChange, false);
+  yield new ContentTask.spawn(newTab.linkedBrowser, null, function* () {
+    let iframe = content.document.getElementById("iframe");
+    let promise = ContentTaskUtils.waitForEvent(iframe, "load", false);
+    iframe.src = "http://example.org/";
+    yield promise;
+  });
 
   ok(!gFindBar.hidden, "the Find bar isn't hidden after the location of a " +
      "subdocument changes");
 
   gFindBar.close();
   gBrowser.removeTab(newTab);
   finish();
-}
+});
 
--- a/browser/base/content/test/general/browser_hide_removing.js
+++ b/browser/base/content/test/general/browser_hide_removing.js
@@ -2,16 +2,22 @@
  * 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/. */
 
 // Bug 587922: tabs don't get removed if they're hidden
 
 function test() {
   waitForExplicitFinish();
 
+  // Ensure TabView has been initialized already. Otherwise it could
+  // activate at an unexpected time and show/hide tabs.
+  TabView._initFrame(runTest);
+}
+
+function runTest() {
   // Add a tab that will get removed and hidden
   let testTab = gBrowser.addTab("about:blank", {skipAnimation: true});
   is(gBrowser.visibleTabs.length, 2, "just added a tab, so 2 tabs");
   gBrowser.selectedTab = testTab;
 
   let numVisBeforeHide, numVisAfterHide;
   gBrowser.tabContainer.addEventListener("TabSelect", function() {
     gBrowser.tabContainer.removeEventListener("TabSelect", arguments.callee, false);
--- a/browser/base/content/test/general/browser_popupUI.js
+++ b/browser/base/content/test/general/browser_popupUI.js
@@ -1,24 +1,23 @@
 function test() {
   waitForExplicitFinish();
   gPrefService.setBoolPref("dom.disable_open_during_load", false);
 
   var browser = gBrowser.selectedBrowser;
-  browser.addEventListener("load", function () {
-    browser.removeEventListener("load", arguments.callee, true);
-
+  BrowserTestUtils.browserLoaded(browser).then(() => {
     if (gPrefService.prefHasUserValue("dom.disable_open_during_load"))
       gPrefService.clearUserPref("dom.disable_open_during_load");
 
     findPopup();
-  }, true);
+  });
 
-  content.location =
-    "data:text/html,<html><script>popup=open('about:blank','','width=300,height=200')</script>";
+  browser.loadURI(
+    "data:text/html,<html><script>popup=open('about:blank','','width=300,height=200')</script>"
+  );
 }
 
 function findPopup() {
   var enumerator = Services.wm.getEnumerator("navigator:browser");
 
   while (enumerator.hasMoreElements()) {
     let win = enumerator.getNext();
     if (win.content.wrappedJSObject == content.wrappedJSObject.popup) {
--- a/browser/base/content/test/general/browser_relatedTabs.js
+++ b/browser/base/content/test/general/browser_relatedTabs.js
@@ -1,49 +1,52 @@
 /* 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/. */
 
-function test() {
+add_task(function*() {
   is(gBrowser.tabs.length, 1, "one tab is open initially");
 
   // Add several new tabs in sequence, interrupted by selecting a
   // different tab, moving a tab around and closing a tab,
   // returning a list of opened tabs for verifying the expected order.
   // The new tab behaviour is documented in bug 465673
   let tabs = [];
+  let promises = [];
   function addTab(aURL, aReferrer) {
-    tabs.push(gBrowser.addTab(aURL, {referrerURI: aReferrer}));
+    let tab = gBrowser.addTab(aURL, {referrerURI: aReferrer});
+    tabs.push(tab);
+    return BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   }
 
-  addTab("http://mochi.test:8888/#0");
+  yield addTab("http://mochi.test:8888/#0");
   gBrowser.selectedTab = tabs[0];
-  addTab("http://mochi.test:8888/#1");
-  addTab("http://mochi.test:8888/#2", gBrowser.currentURI);
-  addTab("http://mochi.test:8888/#3", gBrowser.currentURI);
+  yield addTab("http://mochi.test:8888/#1");
+  yield addTab("http://mochi.test:8888/#2", gBrowser.currentURI);
+  yield addTab("http://mochi.test:8888/#3", gBrowser.currentURI);
   gBrowser.selectedTab = tabs[tabs.length - 1];
   gBrowser.selectedTab = tabs[0];
-  addTab("http://mochi.test:8888/#4", gBrowser.currentURI);
+  yield addTab("http://mochi.test:8888/#4", gBrowser.currentURI);
   gBrowser.selectedTab = tabs[3];
-  addTab("http://mochi.test:8888/#5", gBrowser.currentURI);
+  yield addTab("http://mochi.test:8888/#5", gBrowser.currentURI);
   gBrowser.removeTab(tabs.pop());
-  addTab("about:blank", gBrowser.currentURI);
+  yield addTab("about:blank", gBrowser.currentURI);
   gBrowser.moveTabTo(gBrowser.selectedTab, 1);
-  addTab("http://mochi.test:8888/#6", gBrowser.currentURI);
-  addTab();
-  addTab("http://mochi.test:8888/#7");
+  yield addTab("http://mochi.test:8888/#6", gBrowser.currentURI);
+  yield addTab();
+  yield addTab("http://mochi.test:8888/#7");
 
   function testPosition(tabNum, expectedPosition, msg) {
     is(Array.indexOf(gBrowser.tabs, tabs[tabNum]), expectedPosition, msg);
   }
 
   testPosition(0, 3, "tab without referrer was opened to the far right");
   testPosition(1, 7, "tab without referrer was opened to the far right");
   testPosition(2, 5, "tab with referrer opened immediately to the right");
   testPosition(3, 1, "next tab with referrer opened further to the right");
   testPosition(4, 4, "tab selection changed, tab opens immediately to the right");
   testPosition(5, 6, "blank tab with referrer opens to the right of 3rd original tab where removed tab was");
   testPosition(6, 2, "tab has moved, new tab opens immediately to the right");
   testPosition(7, 8, "blank tab without referrer opens at the end");
   testPosition(8, 9, "tab without referrer opens at the end");
 
   tabs.forEach(gBrowser.removeTab, gBrowser);
-}
+});
--- a/browser/base/content/test/general/browser_selectTabAtIndex.js
+++ b/browser/base/content/test/general/browser_selectTabAtIndex.js
@@ -13,10 +13,10 @@ function test() {
        (isLinux ? "Alt" : "Accel") + "+" + i + " selects expected tab");
   }
 
   gBrowser.selectTabAtIndex(-3);
   is(gBrowser.tabContainer.selectedIndex, gBrowser.tabs.length - 3,
      "gBrowser.selectTabAtIndex(-3) selects expected tab");
 
   for (let i = 0; i < 9; i++)
-    gBrowser.removeCurrentTab();
+    gBrowser.removeTab(gBrowser.selectedTab, {skipPermitUnload: true});
 }
--- a/browser/base/content/test/general/browser_utilityOverlay.js
+++ b/browser/base/content/test/general/browser_utilityOverlay.js
@@ -36,27 +36,25 @@ function test_getBoolPref() {
   is(getBoolPref("this.pref.doesnt.exist", true), true, "getBoolPref fallback");
   is(getBoolPref("this.pref.doesnt.exist", false), false, "getBoolPref fallback #2");
   runNextTest();
 }
 
 function test_openNewTabWith() {
   openNewTabWith("http://example.com/");
   let tab = gBrowser.selectedTab = gBrowser.tabs[1];
-  tab.linkedBrowser.addEventListener("load", function onLoad(event) {
-    tab.linkedBrowser.removeEventListener("load", onLoad, true);
+  BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(() => {
     is(tab.linkedBrowser.currentURI.spec, "http://example.com/", "example.com loaded");
     gBrowser.removeCurrentTab();
     runNextTest();
-  }, true);
+  });
 }
 
 function test_openUILink() {
   let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
-  tab.linkedBrowser.addEventListener("load", function onLoad(event) {
-    tab.linkedBrowser.removeEventListener("load", onLoad, true);
+  BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(() => {
     is(tab.linkedBrowser.currentURI.spec, "http://example.org/", "example.org loaded");
     gBrowser.removeCurrentTab();
     runNextTest();
-  }, true);
+  });
 
   openUILink("http://example.org/"); // defaults to "current"
 }
--- a/browser/base/content/test/general/browser_visibleTabs.js
+++ b/browser/base/content/test/general/browser_visibleTabs.js
@@ -1,13 +1,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/. */
 
-function test() {
+add_task(function* () {
+  // Ensure TabView has been initialized already. Otherwise it could
+  // activate at an unexpected time and show/hide tabs.
+  yield new Promise(resolve => TabView._initFrame(resolve));
+
   // There should be one tab when we start the test
   let [origTab] = gBrowser.visibleTabs;
 
   // Add a tab that will get pinned
   let pinned = gBrowser.addTab();
   gBrowser.pinTab(pinned);
 
   let testTab = gBrowser.addTab();
@@ -95,9 +99,10 @@ function test() {
   gBrowser.removeTab(testTab);
   is(gBrowser.visibleTabs.length, 1, "only orig is left and visible");
   is(gBrowser.tabs.length, 1, "sanity check that it matches");
   is(gBrowser.selectedTab, origTab, "got the orig tab");
   is(origTab.hidden, false, "and it's not hidden -- visible!");
 
   if (tabViewWindow)
     tabViewWindow.GroupItems.groupItems[0].close();
-}
+});
+
--- a/browser/base/content/test/general/browser_visibleTabs_bookmarkAllPages.js
+++ b/browser/base/content/test/general/browser_visibleTabs_bookmarkAllPages.js
@@ -1,15 +1,21 @@
 /* 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/. */
 
 function test() {
   waitForExplicitFinish();
 
+  // Ensure TabView has been initialized already. Otherwise it could
+  // activate at an unexpected time and show/hide tabs.
+  TabView._initFrame(runTest);
+}
+
+function runTest() {
   let tabOne = gBrowser.addTab("about:blank");
   let tabTwo = gBrowser.addTab("http://mochi.test:8888/");
   gBrowser.selectedTab = tabTwo;
 
   let browser = gBrowser.getBrowserForTab(tabTwo);
   let onLoad = function() {
     browser.removeEventListener("load", onLoad, true);
 
--- a/browser/base/content/test/general/browser_visibleTabs_bookmarkAllTabs.js
+++ b/browser/base/content/test/general/browser_visibleTabs_bookmarkAllTabs.js
@@ -1,15 +1,21 @@
 /* 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/. */
 
 function test() {
   waitForExplicitFinish();
 
+  // Ensure TabView has been initialized already. Otherwise it could
+  // activate at an unexpected time and show/hide tabs.
+  TabView._initFrame(runTest);
+}
+
+function runTest() {
   // There should be one tab when we start the test
   let [origTab] = gBrowser.visibleTabs;
   is(gBrowser.visibleTabs.length, 1, "1 tab should be open");  
   is(Disabled(), true, "Bookmark All Tabs should be disabled");
 
   // Add a tab
   let testTab1 = gBrowser.addTab();
   is(gBrowser.visibleTabs.length, 2, "2 tabs should be open");
--- a/browser/base/content/test/general/browser_visibleTabs_contextMenu.js
+++ b/browser/base/content/test/general/browser_visibleTabs_contextMenu.js
@@ -1,13 +1,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/. */
 
-function test() {
+add_task(function* test() {
+  // Ensure TabView has been initialized already. Otherwise it could
+  // activate at an unexpected time and show/hide tabs.
+  yield new Promise(resolve => TabView._initFrame(resolve));
+
   // There should be one tab when we start the test
   let [origTab] = gBrowser.visibleTabs;
   is(gBrowser.visibleTabs.length, 1, "there is one visible tab");
   let testTab = gBrowser.addTab();
   is(gBrowser.visibleTabs.length, 2, "there are now two visible tabs");
 
   // Check the context menu with two tabs
   updateTabContextMenu(origTab);
@@ -46,9 +50,10 @@ function test() {
   
   // Check the context menu of the original tab
   // Close Tabs To The End should now be enabled
   updateTabContextMenu(origTab);
   is(document.getElementById("context_closeTabsToTheEnd").disabled, false, "Close Tabs To The End is enabled");
 
   gBrowser.removeTab(testTab);
   gBrowser.removeTab(pinned);
-}
+});
+
--- a/browser/base/content/test/general/browser_visibleTabs_tabPreview.js
+++ b/browser/base/content/test/general/browser_visibleTabs_tabPreview.js
@@ -1,13 +1,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/. */
 
-function test() {
+add_task(function* test() {
+  // Ensure TabView has been initialized already. Otherwise it could
+  // activate at an unexpected time and show/hide tabs.
+  yield new Promise(resolve => TabView._initFrame(resolve));
+
   gPrefService.setBoolPref("browser.ctrlTab.previews", true);
 
   let [origTab] = gBrowser.visibleTabs;
   let tabOne = gBrowser.addTab();
   let tabTwo = gBrowser.addTab();
 
   // test the ctrlTab.tabList
   pressCtrlTab();
@@ -25,17 +29,17 @@ function test() {
   releaseCtrl();
 
   // cleanup
   gBrowser.removeTab(tabOne);
   gBrowser.removeTab(tabTwo);
 
   if (gPrefService.prefHasUserValue("browser.ctrlTab.previews"))
     gPrefService.clearUserPref("browser.ctrlTab.previews");
-}
+});
 
 function pressCtrlTab(aShiftKey) {
   EventUtils.synthesizeKey("VK_TAB", { ctrlKey: true, shiftKey: !!aShiftKey });
 }
 
 function releaseCtrl() {
   EventUtils.synthesizeKey("VK_CONTROL", { type: "keyup" });
 }
--- a/browser/base/content/test/social/head.js
+++ b/browser/base/content/test/social/head.js
@@ -411,17 +411,17 @@ function loadIntoTab(tab, url, callback)
     tab.linkedBrowser.removeEventListener("load", tabLoad, true);
     executeSoon(function() {callback(tab)});
   }, true);
   tab.linkedBrowser.loadURI(url);
 }
 
 function ensureBrowserTabClosed(tab) {
   let promise = ensureEventFired(gBrowser.tabContainer, "TabClose");
-  gBrowser.removeTab(tab);
+  gBrowser.removeTab(tab, {skipPermitUnload: true});
   return promise;
 }
 
 function ensureFrameLoaded(frame) {
   let deferred = Promise.defer();
   if (frame.contentDocument && frame.contentDocument.readyState == "complete") {
     deferred.resolve();
   } else {
--- a/browser/components/sessionstore/test/browser_522545.js
+++ b/browser/components/sessionstore/test/browser_522545.js
@@ -1,17 +1,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/. */
 
 function test() {
   /** Test for Bug 522545 **/
 
   waitForExplicitFinish();
-  requestLongerTimeout(2);
+  requestLongerTimeout(3);
 
   // This tests the following use case:
   // User opens a new tab which gets focus. The user types something into the
   // address bar, then crashes or quits.
   function test_newTabFocused() {
     let state = {
       windows: [{
         tabs: [
@@ -154,22 +154,20 @@ function test() {
     function firstLocationChange() {
       let state = JSON.parse(ss.getBrowserState());
       let hasUTV = state.windows[0].tabs.some(function(aTab) {
         return aTab.userTypedValue && aTab.userTypedClear && !aTab.entries.length;
       });
 
       ok(hasUTV, "At least one tab has a userTypedValue with userTypedClear with no loaded URL");
 
-      gBrowser.addEventListener("load", firstLoad, true);
+      BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(firstLoad);
     }
 
     function firstLoad() {
-      gBrowser.removeEventListener("load", firstLoad, true);
-
       let state = JSON.parse(ss.getBrowserState());
       let hasSH = state.windows[0].tabs.some(function(aTab) {
         return !("userTypedValue" in aTab) && aTab.entries[0].url;
       });
 
       ok(hasSH, "At least one tab has its entry in SH");
 
       runNextTest();
--- a/browser/components/sessionstore/test/browser_607016.js
+++ b/browser/components/sessionstore/test/browser_607016.js
@@ -1,16 +1,20 @@
 "use strict";
 
 var stateBackup = ss.getBrowserState();
 
 add_task(function* () {
   /** Bug 607016 - If a tab is never restored, attributes (eg. hidden) aren't updated correctly **/
   ignoreAllUncaughtExceptions();
 
+  // Ensure TabView has been initialized already. Otherwise it could
+  // activate at an unexpected time and show/hide tabs.
+  yield new Promise(resolve => TabView._initFrame(resolve));
+
   // Set the pref to true so we know exactly how many tabs should be restoring at
   // any given time. This guarantees that a finishing load won't start another.
   Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true);
 
   let state = { windows: [{ tabs: [
     { entries: [{ url: "http://example.org#1" }], extData: { "uniq": r() } },
     { entries: [{ url: "http://example.org#2" }], extData: { "uniq": r() } }, // overwriting
     { entries: [{ url: "http://example.org#3" }], extData: { "uniq": r() } }, // hiding
--- a/browser/components/sessionstore/test/browser_635418.js
+++ b/browser/components/sessionstore/test/browser_635418.js
@@ -2,16 +2,22 @@
  * 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/. */
 
 // This tests that hiding/showing a tab, on its own, eventually triggers a
 // session store.
 
 function test() {
   waitForExplicitFinish();
+  // Ensure TabView has been initialized already. Otherwise it could
+  // activate at an unexpected time and show/hide tabs.
+  TabView._initFrame(runTest);
+}
+
+function runTest() {
   // We speed up the interval between session saves to ensure that the test
   // runs quickly.
   Services.prefs.setIntPref("browser.sessionstore.interval", 2000);
 
   // Loading a tab causes a save state and this is meant to catch that event.
   waitForSaveState(testBug635418_1);
 
   // Assumption: Only one window is open and it has one tab open.
--- a/browser/modules/RemotePrompt.jsm
+++ b/browser/modules/RemotePrompt.jsm
@@ -34,21 +34,28 @@ var RemotePrompt = {
     }
   },
 
   openTabPrompt: function(args, browser) {
     let window = browser.ownerDocument.defaultView;
     let tabPrompt = window.gBrowser.getTabModalPromptBox(browser)
     let callbackInvoked = false;
     let newPrompt;
+    let needRemove = false;
     let promptId = args._remoteId;
 
     function onPromptClose(forceCleanup) {
+      // It's possible that we removed the prompt during the
+      // appendPrompt call below. In that case, newPrompt will be
+      // undefined. We set the needRemove flag to remember to remove
+      // it right after we've finished adding it.
       if (newPrompt)
         tabPrompt.removePrompt(newPrompt);
+      else
+        needRemove = true;
 
       PromptUtils.fireDialogEvent(window, "DOMModalDialogClosed", browser);
       browser.messageManager.sendAsyncMessage("Prompt:Close", args);
     }
 
     browser.messageManager.addMessageListener("Prompt:ForceClose", function listener(message) {
       // If this was for another prompt in the same tab, ignore it.
       if (message.data._remoteId !== promptId) {
@@ -69,16 +76,20 @@ var RemotePrompt = {
         inPermitUnload: args.inPermitUnload,
       };
       PromptUtils.fireDialogEvent(window, "DOMWillOpenModalDialog", browser, eventDetail);
 
       args.promptActive = true;
 
       newPrompt = tabPrompt.appendPrompt(args, onPromptClose);
 
+      if (needRemove) {
+        tabPrompt.removePrompt(newPrompt);
+      }
+
       // TODO since we don't actually open a window, need to check if
       // there's other stuff in nsWindowWatcher::OpenWindowInternal
       // that we might need to do here as well.
     } catch (ex) {
       onPromptClose(true);
     }
   },
 
--- a/devtools/client/framework/test/browser_target_events.js
+++ b/devtools/client/framework/test/browser_target_events.js
@@ -4,22 +4,20 @@
 
 var target;
 
 function test()
 {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", onLoad, true);
+  BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(onLoad);
 }
 
-function onLoad(evt) {
-  gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true);
-
+function onLoad() {
   target = TargetFactory.forTab(gBrowser.selectedTab);
 
   is(target.tab, gBrowser.selectedTab, "Target linked to the right tab.");
 
   target.once("hidden", onHidden);
   gBrowser.selectedTab = gBrowser.addTab();
 }
 
--- a/devtools/client/locales/en-US/memory.properties
+++ b/devtools/client/locales/en-US/memory.properties
@@ -56,16 +56,21 @@ filter.placeholder=Filter
 # a link in the heap tree to jump to the debugger view.
 # %S represents the URL to match in the debugger.
 viewsourceindebugger=View source in Debugger → %S
 
 # LOCALIZATION NOTE (tree-item.nostack): The label describing the row in the heap tree
 # that represents a row broken down by allocation stack when no stack was available.
 tree-item.nostack=(no stack available)
 
+# LOCALIZATION NOTE (tree-item.nofilename): The label describing the row in the
+# heap tree that represents a row broken down by filename when no filename was
+# available.
+tree-item.nofilename=(no filename available)
+
 # LOCALIZATION NOTE (tree-item.root): The label describing the row in the heap tree
 # that represents the root of the tree when inverted.
 tree-item.root=(root)
 
 # LOCALIZATION NOTE (tree-item.percent): A percent of bytes or count displayed in the tree view.
 tree-item.percent=%S%
 
 # LOCALIZATION NOTE (snapshot.state.saving.full): The label describing the snapshot
--- a/devtools/client/memory/components/tree-item.js
+++ b/devtools/client/memory/components/tree-item.js
@@ -54,14 +54,27 @@ const TreeItem = module.exports = create
       dom.span({ className: "heap-tree-item-field heap-tree-item-name", style: { marginLeft: depth * INDENT }},
         arrow,
         this.toLabel(item.name, toolbox)
       )
     );
   },
 
   toLabel(name, toolbox) {
-    return isSavedFrame(name) ? FrameView({ frame: name, toolbox }) :
-           name === "noStack" ? L10N.getStr("tree-item.nostack") :
-           name === null ? L10N.getStr("tree-item.root") :
-           String(name);
+    if (isSavedFrame(name)) {
+      return FrameView({ frame: name, toolbox });
+    }
+
+    if (name === null) {
+      return L10N.getStr("tree-item.root");
+    }
+
+    if (name === "noStack") {
+      return L10N.getStr("tree-item.nostack");
+    }
+
+    if (name === "noFilename") {
+      return L10N.getStr("tree-item.nofilename");
+    }
+
+    return String(name);
   },
 });
--- a/devtools/client/memory/constants.js
+++ b/devtools/client/memory/constants.js
@@ -51,17 +51,21 @@ const OBJECT_CLASS = { by: "objectClass"
 
 const breakdowns = exports.breakdowns = {
   coarseType: {
     displayName: "Coarse Type",
     breakdown: {
       by: "coarseType",
       objects: OBJECT_CLASS,
       strings: COUNT,
-      scripts: INTERNAL_TYPE,
+      scripts: {
+        by: "filename",
+        then: INTERNAL_TYPE,
+        noFilename: INTERNAL_TYPE
+      },
       other: INTERNAL_TYPE,
     }
   },
 
   allocationStack: {
     displayName: "Allocation Stack",
     breakdown: ALLOCATION_STACK,
   },
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -102,17 +102,18 @@ function addTab(aUrl, aWindow) {
 }
 
 function removeTab(aTab, aWindow) {
   info("Removing tab.");
 
   let targetWindow = aWindow || window;
   let targetBrowser = targetWindow.gBrowser;
 
-  targetBrowser.removeTab(aTab);
+  // browser_net_pane-toggle.js relies on synchronous removeTab behavior.
+  targetBrowser.removeTab(aTab, {skipPermitUnload: true});
 }
 
 function waitForNavigation(aTarget) {
   let deferred = promise.defer();
   aTarget.once("will-navigate", () => {
     aTarget.once("navigate", () => {
       deferred.resolve();
     });
--- a/devtools/client/shared/test/browser_mdn-docs-01.js
+++ b/devtools/client/shared/test/browser_mdn-docs-01.js
@@ -126,36 +126,32 @@ function* testTheBasics(widget) {
   *
   * One complexity is that when you open a new tab,
   * "about:blank" is first loaded into the tab before the
   * actual page. So we ignore that first load event, and keep
   * listening until "load" is triggered for a different URI.
   */
 function checkLinkClick(link) {
 
-  function loadListener(e) {
-    let tab = e.target;
+  function loadListener(tab) {
     var browser = getBrowser().getBrowserForTab(tab);
     var uri = browser.currentURI.spec;
-    // this is horrible, and it's because when we open a new tab
-    // "about:blank: is first loaded into it, before the actual
-    // document we want to load.
-    if (uri != "about:blank") {
-      info("New browser tab has loaded");
-      tab.removeEventListener("load", loadListener);
-      gBrowser.removeTab(tab);
-      info("Resolve promise with new tab URI");
-      deferred.resolve(uri);
-    }
+
+    info("New browser tab has loaded");
+    gBrowser.removeTab(tab);
+    info("Resolve promise with new tab URI");
+    deferred.resolve(uri);
   }
 
   function newTabListener(e) {
     gBrowser.tabContainer.removeEventListener("TabOpen", newTabListener);
     var tab = e.target;
-    tab.addEventListener("load", loadListener, false);
+    BrowserTestUtils.browserLoaded(tab.linkedBrowser, false,
+                                   url => { return url != "about:blank"; })
+      .then(url => loadListener(tab));
   }
 
   let deferred = promise.defer();
   info("Check that clicking the link opens a new tab with the correct URI");
   gBrowser.tabContainer.addEventListener("TabOpen", newTabListener, false);
   info("Click the link to MDN");
   link.click();
   return deferred.promise;
--- a/devtools/client/shared/test/head.js
+++ b/devtools/client/shared/test/head.js
@@ -22,27 +22,26 @@ const OPTIONS_VIEW_URL = TEST_URI_ROOT +
 /**
  * Open a new tab at a URL and call a callback on load
  */
 function addTab(aURL, aCallback)
 {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
-  content.location = aURL;
-
   let tab = gBrowser.selectedTab;
   let browser = gBrowser.getBrowserForTab(tab);
 
-  function onTabLoad() {
-    browser.removeEventListener("load", onTabLoad, true);
+  let url = encodeURI(aURL);
+
+  BrowserTestUtils.browserLoaded(browser, false, url).then(() => {
     aCallback(browser, tab, browser.contentDocument);
-  }
+  });
 
-  browser.addEventListener("load", onTabLoad, true);
+  browser.loadURI(url);
 }
 
 function promiseTab(aURL) {
   return new Promise(resolve =>
     addTab(aURL, resolve));
 }
 
 registerCleanupFunction(function* tearDown() {
--- a/devtools/client/sourceeditor/test/browser_css_getInfo.js
+++ b/devtools/client/sourceeditor/test/browser_css_getInfo.js
@@ -118,22 +118,21 @@ const TEST_URI = "data:text/html;charset
    " </body>",
    " </html>"
   ].join("\n"));
 
 var doc = null;
 function test() {
   waitForExplicitFinish();
   gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function onload() {
-    gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+  BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => {
     doc = content.document;
     runTests();
-  }, true);
-  content.location = TEST_URI;
+  });
+  gBrowser.loadURI(TEST_URI);
 }
 
 function runTests() {
   let completer = new cssAutoCompleter();
   let matches = (arr, toCheck) => !arr.some((x, i) => x != toCheck[i]);
   let checkState = (expected, actual) => {
     if (expected[0] == "null" && actual == null) {
       return true;
--- a/devtools/client/sourceeditor/test/browser_css_statemachine.js
+++ b/devtools/client/sourceeditor/test/browser_css_statemachine.js
@@ -53,23 +53,21 @@ const TEST_URI = "data:text/html;charset
    "  </div>",
    " </body>",
    " </html>"
   ].join("\n"));
 
 var doc = null;
 function test() {
   waitForExplicitFinish();
-  gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function onload() {
-    gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+  gBrowser.selectedTab = gBrowser.addTab(TEST_URI);
+  BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => {
     doc = content.document;
     runTests();
-  }, true);
-  content.location = TEST_URI;
+  });
 }
 
 function runTests() {
   let completer = new cssAutoCompleter();
   let checkState = state => {
     if (state[0] == 'null' && (!completer.state || completer.state == 'null')) {
       return true;
     } else if (state[0] == completer.state && state[0] == 'selector' &&
--- a/devtools/client/sourceeditor/test/helper_codemirror_runner.js
+++ b/devtools/client/sourceeditor/test/helper_codemirror_runner.js
@@ -23,13 +23,13 @@ function runCodeMirrorTest(browser) {
   //     setting a timeout to check again if not.
   mm.loadFrameScript('data:,' +
     'content.wrappedJSObject.mozilla_setStatus = function(statusMsg, type, customMsg) {' +
     '  sendSyncMessage("setStatus", {statusMsg: statusMsg, type: type, customMsg: customMsg});' +
     '};' +
     'function check() { ' +
     '  var doc = content.document; var out = doc.getElementById("status"); ' +
     '  if (!out || !out.classList.contains("done")) { return setTimeout(check, 100); }' +
-    '  sendSyncMessage("done", { failed: content.wrappedJSObject.failed });' +
+    '  sendAsyncMessage("done", { failed: content.wrappedJSObject.failed });' +
     '}' +
     'check();'
   , true);
-}
\ No newline at end of file
+}
--- a/devtools/shared/heapsnapshot/CensusUtils.js
+++ b/devtools/shared/heapsnapshot/CensusUtils.js
@@ -92,21 +92,29 @@ EDGES.coarseType = function (breakdown, 
 
 EDGES.allocationStack = function (breakdown, report) {
   const edges = [];
   report.forEach((value, key) => {
     edges.push({
       edge: key,
       referent: value,
       breakdown: key === "noStack" ? breakdown.noStack : breakdown.then
-    })
+    });
   });
   return edges;
 };
 
+EDGES.filename = function (breakdown, report) {
+  return Object.keys(report).map(key => ({
+    edge: key,
+    referent: report[key],
+    breakdown: key === "noFilename" ? breakdown.noFilename : breakdown.then
+  }));
+};
+
 /**
  * Get the set of outgoing edges from `report` as specified by the given
  * breakdown.
  *
  * @param {Object} breakdown
  *        The census breakdown.
  *
  * @param {Object} report
--- a/devtools/shared/heapsnapshot/CoreDump.pb.cc
+++ b/devtools/shared/heapsnapshot/CoreDump.pb.cc
@@ -44,16 +44,18 @@ struct StackFrame_DataOneofInstance {
 const ::google::protobuf::Descriptor* Node_descriptor_ = NULL;
 const ::google::protobuf::internal::GeneratedMessageReflection*
   Node_reflection_ = NULL;
 struct NodeOneofInstance {
   const ::std::string* typename__;
   ::google::protobuf::uint64 typenameref_;
   const ::std::string* jsobjectclassname_;
   ::google::protobuf::uint64 jsobjectclassnameref_;
+  const ::std::string* scriptfilename_;
+  ::google::protobuf::uint64 scriptfilenameref_;
 }* Node_default_oneof_instance_ = NULL;
 const ::google::protobuf::Descriptor* Edge_descriptor_ = NULL;
 const ::google::protobuf::internal::GeneratedMessageReflection*
   Edge_reflection_ = NULL;
 struct EdgeOneofInstance {
   const ::std::string* name_;
   ::google::protobuf::uint64 nameref_;
 }* Edge_default_oneof_instance_ = NULL;
@@ -125,28 +127,31 @@ void protobuf_AssignDesc_CoreDump_2eprot
       GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(StackFrame_Data, _unknown_fields_),
       -1,
       StackFrame_Data_default_oneof_instance_,
       GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(StackFrame_Data, _oneof_case_[0]),
       ::google::protobuf::DescriptorPool::generated_pool(),
       ::google::protobuf::MessageFactory::generated_factory(),
       sizeof(StackFrame_Data));
   Node_descriptor_ = file->message_type(2);
-  static const int Node_offsets_[11] = {
+  static const int Node_offsets_[14] = {
     GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, id_),
     PROTO2_GENERATED_DEFAULT_ONEOF_FIELD_OFFSET(Node_default_oneof_instance_, typename__),
     PROTO2_GENERATED_DEFAULT_ONEOF_FIELD_OFFSET(Node_default_oneof_instance_, typenameref_),
     GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, size_),
     GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, edges_),
     GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, allocationstack_),
     PROTO2_GENERATED_DEFAULT_ONEOF_FIELD_OFFSET(Node_default_oneof_instance_, jsobjectclassname_),
     PROTO2_GENERATED_DEFAULT_ONEOF_FIELD_OFFSET(Node_default_oneof_instance_, jsobjectclassnameref_),
     GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, coarsetype_),
+    PROTO2_GENERATED_DEFAULT_ONEOF_FIELD_OFFSET(Node_default_oneof_instance_, scriptfilename_),
+    PROTO2_GENERATED_DEFAULT_ONEOF_FIELD_OFFSET(Node_default_oneof_instance_, scriptfilenameref_),
     GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, TypeNameOrRef_),
     GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, JSObjectClassNameOrRef_),
+    GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, ScriptFilenameOrRef_),
   };
   Node_reflection_ =
     new ::google::protobuf::internal::GeneratedMessageReflection(
       Node_descriptor_,
       Node::default_instance_,
       Node_offsets_,
       GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, _has_bits_[0]),
       GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, _unknown_fields_),
@@ -232,27 +237,29 @@ void protobuf_AddDesc_CoreDump_2eproto()
     "ls.protobuf.StackFrame.DataH\000\022\r\n\003ref\030\002 \001"
     "(\004H\000\032\242\002\n\004Data\022\n\n\002id\030\001 \001(\004\0225\n\006parent\030\002 \001("
     "\0132%.mozilla.devtools.protobuf.StackFrame"
     "\022\014\n\004line\030\003 \001(\r\022\016\n\006column\030\004 \001(\r\022\020\n\006source"
     "\030\005 \001(\014H\000\022\023\n\tsourceRef\030\006 \001(\004H\000\022\035\n\023functio"
     "nDisplayName\030\007 \001(\014H\001\022 \n\026functionDisplayN"
     "ameRef\030\010 \001(\004H\001\022\020\n\010isSystem\030\t \001(\010\022\024\n\014isSe"
     "lfHosted\030\n \001(\010B\r\n\013SourceOrRefB\032\n\030Functio"
-    "nDisplayNameOrRefB\020\n\016StackFrameType\"\272\002\n\004"
+    "nDisplayNameOrRefB\020\n\016StackFrameType\"\210\003\n\004"
     "Node\022\n\n\002id\030\001 \001(\004\022\022\n\010typeName\030\002 \001(\014H\000\022\025\n\013"
     "typeNameRef\030\003 \001(\004H\000\022\014\n\004size\030\004 \001(\004\022.\n\005edg"
     "es\030\005 \003(\0132\037.mozilla.devtools.protobuf.Edg"
     "e\022>\n\017allocationStack\030\006 \001(\0132%.mozilla.dev"
     "tools.protobuf.StackFrame\022\033\n\021jsObjectCla"
     "ssName\030\007 \001(\014H\001\022\036\n\024jsObjectClassNameRef\030\010"
-    " \001(\004H\001\022\025\n\ncoarseType\030\t \001(\r:\0010B\017\n\rTypeNam"
-    "eOrRefB\030\n\026JSObjectClassNameOrRef\"L\n\004Edge"
-    "\022\020\n\010referent\030\001 \001(\004\022\016\n\004name\030\002 \001(\014H\000\022\021\n\007na"
-    "meRef\030\003 \001(\004H\000B\017\n\rEdgeNameOrRef", 870);
+    " \001(\004H\001\022\025\n\ncoarseType\030\t \001(\r:\0010\022\030\n\016scriptF"
+    "ilename\030\n \001(\014H\002\022\033\n\021scriptFilenameRef\030\013 \001"
+    "(\004H\002B\017\n\rTypeNameOrRefB\030\n\026JSObjectClassNa"
+    "meOrRefB\025\n\023ScriptFilenameOrRef\"L\n\004Edge\022\020"
+    "\n\010referent\030\001 \001(\004\022\016\n\004name\030\002 \001(\014H\000\022\021\n\007name"
+    "Ref\030\003 \001(\004H\000B\017\n\rEdgeNameOrRef", 948);
   ::google::protobuf::MessageFactory::InternalRegisterGeneratedFile(
     "CoreDump.proto", &protobuf_RegisterTypes);
   Metadata::default_instance_ = new Metadata();
   StackFrame::default_instance_ = new StackFrame();
   StackFrame_default_oneof_instance_ = new StackFrameOneofInstance;
   StackFrame_Data::default_instance_ = new StackFrame_Data();
   StackFrame_Data_default_oneof_instance_ = new StackFrame_DataOneofInstance;
   Node::default_instance_ = new Node();
@@ -1457,30 +1464,34 @@ const int Node::kIdFieldNumber;
 const int Node::kTypeNameFieldNumber;
 const int Node::kTypeNameRefFieldNumber;
 const int Node::kSizeFieldNumber;
 const int Node::kEdgesFieldNumber;
 const int Node::kAllocationStackFieldNumber;
 const int Node::kJsObjectClassNameFieldNumber;
 const int Node::kJsObjectClassNameRefFieldNumber;
 const int Node::kCoarseTypeFieldNumber;
+const int Node::kScriptFilenameFieldNumber;
+const int Node::kScriptFilenameRefFieldNumber;
 #endif  // !_MSC_VER
 
 Node::Node()
   : ::google::protobuf::Message() {
   SharedCtor();
   // @@protoc_insertion_point(constructor:mozilla.devtools.protobuf.Node)
 }
 
 void Node::InitAsDefaultInstance() {
   Node_default_oneof_instance_->typename__ = &::google::protobuf::internal::GetEmptyStringAlreadyInited();
   Node_default_oneof_instance_->typenameref_ = GOOGLE_ULONGLONG(0);
   allocationstack_ = const_cast< ::mozilla::devtools::protobuf::StackFrame*>(&::mozilla::devtools::protobuf::StackFrame::default_instance());
   Node_default_oneof_instance_->jsobjectclassname_ = &::google::protobuf::internal::GetEmptyStringAlreadyInited();
   Node_default_oneof_instance_->jsobjectclassnameref_ = GOOGLE_ULONGLONG(0);
+  Node_default_oneof_instance_->scriptfilename_ = &::google::protobuf::internal::GetEmptyStringAlreadyInited();
+  Node_default_oneof_instance_->scriptfilenameref_ = GOOGLE_ULONGLONG(0);
 }
 
 Node::Node(const Node& from)
   : ::google::protobuf::Message() {
   SharedCtor();
   MergeFrom(from);
   // @@protoc_insertion_point(copy_constructor:mozilla.devtools.protobuf.Node)
 }
@@ -1490,30 +1501,34 @@ void Node::SharedCtor() {
   _cached_size_ = 0;
   id_ = GOOGLE_ULONGLONG(0);
   size_ = GOOGLE_ULONGLONG(0);
   allocationstack_ = NULL;
   coarsetype_ = 0u;
   ::memset(_has_bits_, 0, sizeof(_has_bits_));
   clear_has_TypeNameOrRef();
   clear_has_JSObjectClassNameOrRef();
+  clear_has_ScriptFilenameOrRef();
 }
 
 Node::~Node() {
   // @@protoc_insertion_point(destructor:mozilla.devtools.protobuf.Node)
   SharedDtor();
 }
 
 void Node::SharedDtor() {
   if (has_TypeNameOrRef()) {
     clear_TypeNameOrRef();
   }
   if (has_JSObjectClassNameOrRef()) {
     clear_JSObjectClassNameOrRef();
   }
+  if (has_ScriptFilenameOrRef()) {
+    clear_ScriptFilenameOrRef();
+  }
   if (this != default_instance_) {
     delete allocationstack_;
   }
 }
 
 void Node::SetCachedSize(int size) const {
   GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
   _cached_size_ = size;
@@ -1564,16 +1579,33 @@ void Node::clear_JSObjectClassNameOrRef(
     }
     case JSOBJECTCLASSNAMEORREF_NOT_SET: {
       break;
     }
   }
   _oneof_case_[1] = JSOBJECTCLASSNAMEORREF_NOT_SET;
 }
 
+void Node::clear_ScriptFilenameOrRef() {
+  switch(ScriptFilenameOrRef_case()) {
+    case kScriptFilename: {
+      delete ScriptFilenameOrRef_.scriptfilename_;
+      break;
+    }
+    case kScriptFilenameRef: {
+      // No need to clear
+      break;
+    }
+    case SCRIPTFILENAMEORREF_NOT_SET: {
+      break;
+    }
+  }
+  _oneof_case_[2] = SCRIPTFILENAMEORREF_NOT_SET;
+}
+
 
 void Node::Clear() {
 #define OFFSET_OF_FIELD_(f) (reinterpret_cast<char*>(      \
   &reinterpret_cast<Node*>(16)->f) - \
    reinterpret_cast<char*>(16))
 
 #define ZR_(first, last) do {                              \
     size_t f = OFFSET_OF_FIELD_(first);                    \
@@ -1590,16 +1622,17 @@ void Node::Clear() {
   coarsetype_ = 0u;
 
 #undef OFFSET_OF_FIELD_
 #undef ZR_
 
   edges_.Clear();
   clear_TypeNameOrRef();
   clear_JSObjectClassNameOrRef();
+  clear_ScriptFilenameOrRef();
   ::memset(_has_bits_, 0, sizeof(_has_bits_));
   mutable_unknown_fields()->Clear();
 }
 
 bool Node::MergePartialFromCodedStream(
     ::google::protobuf::io::CodedInputStream* input) {
 #define DO_(EXPRESSION) if (!(EXPRESSION)) goto failure
   ::google::protobuf::uint32 tag;
@@ -1729,16 +1762,45 @@ bool Node::MergePartialFromCodedStream(
          parse_coarseType:
           DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
                    ::google::protobuf::uint32, ::google::protobuf::internal::WireFormatLite::TYPE_UINT32>(
                  input, &coarsetype_)));
           set_has_coarsetype();
         } else {
           goto handle_unusual;
         }
+        if (input->ExpectTag(82)) goto parse_scriptFilename;
+        break;
+      }
+
+      // optional bytes scriptFilename = 10;
+      case 10: {
+        if (tag == 82) {
+         parse_scriptFilename:
+          DO_(::google::protobuf::internal::WireFormatLite::ReadBytes(
+                input, this->mutable_scriptfilename()));
+        } else {
+          goto handle_unusual;
+        }
+        if (input->ExpectTag(88)) goto parse_scriptFilenameRef;
+        break;
+      }
+
+      // optional uint64 scriptFilenameRef = 11;
+      case 11: {
+        if (tag == 88) {
+         parse_scriptFilenameRef:
+          clear_ScriptFilenameOrRef();
+          DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+                   ::google::protobuf::uint64, ::google::protobuf::internal::WireFormatLite::TYPE_UINT64>(
+                 input, &ScriptFilenameOrRef_.scriptfilenameref_)));
+          set_has_scriptfilenameref();
+        } else {
+          goto handle_unusual;
+        }
         if (input->ExpectAtEnd()) goto success;
         break;
       }
 
       default: {
       handle_unusual:
         if (tag == 0 ||
             ::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
@@ -1807,16 +1869,27 @@ void Node::SerializeWithCachedSizes(
     ::google::protobuf::internal::WireFormatLite::WriteUInt64(8, this->jsobjectclassnameref(), output);
   }
 
   // optional uint32 coarseType = 9 [default = 0];
   if (has_coarsetype()) {
     ::google::protobuf::internal::WireFormatLite::WriteUInt32(9, this->coarsetype(), output);
   }
 
+  // optional bytes scriptFilename = 10;
+  if (has_scriptfilename()) {
+    ::google::protobuf::internal::WireFormatLite::WriteBytesMaybeAliased(
+      10, this->scriptfilename(), output);
+  }
+
+  // optional uint64 scriptFilenameRef = 11;
+  if (has_scriptfilenameref()) {
+    ::google::protobuf::internal::WireFormatLite::WriteUInt64(11, this->scriptfilenameref(), output);
+  }
+
   if (!unknown_fields().empty()) {
     ::google::protobuf::internal::WireFormat::SerializeUnknownFields(
         unknown_fields(), output);
   }
   // @@protoc_insertion_point(serialize_end:mozilla.devtools.protobuf.Node)
 }
 
 ::google::protobuf::uint8* Node::SerializeWithCachedSizesToArray(
@@ -1870,16 +1943,28 @@ void Node::SerializeWithCachedSizes(
     target = ::google::protobuf::internal::WireFormatLite::WriteUInt64ToArray(8, this->jsobjectclassnameref(), target);
   }
 
   // optional uint32 coarseType = 9 [default = 0];
   if (has_coarsetype()) {
     target = ::google::protobuf::internal::WireFormatLite::WriteUInt32ToArray(9, this->coarsetype(), target);
   }
 
+  // optional bytes scriptFilename = 10;
+  if (has_scriptfilename()) {
+    target =
+      ::google::protobuf::internal::WireFormatLite::WriteBytesToArray(
+        10, this->scriptfilename(), target);
+  }
+
+  // optional uint64 scriptFilenameRef = 11;
+  if (has_scriptfilenameref()) {
+    target = ::google::protobuf::internal::WireFormatLite::WriteUInt64ToArray(11, this->scriptfilenameref(), target);
+  }
+
   if (!unknown_fields().empty()) {
     target = ::google::protobuf::internal::WireFormat::SerializeUnknownFieldsToArray(
         unknown_fields(), target);
   }
   // @@protoc_insertion_point(serialize_to_array_end:mozilla.devtools.protobuf.Node)
   return target;
 }
 
@@ -1959,16 +2044,35 @@ int Node::ByteSize() const {
         ::google::protobuf::internal::WireFormatLite::UInt64Size(
           this->jsobjectclassnameref());
       break;
     }
     case JSOBJECTCLASSNAMEORREF_NOT_SET: {
       break;
     }
   }
+  switch (ScriptFilenameOrRef_case()) {
+    // optional bytes scriptFilename = 10;
+    case kScriptFilename: {
+      total_size += 1 +
+        ::google::protobuf::internal::WireFormatLite::BytesSize(
+          this->scriptfilename());
+      break;
+    }
+    // optional uint64 scriptFilenameRef = 11;
+    case kScriptFilenameRef: {
+      total_size += 1 +
+        ::google::protobuf::internal::WireFormatLite::UInt64Size(
+          this->scriptfilenameref());
+      break;
+    }
+    case SCRIPTFILENAMEORREF_NOT_SET: {
+      break;
+    }
+  }
   if (!unknown_fields().empty()) {
     total_size +=
       ::google::protobuf::internal::WireFormat::ComputeUnknownFieldsSize(
         unknown_fields());
   }
   GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
   _cached_size_ = total_size;
   GOOGLE_SAFE_CONCURRENT_WRITES_END();
@@ -2011,16 +2115,29 @@ void Node::MergeFrom(const Node& from) {
     case kJsObjectClassNameRef: {
       set_jsobjectclassnameref(from.jsobjectclassnameref());
       break;
     }
     case JSOBJECTCLASSNAMEORREF_NOT_SET: {
       break;
     }
   }
+  switch (from.ScriptFilenameOrRef_case()) {
+    case kScriptFilename: {
+      set_scriptfilename(from.scriptfilename());
+      break;
+    }
+    case kScriptFilenameRef: {
+      set_scriptfilenameref(from.scriptfilenameref());
+      break;
+    }
+    case SCRIPTFILENAMEORREF_NOT_SET: {
+      break;
+    }
+  }
   if (from._has_bits_[0 / 32] & (0xffu << (0 % 32))) {
     if (from.has_id()) {
       set_id(from.id());
     }
     if (from.has_size()) {
       set_size(from.size());
     }
     if (from.has_allocationstack()) {
@@ -2058,16 +2175,18 @@ void Node::Swap(Node* other) {
     std::swap(size_, other->size_);
     edges_.Swap(&other->edges_);
     std::swap(allocationstack_, other->allocationstack_);
     std::swap(coarsetype_, other->coarsetype_);
     std::swap(TypeNameOrRef_, other->TypeNameOrRef_);
     std::swap(_oneof_case_[0], other->_oneof_case_[0]);
     std::swap(JSObjectClassNameOrRef_, other->JSObjectClassNameOrRef_);
     std::swap(_oneof_case_[1], other->_oneof_case_[1]);
+    std::swap(ScriptFilenameOrRef_, other->ScriptFilenameOrRef_);
+    std::swap(_oneof_case_[2], other->_oneof_case_[2]);
     std::swap(_has_bits_[0], other->_has_bits_[0]);
     _unknown_fields_.Swap(&other->_unknown_fields_);
     std::swap(_cached_size_, other->_cached_size_);
   }
 }
 
 ::google::protobuf::Metadata Node::GetMetadata() const {
   protobuf_AssignDescriptorsOnce();
--- a/devtools/shared/heapsnapshot/CoreDump.pb.h
+++ b/devtools/shared/heapsnapshot/CoreDump.pb.h
@@ -463,16 +463,22 @@ class Node : public ::google::protobuf::
   };
 
   enum JSObjectClassNameOrRefCase {
     kJsObjectClassName = 7,
     kJsObjectClassNameRef = 8,
     JSOBJECTCLASSNAMEORREF_NOT_SET = 0,
   };
 
+  enum ScriptFilenameOrRefCase {
+    kScriptFilename = 10,
+    kScriptFilenameRef = 11,
+    SCRIPTFILENAMEORREF_NOT_SET = 0,
+  };
+
   void Swap(Node* other);
 
   // implements Message ----------------------------------------------
 
   Node* New() const;
   void CopyFrom(const ::google::protobuf::Message& from);
   void MergeFrom(const ::google::protobuf::Message& from);
   void CopyFrom(const Node& from);
@@ -573,41 +579,67 @@ class Node : public ::google::protobuf::
 
   // optional uint32 coarseType = 9 [default = 0];
   inline bool has_coarsetype() const;
   inline void clear_coarsetype();
   static const int kCoarseTypeFieldNumber = 9;
   inline ::google::protobuf::uint32 coarsetype() const;
   inline void set_coarsetype(::google::protobuf::uint32 value);
 
+  // optional bytes scriptFilename = 10;
+  inline bool has_scriptfilename() const;
+  inline void clear_scriptfilename();
+  static const int kScriptFilenameFieldNumber = 10;
+  inline const ::std::string& scriptfilename() const;
+  inline void set_scriptfilename(const ::std::string& value);
+  inline void set_scriptfilename(const char* value);
+  inline void set_scriptfilename(const void* value, size_t size);
+  inline ::std::string* mutable_scriptfilename();
+  inline ::std::string* release_scriptfilename();
+  inline void set_allocated_scriptfilename(::std::string* scriptfilename);
+
+  // optional uint64 scriptFilenameRef = 11;
+  inline bool has_scriptfilenameref() const;
+  inline void clear_scriptfilenameref();
+  static const int kScriptFilenameRefFieldNumber = 11;
+  inline ::google::protobuf::uint64 scriptfilenameref() const;
+  inline void set_scriptfilenameref(::google::protobuf::uint64 value);
+
   inline TypeNameOrRefCase TypeNameOrRef_case() const;
   inline JSObjectClassNameOrRefCase JSObjectClassNameOrRef_case() const;
+  inline ScriptFilenameOrRefCase ScriptFilenameOrRef_case() const;
   // @@protoc_insertion_point(class_scope:mozilla.devtools.protobuf.Node)
  private:
   inline void set_has_id();
   inline void clear_has_id();
   inline void set_has_typename_();
   inline void set_has_typenameref();
   inline void set_has_size();
   inline void clear_has_size();
   inline void set_has_allocationstack();
   inline void clear_has_allocationstack();
   inline void set_has_jsobjectclassname();
   inline void set_has_jsobjectclassnameref();
   inline void set_has_coarsetype();
   inline void clear_has_coarsetype();
+  inline void set_has_scriptfilename();
+  inline void set_has_scriptfilenameref();
 
   inline bool has_TypeNameOrRef();
   void clear_TypeNameOrRef();
   inline void clear_has_TypeNameOrRef();
 
   inline bool has_JSObjectClassNameOrRef();
   void clear_JSObjectClassNameOrRef();
   inline void clear_has_JSObjectClassNameOrRef();
 
+  inline bool has_ScriptFilenameOrRef();
+  void clear_ScriptFilenameOrRef();
+  inline void clear_has_ScriptFilenameOrRef();
+
   ::google::protobuf::UnknownFieldSet _unknown_fields_;
 
   ::google::protobuf::uint32 _has_bits_[1];
   mutable int _cached_size_;
   ::google::protobuf::uint64 id_;
   ::google::protobuf::uint64 size_;
   ::google::protobuf::RepeatedPtrField< ::mozilla::devtools::protobuf::Edge > edges_;
   ::mozilla::devtools::protobuf::StackFrame* allocationstack_;
@@ -615,17 +647,21 @@ class Node : public ::google::protobuf::
   union TypeNameOrRefUnion {
     ::std::string* typename__;
     ::google::protobuf::uint64 typenameref_;
   } TypeNameOrRef_;
   union JSObjectClassNameOrRefUnion {
     ::std::string* jsobjectclassname_;
     ::google::protobuf::uint64 jsobjectclassnameref_;
   } JSObjectClassNameOrRef_;
-  ::google::protobuf::uint32 _oneof_case_[2];
+  union ScriptFilenameOrRefUnion {
+    ::std::string* scriptfilename_;
+    ::google::protobuf::uint64 scriptfilenameref_;
+  } ScriptFilenameOrRef_;
+  ::google::protobuf::uint32 _oneof_case_[3];
 
   friend void  protobuf_AddDesc_CoreDump_2eproto();
   friend void protobuf_AssignDesc_CoreDump_2eproto();
   friend void protobuf_ShutdownFile_CoreDump_2eproto();
 
   void InitAsDefaultInstance();
   static Node* default_instance_;
 };
@@ -1573,34 +1609,140 @@ inline ::google::protobuf::uint32 Node::
   return coarsetype_;
 }
 inline void Node::set_coarsetype(::google::protobuf::uint32 value) {
   set_has_coarsetype();
   coarsetype_ = value;
   // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.coarseType)
 }
 
+// optional bytes scriptFilename = 10;
+inline bool Node::has_scriptfilename() const {
+  return ScriptFilenameOrRef_case() == kScriptFilename;
+}
+inline void Node::set_has_scriptfilename() {
+  _oneof_case_[2] = kScriptFilename;
+}
+inline void Node::clear_scriptfilename() {
+  if (has_scriptfilename()) {
+    delete ScriptFilenameOrRef_.scriptfilename_;
+    clear_has_ScriptFilenameOrRef();
+  }
+}
+inline const ::std::string& Node::scriptfilename() const {
+  if (has_scriptfilename()) {
+    return *ScriptFilenameOrRef_.scriptfilename_;
+  }
+  return ::google::protobuf::internal::GetEmptyStringAlreadyInited();
+}
+inline void Node::set_scriptfilename(const ::std::string& value) {
+  if (!has_scriptfilename()) {
+    clear_ScriptFilenameOrRef();
+    set_has_scriptfilename();
+    ScriptFilenameOrRef_.scriptfilename_ = new ::std::string;
+  }
+  ScriptFilenameOrRef_.scriptfilename_->assign(value);
+}
+inline void Node::set_scriptfilename(const char* value) {
+  if (!has_scriptfilename()) {
+    clear_ScriptFilenameOrRef();
+    set_has_scriptfilename();
+    ScriptFilenameOrRef_.scriptfilename_ = new ::std::string;
+  }
+  ScriptFilenameOrRef_.scriptfilename_->assign(value);
+}
+inline void Node::set_scriptfilename(const void* value, size_t size) {
+  if (!has_scriptfilename()) {
+    clear_ScriptFilenameOrRef();
+    set_has_scriptfilename();
+    ScriptFilenameOrRef_.scriptfilename_ = new ::std::string;
+  }
+  ScriptFilenameOrRef_.scriptfilename_->assign(
+      reinterpret_cast<const char*>(value), size);
+}
+inline ::std::string* Node::mutable_scriptfilename() {
+  if (!has_scriptfilename()) {
+    clear_ScriptFilenameOrRef();
+    set_has_scriptfilename();
+    ScriptFilenameOrRef_.scriptfilename_ = new ::std::string;
+  }
+  return ScriptFilenameOrRef_.scriptfilename_;
+}
+inline ::std::string* Node::release_scriptfilename() {
+  if (has_scriptfilename()) {
+    clear_has_ScriptFilenameOrRef();
+    ::std::string* temp = ScriptFilenameOrRef_.scriptfilename_;
+    ScriptFilenameOrRef_.scriptfilename_ = NULL;
+    return temp;
+  } else {
+    return NULL;
+  }
+}
+inline void Node::set_allocated_scriptfilename(::std::string* scriptfilename) {
+  clear_ScriptFilenameOrRef();
+  if (scriptfilename) {
+    set_has_scriptfilename();
+    ScriptFilenameOrRef_.scriptfilename_ = scriptfilename;
+  }
+}
+
+// optional uint64 scriptFilenameRef = 11;
+inline bool Node::has_scriptfilenameref() const {
+  return ScriptFilenameOrRef_case() == kScriptFilenameRef;
+}
+inline void Node::set_has_scriptfilenameref() {
+  _oneof_case_[2] = kScriptFilenameRef;
+}
+inline void Node::clear_scriptfilenameref() {
+  if (has_scriptfilenameref()) {
+    ScriptFilenameOrRef_.scriptfilenameref_ = GOOGLE_ULONGLONG(0);
+    clear_has_ScriptFilenameOrRef();
+  }
+}
+inline ::google::protobuf::uint64 Node::scriptfilenameref() const {
+  if (has_scriptfilenameref()) {
+    return ScriptFilenameOrRef_.scriptfilenameref_;
+  }
+  return GOOGLE_ULONGLONG(0);
+}
+inline void Node::set_scriptfilenameref(::google::protobuf::uint64 value) {
+  if (!has_scriptfilenameref()) {
+    clear_ScriptFilenameOrRef();
+    set_has_scriptfilenameref();
+  }
+  ScriptFilenameOrRef_.scriptfilenameref_ = value;
+}
+
 inline bool Node::has_TypeNameOrRef() {
   return TypeNameOrRef_case() != TYPENAMEORREF_NOT_SET;
 }
 inline void Node::clear_has_TypeNameOrRef() {
   _oneof_case_[0] = TYPENAMEORREF_NOT_SET;
 }
 inline bool Node::has_JSObjectClassNameOrRef() {
   return JSObjectClassNameOrRef_case() != JSOBJECTCLASSNAMEORREF_NOT_SET;
 }
 inline void Node::clear_has_JSObjectClassNameOrRef() {
   _oneof_case_[1] = JSOBJECTCLASSNAMEORREF_NOT_SET;
 }
+inline bool Node::has_ScriptFilenameOrRef() {
+  return ScriptFilenameOrRef_case() != SCRIPTFILENAMEORREF_NOT_SET;
+}
+inline void Node::clear_has_ScriptFilenameOrRef() {
+  _oneof_case_[2] = SCRIPTFILENAMEORREF_NOT_SET;
+}
 inline Node::TypeNameOrRefCase Node::TypeNameOrRef_case() const {
   return Node::TypeNameOrRefCase(_oneof_case_[0]);
 }
 inline Node::JSObjectClassNameOrRefCase Node::JSObjectClassNameOrRef_case() const {
   return Node::JSObjectClassNameOrRefCase(_oneof_case_[1]);
 }
+inline Node::ScriptFilenameOrRefCase Node::ScriptFilenameOrRef_case() const {
+  return Node::ScriptFilenameOrRefCase(_oneof_case_[2]);
+}
 // -------------------------------------------------------------------
 
 // Edge
 
 // optional uint64 referent = 1;
 inline bool Edge::has_referent() const {
   return (_has_bits_[0] & 0x00000001u) != 0;
 }
--- a/devtools/shared/heapsnapshot/CoreDump.proto
+++ b/devtools/shared/heapsnapshot/CoreDump.proto
@@ -118,16 +118,22 @@ message Node {
     // De-duplicated one-byte string.
     oneof JSObjectClassNameOrRef {
         bytes           jsObjectClassName    = 7;
         uint64          jsObjectClassNameRef = 8;
     }
 
     // JS::ubi::CoarseType. Defaults to Other.
     optional uint32     coarseType           = 9 [default = 0];
+
+    // De-duplicated one-byte string.
+    oneof ScriptFilenameOrRef {
+        bytes           scriptFilename       = 10;
+        uint64          scriptFilenameRef    = 11;
+    }
 }
 
 // A serialized edge from the heap graph.
 message Edge {
     optional uint64 referent    = 1;
 
     // De-duplicated two-byte string.
     oneof EdgeNameOrRef {
--- a/devtools/shared/heapsnapshot/DeserializedNode.h
+++ b/devtools/shared/heapsnapshot/DeserializedNode.h
@@ -62,47 +62,52 @@ struct DeserializedNode {
   JS::ubi::CoarseType coarseType;
   // A borrowed reference to a string owned by this node's owning HeapSnapshot.
   const char16_t*     typeName;
   uint64_t            size;
   EdgeVector          edges;
   Maybe<StackFrameId> allocationStack;
   // A borrowed reference to a string owned by this node's owning HeapSnapshot.
   const char*         jsObjectClassName;
+  // A borrowed reference to a string owned by this node's owning HeapSnapshot.
+  const char*         scriptFilename;
   // A weak pointer to this node's owning `HeapSnapshot`. Safe without
   // AddRef'ing because this node's lifetime is equal to that of its owner.
   HeapSnapshot*       owner;
 
   DeserializedNode(NodeId id,
                    JS::ubi::CoarseType coarseType,
                    const char16_t* typeName,
                    uint64_t size,
                    EdgeVector&& edges,
                    Maybe<StackFrameId> allocationStack,
                    const char* className,
+                   const char* filename,
                    HeapSnapshot& owner)
     : id(id)
     , coarseType(coarseType)
     , typeName(typeName)
     , size(size)
     , edges(Move(edges))
     , allocationStack(allocationStack)
     , jsObjectClassName(className)
+    , scriptFilename(filename)
     , owner(&owner)
   { }
   virtual ~DeserializedNode() { }
 
   DeserializedNode(DeserializedNode&& rhs)
     : id(rhs.id)
     , coarseType(rhs.coarseType)
     , typeName(rhs.typeName)
     , size(rhs.size)
     , edges(Move(rhs.edges))
     , allocationStack(rhs.allocationStack)
     , jsObjectClassName(rhs.jsObjectClassName)
+    , scriptFilename(rhs.scriptFilename)
     , owner(rhs.owner)
   { }
 
   DeserializedNode& operator=(DeserializedNode&& rhs)
   {
     MOZ_ASSERT(&rhs != this);
     this->~DeserializedNode();
     new(this) DeserializedNode(Move(rhs));
@@ -120,16 +125,17 @@ protected:
   DeserializedNode(NodeId id, const char16_t* typeName, uint64_t size)
     : id(id)
     , coarseType(JS::ubi::CoarseType::Other)
     , typeName(typeName)
     , size(size)
     , edges()
     , allocationStack(Nothing())
     , jsObjectClassName(nullptr)
+    , scriptFilename(nullptr)
     , owner(nullptr)
   { }
 
 private:
   DeserializedNode(const DeserializedNode&) = delete;
   DeserializedNode& operator=(const DeserializedNode&) = delete;
 };
 
@@ -255,16 +261,17 @@ public:
   }
 
   CoarseType coarseType() const final { return get().coarseType; }
   Id identifier() const override { return get().id; }
   bool isLive() const override { return false; }
   const char16_t* typeName() const override;
   Node::Size size(mozilla::MallocSizeOf mallocSizeof) const override;
   const char* jsObjectClassName() const override { return get().jsObjectClassName; }
+  const char* scriptFilename() const final { return get().scriptFilename; }
 
   bool hasAllocationStack() const override { return get().allocationStack.isSome(); }
   StackFrame allocationStack() const override;
 
   // We ignore the `bool wantNames` parameter because we can't control whether
   // the core dump was serialized with edge names or not.
   UniquePtr<EdgeRange> edges(JSRuntime* rt, bool) const override;
 };
--- a/devtools/shared/heapsnapshot/HeapSnapshot.cpp
+++ b/devtools/shared/heapsnapshot/HeapSnapshot.cpp
@@ -271,20 +271,29 @@ HeapSnapshot::saveNode(const protobuf::N
   const char* jsObjectClassName = nullptr;
   if (node.JSObjectClassNameOrRef_case() != protobuf::Node::JSOBJECTCLASSNAMEORREF_NOT_SET) {
     Maybe<StringOrRef> clsNameOrRef = GET_STRING_OR_REF(node, jsobjectclassname);
     jsObjectClassName = getOrInternString<char>(internedOneByteStrings, clsNameOrRef);
     if (NS_WARN_IF(!jsObjectClassName))
       return false;
   }
 
+  const char* scriptFilename = nullptr;
+  if (node.ScriptFilenameOrRef_case() != protobuf::Node::SCRIPTFILENAMEORREF_NOT_SET) {
+    Maybe<StringOrRef> scriptFilenameOrRef = GET_STRING_OR_REF(node, scriptfilename);
+    scriptFilename = getOrInternString<char>(internedOneByteStrings, scriptFilenameOrRef);
+    if (NS_WARN_IF(!scriptFilename))
+      return false;
+  }
+
   if (NS_WARN_IF(!nodes.putNew(id, DeserializedNode(id, coarseType, typeName,
                                                     size, Move(edges),
                                                     allocationStack,
-                                                    jsObjectClassName, *this))))
+                                                    jsObjectClassName,
+                                                    scriptFilename, *this))))
   {
     return false;
   };
 
   return true;
 }
 
 bool
@@ -1074,16 +1083,25 @@ public:
       if (NS_WARN_IF(!attachOneByteString(className,
                                           [&] (std::string* name) { protobufNode.set_allocated_jsobjectclassname(name); },
                                           [&] (uint64_t ref) { protobufNode.set_jsobjectclassnameref(ref); })))
       {
         return false;
       }
     }
 
+    if (auto scriptFilename = ubiNode.scriptFilename()) {
+      if (NS_WARN_IF(!attachOneByteString(scriptFilename,
+                                          [&] (std::string* name) { protobufNode.set_allocated_scriptfilename(name); },
+                                          [&] (uint64_t ref) { protobufNode.set_scriptfilenameref(ref); })))
+      {
+        return false;
+      }
+    }
+
     return writeMessage(protobufNode);
   }
 };
 
 // A JS::ubi::BreadthFirst handler that serializes a snapshot of the heap into a
 // core dump.
 class MOZ_STACK_CLASS HeapSnapshotHandler
 {
--- a/devtools/shared/heapsnapshot/tests/gtest/DeserializedNodeUbiNodes.cpp
+++ b/devtools/shared/heapsnapshot/tests/gtest/DeserializedNodeUbiNodes.cpp
@@ -34,34 +34,37 @@ size_t fakeMallocSizeOf(const void*) {
   MOZ_ASSERT_UNREACHABLE("fakeMallocSizeOf should never be called because "
                          "DeserializedNodes report the deserialized size.");
   return 0;
 }
 
 DEF_TEST(DeserializedNodeUbiNodes, {
     const char16_t* typeName = MOZ_UTF16("TestTypeName");
     const char* className = "MyObjectClassName";
+    const char* filename = "my-cool-filename.js";
 
     NodeId id = uint64_t(1) << 33;
     uint64_t size = uint64_t(1) << 60;
     MockDeserializedNode mocked(id, typeName, size);
     mocked.coarseType = JS::ubi::CoarseType::Script;
     mocked.jsObjectClassName = className;
+    mocked.scriptFilename = filename;
 
     DeserializedNode& deserialized = mocked;
     JS::ubi::Node ubi(&deserialized);
 
     // Test the ubi::Node accessors.
 
     EXPECT_EQ(size, ubi.size(fakeMallocSizeOf));
     EXPECT_EQ(typeName, ubi.typeName());
     EXPECT_EQ(JS::ubi::CoarseType::Script, ubi.coarseType());
     EXPECT_EQ(id, ubi.identifier());
     EXPECT_FALSE(ubi.isLive());
     EXPECT_EQ(ubi.jsObjectClassName(), className);
+    EXPECT_EQ(ubi.scriptFilename(), filename);
 
     // Test the ubi::Node's edges.
 
     UniquePtr<DeserializedNode> referent1(new MockDeserializedNode(1,
                                                                    nullptr,
                                                                    10));
     DeserializedEdge edge1(referent1->id);
     mocked.addEdge(Move(edge1));
new file mode 100644
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-08.js
@@ -0,0 +1,133 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test inverting CensusTreeNode with a non-allocation stack breakdown.
+ */
+
+function run_test() {
+  const BREAKDOWN = {
+    by: "filename",
+    then: {
+      by: "internalType",
+      then: { by: "count", count: true, bytes: true }
+    },
+    noFilename: {
+      by: "internalType",
+      then: { by: "count", count: true, bytes: true }
+    },
+  };
+
+  const REPORT = {
+    "http://example.com/app.js": {
+      JSScript: { count: 10, bytes: 100 }
+    },
+    "http://example.com/ads.js": {
+      "js::LazyScript": { count: 20, bytes: 200 }
+    },
+    "http://example.com/trackers.js": {
+      JSScript: { count: 30, bytes: 300 }
+    },
+    noFilename: {
+      "js::jit::JitCode": { count: 40, bytes: 400 }
+    }
+  };
+
+  const EXPECTED = {
+    name: null,
+    bytes: 0,
+    totalBytes: 1000,
+    count: 0,
+    totalCount: 100,
+    children: [
+      {
+        name: "noFilename",
+        bytes: 0,
+        totalBytes: 400,
+        count: 0,
+        totalCount: 40,
+        children: [
+          {
+            name: "js::jit::JitCode",
+            bytes: 400,
+            totalBytes: 400,
+            count: 40,
+            totalCount: 40,
+            children: undefined,
+            id: 9,
+            parent: 8
+          }
+        ],
+        id: 8,
+        parent: 1
+      },
+      {
+        name: "http://example.com/trackers.js",
+        bytes: 0,
+        totalBytes: 300,
+        count: 0,
+        totalCount: 30,
+        children: [
+          {
+            name: "JSScript",
+            bytes: 300,
+            totalBytes: 300,
+            count: 30,
+            totalCount: 30,
+            children: undefined,
+            id: 7,
+            parent: 6
+          }
+        ],
+        id: 6,
+        parent: 1
+      },
+      {
+        name: "http://example.com/ads.js",
+        bytes: 0,
+        totalBytes: 200,
+        count: 0,
+        totalCount: 20,
+        children: [
+          {
+            name: "js::LazyScript",
+            bytes: 200,
+            totalBytes: 200,
+            count: 20,
+            totalCount: 20,
+            children: undefined,
+            id: 5,
+            parent: 4
+          }
+        ],
+        id: 4,
+        parent: 1
+      },
+      {
+        name: "http://example.com/app.js",
+        bytes: 0,
+        totalBytes: 100,
+        count: 0,
+        totalCount: 10,
+        children: [
+          {
+            name: "JSScript",
+            bytes: 100,
+            totalBytes: 100,
+            count: 10,
+            totalCount: 10,
+            children: undefined,
+            id: 3,
+            parent: 2
+          }
+        ],
+        id: 2,
+        parent: 1
+      }
+    ],
+    id: 1,
+    parent: undefined,
+  };
+
+  compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
+}
--- a/devtools/shared/heapsnapshot/tests/unit/xpcshell.ini
+++ b/devtools/shared/heapsnapshot/tests/unit/xpcshell.ini
@@ -21,16 +21,17 @@ support-files =
 [test_census_filtering_03.js]
 [test_census-tree-node-01.js]
 [test_census-tree-node-02.js]
 [test_census-tree-node-03.js]
 [test_census-tree-node-04.js]
 [test_census-tree-node-05.js]
 [test_census-tree-node-06.js]
 [test_census-tree-node-07.js]
+[test_census-tree-node-08.js]
 [test_HeapAnalyses_getCreationTime_01.js]
 [test_HeapAnalyses_readHeapSnapshot_01.js]
 [test_HeapAnalyses_takeCensusDiff_01.js]
 [test_HeapAnalyses_takeCensusDiff_02.js]
 [test_HeapAnalyses_takeCensus_01.js]
 [test_HeapAnalyses_takeCensus_02.js]
 [test_HeapAnalyses_takeCensus_03.js]
 [test_HeapAnalyses_takeCensus_04.js]
--- a/docshell/test/browser/browser_bug441169.js
+++ b/docshell/test/browser/browser_bug441169.js
@@ -1,31 +1,37 @@
 /* Make sure that netError won't allow HTML injection through badcert parameters.  See bug 441169. */
 var newBrowser
 
-// An edited version of the standard neterror url which attempts to
-// insert a <span id="test_span"> tag into the text.  We will navigate to this page
-// and ensure that the span tag is not parsed as HTML.
-var chromeURL = "about:neterror?e=nssBadCert&u=https%3A//test.kuix.de/&c=UTF-8&d=This%20sentence%20should%20not%20be%20parsed%20to%20include%20a%20%3Cspan%20id=%22test_span%22%3Enamed%3C/span%3E%20span%20tag.%0A%0AThe%20certificate%20is%20only%20valid%20for%20%3Ca%20id=%22cert_domain_link%22%20title=%22kuix.de%22%3Ekuix.de%3C/a%3E%0A%0A(Error%20code%3A%20ssl_error_bad_cert_domain)";
+function task() {
+  let resolve;
+  let promise = new Promise(r => { resolve = r; });
+
+  addEventListener("DOMContentLoaded", checkPage, false);
+
+  function checkPage(event) {
+    if (event.target != content.document) {
+      return;
+    }
+    removeEventListener("DOMContentLoaded", checkPage, false);
+
+    is(content.document.getElementById("test_span"), null, "Error message should not be parsed as HTML, and hence shouldn't include the 'test_span' element.");
+    resolve();
+  }
+
+  var chromeURL = "about:neterror?e=nssBadCert&u=https%3A//test.kuix.de/&c=UTF-8&d=This%20sentence%20should%20not%20be%20parsed%20to%20include%20a%20%3Cspan%20id=%22test_span%22%3Enamed%3C/span%3E%20span%20tag.%0A%0AThe%20certificate%20is%20only%20valid%20for%20%3Ca%20id=%22cert_domain_link%22%20title=%22kuix.de%22%3Ekuix.de%3C/a%3E%0A%0A(Error%20code%3A%20ssl_error_bad_cert_domain)";
+  content.location = chromeURL;
+
+  return promise;
+}
 
 function test() {
   waitForExplicitFinish();
-  
+
   var newTab = gBrowser.addTab();
   gBrowser.selectedTab = newTab;
   newBrowser = gBrowser.getBrowserForTab(newTab);
-  
-  window.addEventListener("DOMContentLoaded", checkPage, false);
-  newBrowser.contentWindow.location = chromeURL;
+
+  ContentTask.spawn(newBrowser, null, task).then(() => {
+    gBrowser.removeCurrentTab();
+    finish();
+  });
 }
-
-function checkPage(event) {
-  if (event.target != gBrowser.selectedBrowser.contentDocument) {
-    return;
-  }
-
-  window.removeEventListener("DOMContentLoaded", checkPage, false);
-  
-  is(newBrowser.contentDocument.getElementById("test_span"), null, "Error message should not be parsed as HTML, and hence shouldn't include the 'test_span' element.");
-  
-  gBrowser.removeCurrentTab();
-  finish();
-}
--- a/docshell/test/browser/browser_bug655273.js
+++ b/docshell/test/browser/browser_bug655273.js
@@ -9,27 +9,25 @@
  **/
 
 function test() {
   waitForExplicitFinish();
 
   let tab = gBrowser.addTab('http://example.com');
   let tabBrowser = tab.linkedBrowser;
 
-  tabBrowser.addEventListener('load', function(aEvent) {
-    tabBrowser.removeEventListener('load', arguments.callee, true);
-
+  BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(() => {
     let cw = tabBrowser.contentWindow;
     let oldTitle = cw.document.title;
     ok(oldTitle, 'Content window should initially have a title.');
     cw.history.pushState('', '', 'new_page');
 
     let shistory = cw.QueryInterface(Ci.nsIInterfaceRequestor)
                      .getInterface(Ci.nsIWebNavigation)
                      .sessionHistory;
 
     is(shistory.getEntryAtIndex(shistory.index, false).title,
        oldTitle, 'SHEntry title after pushstate.');
 
     gBrowser.removeTab(tab);
     finish();
-  }, true);
+  });
 }
--- a/docshell/test/browser/browser_onbeforeunload_navigation.js
+++ b/docshell/test/browser/browser_onbeforeunload_navigation.js
@@ -5,16 +5,17 @@ var stayingOnPage = true;
 
 var TEST_PAGE = "http://mochi.test:8888/browser/docshell/test/browser/file_bug1046022.html";
 var TARGETED_PAGE = "data:text/html," + encodeURIComponent("<body>Shouldn't be seeing this</body>");
 
 SpecialPowers.pushPrefEnv({"set": [["dom.require_user_interaction_for_beforeunload", false]]});
 
 var loadExpected = TEST_PAGE;
 var testTab;
+var testsLength;
 
 var loadStarted = false;
 var tabStateListener = {
   onStateChange: function(webprogress, request, stateFlags, status) {
     let startDocumentFlags = Ci.nsIWebProgressListener.STATE_START |
                              Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
     if ((stateFlags & startDocumentFlags) == startDocumentFlags) {
       loadStarted = true;
@@ -35,16 +36,20 @@ function onTabLoaded(event) {
     return;
   }
 
   if (!loadExpected) {
     ok(false, "Expected no page loads, but loaded " + loadedPage + " instead!");
     return;
   }
 
+  if (!testsLength) {
+    testsLength = testTab.linkedBrowser.contentWindow.wrappedJSObject.testFns.length;
+  }
+
   is(loadedPage, loadExpected, "Loaded the expected page");
   if (contentWindow) {
     is(contentWindow.document, event.target, "Same doc");
   }
   if (onAfterPageLoad) {
     onAfterPageLoad();
   }
 }
@@ -97,57 +102,19 @@ function onTabModalDialogLoaded(node) {
   // ... and then actually make the dialog go away
   info("Clicking button: " + button.label);
   EventUtils.synthesizeMouseAtCenter(button, {});
 }
 
 // Listen for the dialog being created
 Services.obs.addObserver(onTabModalDialogLoaded, "tabmodal-dialog-loaded", false);
 
-var testFns = [
-  function(e) {
-    e.target.location.href = 'otherpage-href-set.html';
-    return "stop";
-  },
-  function(e) {
-    e.target.location.reload();
-    return "stop";
-  },
-  function(e) {
-    e.target.location.replace('otherpage-location-replaced.html');
-    return "stop";
-  },
-  function(e) {
-    var link = e.target.createElement('a');
-    link.href = "otherpage.html";
-    e.target.body.appendChild(link);
-    link.click();
-    return "stop";
-  },
-  function(e) {
-    var link = e.target.createElement('a');
-    link.href = "otherpage.html";
-    link.setAttribute("target", "_blank");
-    e.target.body.appendChild(link);
-    link.click();
-    return "stop";
-  },
-  function(e) {
-    var link = e.target.createElement('a');
-    link.href = e.target.location.href;
-    e.target.body.appendChild(link);
-    link.setAttribute("target", "somearbitrarywindow");
-    link.click();
-    return "stop";
-  },
-];
-
 function runNextTest() {
   currentTest++;
-  if (currentTest >= testFns.length) {
+  if (currentTest >= testsLength) {
     if (!stayingOnPage) {
       finish();
       return;
     }
     // Run the same tests again, but this time let the navigation happen:
     stayingOnPage = false;
     currentTest = 0;
   }
@@ -166,20 +133,20 @@ function runNextTest() {
 
 function runCurrentTest() {
   // Reset things so we're sure the previous tests failings don't influence this one:
   contentWindow = testTab.linkedBrowser.contentWindow;
   contentWindow.mySuperSpecialMark = 42;
   contentWindow.dialogWasInvoked = false;
   originalLocation = contentWindow.location.href;
   // And run this test:
-  info("Running test with onbeforeunload " + testFns[currentTest].toSource());
-  contentWindow.onbeforeunload = testFns[currentTest];
+  info("Running test with onbeforeunload " + contentWindow.wrappedJSObject.testFns[currentTest].toSource());
+  contentWindow.onbeforeunload = contentWindow.wrappedJSObject.testFns[currentTest];
   loadStarted = false;
-  contentWindow.location.href = TARGETED_PAGE;
+  testTab.linkedBrowser.loadURI(TARGETED_PAGE);
 }
 
 var onAfterPageLoad = runNextTest;
 
 function test() {
   waitForExplicitFinish();
   gBrowser.addProgressListener(tabStateListener);
 
--- a/docshell/test/browser/file_bug1046022.html
+++ b/docshell/test/browser/file_bug1046022.html
@@ -2,9 +2,49 @@
 <html>
   <head>
     <meta charset="utf-8">
     <title>Bug 1046022 - test navigating inside onbeforeunload</title>
   </head>
   <body>
     Waiting for onbeforeunload to hit...
   </body>
+
+  <script>
+var testFns = [
+  function(e) {
+    e.target.location.href = 'otherpage-href-set.html';
+    return "stop";
+  },
+  function(e) {
+    e.target.location.reload();
+    return "stop";
+  },
+  function(e) {
+    e.target.location.replace('otherpage-location-replaced.html');
+    return "stop";
+  },
+  function(e) {
+    var link = e.target.createElement('a');
+    link.href = "otherpage.html";
+    e.target.body.appendChild(link);
+    link.click();
+    return "stop";
+  },
+  function(e) {
+    var link = e.target.createElement('a');
+    link.href = "otherpage.html";
+    link.setAttribute("target", "_blank");
+    e.target.body.appendChild(link);
+    link.click();
+    return "stop";
+  },
+  function(e) {
+    var link = e.target.createElement('a');
+    link.href = e.target.location.href;
+    e.target.body.appendChild(link);
+    link.setAttribute("target", "somearbitrarywindow");
+    link.click();
+    return "stop";
+  },
+];
+  </script>
 </html>
--- a/docshell/test/browser/head.js
+++ b/docshell/test/browser/head.js
@@ -26,18 +26,18 @@ function makeTimelineTest(frameScriptNam
     // content process can forward their results to the main process.
     mm.addMessageListener("browser:test:ok", function(message) {
       ok(message.data.value, message.data.message);
     });
     mm.addMessageListener("browser:test:info", function(message) {
       info(message.data.message);
     });
     mm.addMessageListener("browser:test:finish", function(ignore) {
+      gBrowser.removeCurrentTab();
       finish();
-      gBrowser.removeCurrentTab();
     });
   });
 }
 
 /* Open a URL for a timeline test.  */
 function timelineTestOpenUrl(url) {
   window.focus();
 
--- a/docshell/test/test_bug369814.html
+++ b/docshell/test/test_bug369814.html
@@ -181,17 +181,18 @@ var gTests = [
 var gNextTest = 0;
 
 function runNextTest()
 {
   if (gNextTest < gTests.length) {
     gCurrentTest = gTests[gNextTest++];
     gNumPokes = 0;
 
-    SpecialPowers.pushPrefEnv({"set": [["network.jar.open-unsafe-types", gCurrentTest['pref']]]}, function() {
+    SpecialPowers.pushPrefEnv({"set": [["network.jar.block-remote-files", false],
+                                       ["network.jar.open-unsafe-types", gCurrentTest['pref']]]}, function() {
 
       // Create a new frame each time, so our restictions on loads in a
       // jar:-loaded iframe don't interfere with the test.
       if (gTestFrame) {
         document.body.removeChild(gTestFrame);
       }
       gTestFrame = document.createElement("iframe");
       document.body.insertBefore(gTestFrame, $("test"));
new file mode 100644
--- /dev/null
+++ b/dom/animation/ComputedTimingFunction.cpp
@@ -0,0 +1,99 @@
+/* -*- 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 "ComputedTimingFunction.h"
+#include "nsStyleUtil.h"
+
+namespace mozilla {
+
+void
+ComputedTimingFunction::Init(const nsTimingFunction &aFunction)
+{
+  mType = aFunction.mType;
+  if (nsTimingFunction::IsSplineType(mType)) {
+    mTimingFunction.Init(aFunction.mFunc.mX1, aFunction.mFunc.mY1,
+                         aFunction.mFunc.mX2, aFunction.mFunc.mY2);
+  } else {
+    mSteps = aFunction.mSteps;
+    mStepSyntax = aFunction.mStepSyntax;
+  }
+}
+
+static inline double
+StepEnd(uint32_t aSteps, double aPortion)
+{
+  MOZ_ASSERT(0.0 <= aPortion && aPortion <= 1.0, "out of range");
+  uint32_t step = uint32_t(aPortion * aSteps); // floor
+  return double(step) / double(aSteps);
+}
+
+double
+ComputedTimingFunction::GetValue(double aPortion) const
+{
+  if (HasSpline()) {
+    return mTimingFunction.GetSplineValue(aPortion);
+  }
+  if (mType == nsTimingFunction::Type::StepStart) {
+    // There are diagrams in the spec that seem to suggest this check
+    // and the bounds point should not be symmetric with StepEnd, but
+    // should actually step up at rather than immediately after the
+    // fraction points.  However, we rely on rounding negative values
+    // up to zero, so we can't do that.  And it's not clear the spec
+    // really meant it.
+    return 1.0 - StepEnd(mSteps, 1.0 - aPortion);
+  }
+  MOZ_ASSERT(mType == nsTimingFunction::Type::StepEnd, "bad type");
+  return StepEnd(mSteps, aPortion);
+}
+
+int32_t
+ComputedTimingFunction::Compare(const ComputedTimingFunction& aRhs) const
+{
+  if (mType != aRhs.mType) {
+    return int32_t(mType) - int32_t(aRhs.mType);
+  }
+
+  if (mType == nsTimingFunction::Type::CubicBezier) {
+    int32_t order = mTimingFunction.Compare(aRhs.mTimingFunction);
+    if (order != 0) {
+      return order;
+    }
+  } else if (mType == nsTimingFunction::Type::StepStart ||
+             mType == nsTimingFunction::Type::StepEnd) {
+    if (mSteps != aRhs.mSteps) {
+      return int32_t(mSteps) - int32_t(aRhs.mSteps);
+    }
+    if (mStepSyntax != aRhs.mStepSyntax) {
+      return int32_t(mStepSyntax) - int32_t(aRhs.mStepSyntax);
+    }
+  }
+
+  return 0;
+}
+
+void
+ComputedTimingFunction::AppendToString(nsAString& aResult) const
+{
+  switch (mType) {
+    case nsTimingFunction::Type::CubicBezier:
+      nsStyleUtil::AppendCubicBezierTimingFunction(mTimingFunction.X1(),
+                                                   mTimingFunction.Y1(),
+                                                   mTimingFunction.X2(),
+                                                   mTimingFunction.Y2(),
+                                                   aResult);
+      break;
+    case nsTimingFunction::Type::StepStart:
+    case nsTimingFunction::Type::StepEnd:
+      nsStyleUtil::AppendStepsTimingFunction(mType, mSteps, mStepSyntax,
+                                             aResult);
+      break;
+    default:
+      nsStyleUtil::AppendCubicBezierKeywordTimingFunction(mType, aResult);
+      break;
+  }
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/animation/ComputedTimingFunction.h
@@ -0,0 +1,53 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ComputedTimingFunction_h
+#define mozilla_ComputedTimingFunction_h
+
+#include "nsSMILKeySpline.h"  // nsSMILKeySpline
+#include "nsStyleStruct.h"    // nsTimingFunction
+
+namespace mozilla {
+
+class ComputedTimingFunction
+{
+public:
+  void Init(const nsTimingFunction &aFunction);
+  double GetValue(double aPortion) const;
+  const nsSMILKeySpline* GetFunction() const
+  {
+    NS_ASSERTION(HasSpline(), "Type mismatch");
+    return &mTimingFunction;
+  }
+  nsTimingFunction::Type GetType() const { return mType; }
+  bool HasSpline() const { return nsTimingFunction::IsSplineType(mType); }
+  uint32_t GetSteps() const { return mSteps; }
+  nsTimingFunction::StepSyntax GetStepSyntax() const { return mStepSyntax; }
+  bool operator==(const ComputedTimingFunction& aOther) const
+  {
+    return mType == aOther.mType &&
+           (HasSpline() ?
+            mTimingFunction == aOther.mTimingFunction :
+            (mSteps == aOther.mSteps &&
+             mStepSyntax == aOther.mStepSyntax));
+  }
+  bool operator!=(const ComputedTimingFunction& aOther) const
+  {
+    return !(*this == aOther);
+  }
+  int32_t Compare(const ComputedTimingFunction& aRhs) const;
+  void AppendToString(nsAString& aResult) const;
+
+private:
+  nsTimingFunction::Type mType;
+  nsSMILKeySpline mTimingFunction;
+  uint32_t mSteps;
+  nsTimingFunction::StepSyntax mStepSyntax;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_dom_AnimationEffectReadOnly_h
--- a/dom/animation/KeyframeEffect.cpp
+++ b/dom/animation/KeyframeEffect.cpp
@@ -81,104 +81,16 @@ GetComputedTimingDictionary(const Comput
     // (uint64_t) aComputedTiming.mCurrentIteration to UINT64_MAX
     double iteration = aComputedTiming.mCurrentIteration == UINT64_MAX
                      ? PositiveInfinity<double>()
                      : static_cast<double>(aComputedTiming.mCurrentIteration);
     aRetVal.mCurrentIteration.SetValue(iteration);
   }
 }
 
-void
-ComputedTimingFunction::Init(const nsTimingFunction &aFunction)
-{
-  mType = aFunction.mType;
-  if (nsTimingFunction::IsSplineType(mType)) {
-    mTimingFunction.Init(aFunction.mFunc.mX1, aFunction.mFunc.mY1,
-                         aFunction.mFunc.mX2, aFunction.mFunc.mY2);
-  } else {
-    mSteps = aFunction.mSteps;
-    mStepSyntax = aFunction.mStepSyntax;
-  }
-}
-
-static inline double
-StepEnd(uint32_t aSteps, double aPortion)
-{
-  MOZ_ASSERT(0.0 <= aPortion && aPortion <= 1.0, "out of range");
-  uint32_t step = uint32_t(aPortion * aSteps); // floor
-  return double(step) / double(aSteps);
-}
-
-double
-ComputedTimingFunction::GetValue(double aPortion) const
-{
-  if (HasSpline()) {
-    return mTimingFunction.GetSplineValue(aPortion);
-  }
-  if (mType == nsTimingFunction::Type::StepStart) {
-    // There are diagrams in the spec that seem to suggest this check
-    // and the bounds point should not be symmetric with StepEnd, but
-    // should actually step up at rather than immediately after the
-    // fraction points.  However, we rely on rounding negative values
-    // up to zero, so we can't do that.  And it's not clear the spec
-    // really meant it.
-    return 1.0 - StepEnd(mSteps, 1.0 - aPortion);
-  }
-  MOZ_ASSERT(mType == nsTimingFunction::Type::StepEnd, "bad type");
-  return StepEnd(mSteps, aPortion);
-}
-
-int32_t
-ComputedTimingFunction::Compare(const ComputedTimingFunction& aRhs) const
-{
-  if (mType != aRhs.mType) {
-    return int32_t(mType) - int32_t(aRhs.mType);
-  }
-
-  if (mType == nsTimingFunction::Type::CubicBezier) {
-    int32_t order = mTimingFunction.Compare(aRhs.mTimingFunction);
-    if (order != 0) {
-      return order;
-    }
-  } else if (mType == nsTimingFunction::Type::StepStart ||
-             mType == nsTimingFunction::Type::StepEnd) {
-    if (mSteps != aRhs.mSteps) {
-      return int32_t(mSteps) - int32_t(aRhs.mSteps);
-    }
-    if (mStepSyntax != aRhs.mStepSyntax) {
-      return int32_t(mStepSyntax) - int32_t(aRhs.mStepSyntax);
-    }
-  }
-
-  return 0;
-}
-
-void
-ComputedTimingFunction::AppendToString(nsAString& aResult) const
-{
-  switch (mType) {
-    case nsTimingFunction::Type::CubicBezier:
-      nsStyleUtil::AppendCubicBezierTimingFunction(mTimingFunction.X1(),
-                                                   mTimingFunction.Y1(),
-                                                   mTimingFunction.X2(),
-                                                   mTimingFunction.Y2(),
-                                                   aResult);
-      break;
-    case nsTimingFunction::Type::StepStart:
-    case nsTimingFunction::Type::StepEnd:
-      nsStyleUtil::AppendStepsTimingFunction(mType, mSteps, mStepSyntax,
-                                             aResult);
-      break;
-    default:
-      nsStyleUtil::AppendCubicBezierKeywordTimingFunction(mType, aResult);
-      break;
-  }
-}
-
-
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(KeyframeEffectReadOnly,
                                    AnimationEffectReadOnly,
                                    mTarget,
                                    mAnimation)
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(KeyframeEffectReadOnly,
--- a/dom/animation/KeyframeEffect.h
+++ b/dom/animation/KeyframeEffect.h
@@ -8,26 +8,25 @@
 #define mozilla_dom_KeyframeEffect_h
 
 #include "nsAutoPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsCSSPseudoElements.h"
 #include "nsIDocument.h"
 #include "nsWrapperCache.h"
 #include "mozilla/Attributes.h"
-#include "mozilla/LayerAnimationInfo.h" // LayerAnimations::kRecords
+#include "mozilla/ComputedTimingFunction.h" // ComputedTimingFunction
+#include "mozilla/LayerAnimationInfo.h"     // LayerAnimations::kRecords
 #include "mozilla/StickyTimeDuration.h"
 #include "mozilla/StyleAnimationValue.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/dom/AnimationEffectReadOnly.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/KeyframeBinding.h"
 #include "mozilla/dom/Nullable.h"
-#include "nsSMILKeySpline.h"
-#include "nsStyleStruct.h" // for nsTimingFunction
 
 struct JSContext;
 class nsCSSPropertySet;
 
 namespace mozilla {
 
 class AnimValuesStyleRule;
 
@@ -92,51 +91,16 @@ struct ComputedTiming
     Null,   // Not sampled (null sample time)
     Before, // Sampled prior to the start of the active interval
     Active, // Sampled within the active interval
     After   // Sampled after (or at) the end of the active interval
   };
   AnimationPhase      mPhase = AnimationPhase::Null;
 };
 
-class ComputedTimingFunction
-{
-public:
-  typedef nsTimingFunction::Type Type;
-  typedef nsTimingFunction::StepSyntax StepSyntax;
-  void Init(const nsTimingFunction &aFunction);
-  double GetValue(double aPortion) const;
-  const nsSMILKeySpline* GetFunction() const {
-    NS_ASSERTION(HasSpline(), "Type mismatch");
-    return &mTimingFunction;
-  }
-  Type GetType() const { return mType; }
-  bool HasSpline() const { return nsTimingFunction::IsSplineType(mType); }
-  uint32_t GetSteps() const { return mSteps; }
-  StepSyntax GetStepSyntax() const { return mStepSyntax; }
-  bool operator==(const ComputedTimingFunction& aOther) const {
-    return mType == aOther.mType &&
-           (HasSpline() ?
-            mTimingFunction == aOther.mTimingFunction :
-            (mSteps == aOther.mSteps &&
-             mStepSyntax == aOther.mStepSyntax));
-  }
-  bool operator!=(const ComputedTimingFunction& aOther) const {
-    return !(*this == aOther);
-  }
-  int32_t Compare(const ComputedTimingFunction& aRhs) const;
-  void AppendToString(nsAString& aResult) const;
-
-private:
-  Type mType;
-  nsSMILKeySpline mTimingFunction;
-  uint32_t mSteps;
-  StepSyntax mStepSyntax;
-};
-
 struct AnimationPropertySegment
 {
   float mFromKey, mToKey;
   StyleAnimationValue mFromValue, mToValue;
   ComputedTimingFunction mTimingFunction;
 
   bool operator==(const AnimationPropertySegment& aOther) const {
     return mFromKey == aOther.mFromKey &&
--- a/dom/animation/moz.build
+++ b/dom/animation/moz.build
@@ -13,23 +13,25 @@ EXPORTS.mozilla.dom += [
     'AnimationTimeline.h',
     'DocumentTimeline.h',
     'KeyframeEffect.h',
 ]
 
 EXPORTS.mozilla += [
     'AnimationComparator.h',
     'AnimationUtils.h',
+    'ComputedTimingFunction.h',
     'PendingAnimationTracker.h',
 ]
 
 UNIFIED_SOURCES += [
     'Animation.cpp',
     'AnimationEffectReadOnly.cpp',
     'AnimationTimeline.cpp',
+    'ComputedTimingFunction.cpp',
     'DocumentTimeline.cpp',
     'KeyframeEffect.cpp',
     'PendingAnimationTracker.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/dom/base',
 ]
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -101,17 +101,16 @@ class nsViewportInfo;
 class nsWrapperCache;
 class nsAttrValue;
 class nsITransferable;
 class nsPIWindowRoot;
 class nsIWindowProvider;
 
 struct JSPropertyDescriptor;
 struct JSRuntime;
-struct nsIntMargin;
 
 template<class E> class nsCOMArray;
 template<class K, class V> class nsDataHashtable;
 template<class K, class V> class nsRefPtrHashtable;
 template<class T> class nsReadingIterator;
 
 namespace mozilla {
 class ErrorResult;
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -8889,37 +8889,40 @@ nsDocument::Destroy()
 
   // Shut down our external resource map.  We might not need this for
   // leak-fixing if we fix nsDocumentViewer to do cycle-collection, but
   // tearing down all those frame trees right now is the right thing to do.
   mExternalResourceMap.Shutdown();
 
   mRegistry = nullptr;
 
-  using mozilla::dom::workers::ServiceWorkerManager;
-  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
-  if (swm) {
-    ErrorResult error;
-    if (swm->IsControlled(this, error)) {
-      nsContentUtils::GetImgLoaderForDocument(this)->ClearCacheForControlledDocument(this);
-    }
-    swm->MaybeStopControlling(this);
-  }
-
   // XXX We really should let cycle collection do this, but that currently still
   //     leaks (see https://bugzilla.mozilla.org/show_bug.cgi?id=406684).
   ReleaseWrapper(static_cast<nsINode*>(this));
 }
 
 void
 nsDocument::RemovedFromDocShell()
 {
   if (mRemovedFromDocShell)
     return;
 
+  using mozilla::dom::workers::ServiceWorkerManager;
+  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+  if (swm) {
+    ErrorResult error;
+    if (swm->IsControlled(this, error)) {
+      imgLoader* loader = nsContentUtils::GetImgLoaderForDocument(this);
+      if (loader) {
+        loader->ClearCacheForControlledDocument(this);
+      }
+    }
+    swm->MaybeStopControlling(this);
+  }
+
   mRemovedFromDocShell = true;
   EnumerateActivityObservers(NotifyActivityChanged, nullptr);
 
   uint32_t i, count = mChildren.ChildCount();
   for (i = 0; i < count; ++i) {
     mChildren.ChildAt(i)->SaveSubtreeState();
   }
 }
--- a/dom/base/test/test_bug804395.html
+++ b/dom/base/test/test_bug804395.html
@@ -59,14 +59,16 @@ function runTests() {
   var test = tests.shift();
   test();
 }
 
 /** Test for Bug 804395 **/
 SimpleTest.waitForExplicitFinish();
 
 addLoadEvent(function() {
-   SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], runTests);
+  SpecialPowers.pushPrefEnv({"set": [["network.jar.block-remote-files", false]]}, function() {
+    SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], runTests);
+  });
 });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/bluetooth/bluedroid/BluetoothMapSmsManager.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothMapSmsManager.cpp
@@ -497,18 +497,17 @@ BluetoothMapSmsManager::ReplyToConnect()
   // Section 3.3.1 "Connect", IrOBEX 1.2
   // [opcode:1][length:2][version:1][flags:1][MaxPktSizeWeCanReceive:2]
   // [Headers:var]
   uint8_t req[255];
   int index = 7;
 
   req[3] = 0x10; // version=1.0
   req[4] = 0x00; // flag=0x00
-  req[5] = BluetoothMapSmsManager::MAX_PACKET_LENGTH >> 8;
-  req[6] = (uint8_t)BluetoothMapSmsManager::MAX_PACKET_LENGTH;
+  BigEndian::writeUint16(&req[5], BluetoothMapSmsManager::MAX_PACKET_LENGTH);
 
   // Section 6.4 "Establishing an OBEX Session", MapSms 1.2
   // Headers: [Who:16][Connection ID]
   index += AppendHeaderWho(&req[index], 255, kMapMasObexTarget.mUuid,
                            sizeof(BluetoothUuid));
   index += AppendHeaderConnectionId(&req[index], 0x01);
   SendMasObexData(req, ObexResponseCode::Success, index);
 }
--- a/dom/bluetooth/bluedroid/BluetoothOppManager.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothOppManager.cpp
@@ -861,18 +861,17 @@ BluetoothOppManager::ComposePacket(uint8
   int frameHeaderLength = 0;
 
   // See if this is the first part of each Put packet
   if (mPutPacketReceivedLength == 0) {
     // Section 3.3.3 "Put", IrOBEX 1.2
     // [opcode:1][length:2][Headers:var]
     frameHeaderLength = 3;
 
-    mPacketLength = ((static_cast<int>(data[1]) << 8) | data[2]) -
-                    frameHeaderLength;
+    mPacketLength = BigEndian::readUint16(&data[1]) - frameHeaderLength;
 
     /**
      * A PUT request from remote devices may be divided into multiple parts.
      * In other words, one request may need to be received multiple times,
      * so here we keep a variable mPutPacketReceivedLength to indicate if
      * current PUT request is done.
      */
     mReceivedDataBuffer = new uint8_t[mPacketLength];
@@ -1114,17 +1113,17 @@ BluetoothOppManager::ClientDataHandler(U
       BT_LOGR("The length of connect response packet is invalid");
       SendDisconnectRequest();
       return;
     }
 
     // Keep remote information
     mRemoteObexVersion = data[3];
     mRemoteConnectionFlags = data[4];
-    mRemoteMaxPacketLength = ((static_cast<int>(data[5]) << 8) | data[6]);
+    mRemoteMaxPacketLength = BigEndian::readUint16(&data[5]);
 
     // The length of file name exceeds maximum length.
     int fileNameByteLen = (mFileName.Length() + 1) * 2;
     int headerLen = kPutRequestHeaderSize + kPutRequestAppendHeaderSize;
     if (fileNameByteLen > mRemoteMaxPacketLength - headerLen) {
       BT_WARNING("The length of file name is aberrant.");
       SendDisconnectRequest();
       return;
--- a/dom/bluetooth/bluedroid/BluetoothPbapManager.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothPbapManager.cpp
@@ -237,17 +237,17 @@ BluetoothPbapManager::ReceiveSocketData(
       // Section 6.4 "Establishing an OBEX Session", PBAP 1.2
       // The OBEX header target shall equal to kPbapObexTarget.
       if (!CompareHeaderTarget(pktHeaders)) {
         ReplyError(ObexResponseCode::BadRequest);
         return;
       }
 
       // Save the max packet length from remote information
-      mRemoteMaxPacketLength = ((static_cast<int>(data[5]) << 8) | data[6]);
+      mRemoteMaxPacketLength = BigEndian::readUint16(&data[5]);
 
       if (mRemoteMaxPacketLength < kObexLeastMaxSize) {
         BT_LOGR("Remote maximum packet length %d is smaller than %d bytes",
           mRemoteMaxPacketLength, kObexLeastMaxSize);
         mRemoteMaxPacketLength = 0;
         ReplyError(ObexResponseCode::BadRequest);
         return;
       }
--- a/dom/bluetooth/bluez/BluetoothOppManager.cpp
+++ b/dom/bluetooth/bluez/BluetoothOppManager.cpp
@@ -837,18 +837,17 @@ BluetoothOppManager::ComposePacket(uint8
   const uint8_t* data = aMessage->GetData();
 
   // See if this is the first part of each Put packet
   if (mPutPacketReceivedLength == 0) {
     // Section 3.3.3 "Put", IrOBEX 1.2
     // [opcode:1][length:2][Headers:var]
     frameHeaderLength = 3;
 
-    mPacketLength = ((static_cast<int>(data[1]) << 8) | data[2]) -
-                    frameHeaderLength;
+    mPacketLength = BigEndian::readUint16(&data[1]) - frameHeaderLength;
     /**
      * A PUT request from remote devices may be divided into multiple parts.
      * In other words, one request may need to be received multiple times,
      * so here we keep a variable mPutPacketReceivedLength to indicate if
      * current PUT request is done.
      */
     mReceivedDataBuffer = new uint8_t[mPacketLength];
     mPutFinalFlag = (aOpCode == ObexRequestCode::PutFinal);
@@ -1089,17 +1088,17 @@ BluetoothOppManager::ClientDataHandler(U
       BT_LOGR("The length of connect response packet is invalid");
       SendDisconnectRequest();
       return;
     }
 
     // Keep remote information
     mRemoteObexVersion = data[3];
     mRemoteConnectionFlags = data[4];
-    mRemoteMaxPacketLength = (static_cast<int>(data[5]) << 8) | data[6];
+    mRemoteMaxPacketLength = BigEndian::readUint16(&data[5]);
 
     // The length of file name exceeds maximum length.
     int fileNameByteLen = (mFileName.Length() + 1) * 2;
     int headerLen = kPutRequestHeaderSize + kPutRequestAppendHeaderSize;
     if (fileNameByteLen > mRemoteMaxPacketLength - headerLen) {
       BT_WARNING("The length of file name is aberrant.");
       SendDisconnectRequest();
       return;
--- a/dom/bluetooth/common/BluetoothCommon.h
+++ b/dom/bluetooth/common/BluetoothCommon.h
@@ -4,16 +4,17 @@
  * 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/. */
 
 #ifndef mozilla_dom_bluetooth_BluetoothCommon_h
 #define mozilla_dom_bluetooth_BluetoothCommon_h
 
 #include <algorithm>
 #include "mozilla/Compiler.h"
+#include "mozilla/Endian.h"
 #include "mozilla/Observer.h"
 #include "nsAutoPtr.h"
 #include "nsPrintfCString.h"
 #include "nsString.h"
 #include "nsTArray.h"
 
 extern bool gBluetoothDebugFlag;
 
@@ -491,24 +492,22 @@ struct BluetoothAddress {
 
   void SetUAP(uint8_t aUAP)
   {
     mAddr[3] = aUAP;
   }
 
   uint16_t GetNAP() const
   {
-    return (static_cast<uint16_t>(mAddr[4])) |
-           (static_cast<uint16_t>(mAddr[5]) << 8);
+    return LittleEndian::readUint16(&mAddr[4]);
   }
 
   void SetNAP(uint16_t aNAP)
   {
-    mAddr[4] = aNAP;
-    mAddr[5] = aNAP >> 8;
+    LittleEndian::writeUint16(&mAddr[4], aNAP);
   }
 
 };
 
 struct BluetoothConfigurationParameter {
   uint8_t mType;
   uint16_t mLength;
   nsAutoArrayPtr<uint8_t> mValue;
@@ -630,51 +629,44 @@ struct BluetoothUuid {
    * and 4 (indices 2 and 3) are represented by UUID16. The rest of
    * the UUID is filled with the SDP base UUID.
    *
    * Below are helpers for accessing these values.
    */
 
   void SetUuid32(uint32_t aUuid32)
   {
-    mUuid[0] = static_cast<uint8_t>(0xff & (aUuid32 >> 24));
-    mUuid[1] = static_cast<uint8_t>(0xff & (aUuid32 >> 16));
-    mUuid[2] = static_cast<uint8_t>(0xff & (aUuid32 >> 8));
-    mUuid[3] = static_cast<uint8_t>(0xff & (aUuid32));
+    BigEndian::writeUint32(&mUuid[0], aUuid32);
     mUuid[4] = 0x00;
     mUuid[5] = 0x00;
     mUuid[6] = 0x10;
     mUuid[7] = 0x00;
     mUuid[8] = 0x80;
     mUuid[9] = 0x00;
     mUuid[10] = 0x00;
     mUuid[11] = 0x80;
     mUuid[12] = 0x5f;
     mUuid[13] = 0x9b;
     mUuid[14] = 0x34;
     mUuid[15] = 0xfb;
   }
 
   uint32_t GetUuid32() const
   {
-    return (static_cast<uint32_t>(mUuid[0]) << 24) |
-           (static_cast<uint32_t>(mUuid[1]) << 16) |
-           (static_cast<uint32_t>(mUuid[2]) << 8) |
-           (static_cast<uint32_t>(mUuid[3]));
+    return BigEndian::readUint32(&mUuid[0]);
   }
 
   void SetUuid16(uint16_t aUuid16)
   {
     SetUuid32(aUuid16); // MSB is 0x0000
   }
 
   uint16_t GetUuid16() const
   {
-    return (static_cast<uint16_t>(mUuid[2]) << 8) |
-           (static_cast<uint16_t>(mUuid[3]));
+    return BigEndian::readUint16(&mUuid[2]);
   }
 };
 
 struct BluetoothPinCode {
   uint8_t mPinCode[16]; /* not \0-terminated */
   uint8_t mLength;
 };
 
--- a/dom/bluetooth/common/ObexBase.cpp
+++ b/dom/bluetooth/common/ObexBase.cpp
@@ -18,34 +18,30 @@ BEGIN_BLUETOOTH_NAMESPACE
 int
 AppendHeader(uint8_t aHeaderId, uint8_t* aRetBuf, int aBufferSize,
              const uint8_t* aData, int aLength)
 {
   int headerLength = aLength + 3;
   int writtenLength = (headerLength < aBufferSize) ? headerLength : aBufferSize;
 
   aRetBuf[0] = aHeaderId;
-  aRetBuf[1] = (headerLength & 0xFF00) >> 8;
-  aRetBuf[2] = headerLength & 0x00FF;
+  BigEndian::writeUint16(&aRetBuf[1], headerLength);
   memcpy(&aRetBuf[3], aData, writtenLength - 3);
 
   return writtenLength;
 }
 
 /**
  * Append 4-byte integer to header
  */
 int
 AppendHeader(uint8_t aHeaderId, uint8_t* aRetBuf, int aValue)
 {
   aRetBuf[0] = aHeaderId;
-  aRetBuf[1] = (aValue & 0xFF000000) >> 24;
-  aRetBuf[2] = (aValue & 0x00FF0000) >> 16;
-  aRetBuf[3] = (aValue & 0x0000FF00) >> 8;
-  aRetBuf[4] = aValue & 0x000000FF;
+  BigEndian::writeInt32(&aRetBuf[1], aValue);
 
   return 5;
 }
 
 //
 // Exposed functions
 //
 
@@ -130,44 +126,41 @@ AppendHeaderEndOfBody(uint8_t* aRetBuf)
 
   return 3;
 }
 
 void
 SetObexPacketInfo(uint8_t* aRetBuf, uint8_t aOpcode, int aPacketLength)
 {
   aRetBuf[0] = aOpcode;
-  aRetBuf[1] = (aPacketLength & 0xFF00) >> 8;
-  aRetBuf[2] = aPacketLength & 0x00FF;
+  BigEndian::writeUint16(&aRetBuf[1], aPacketLength);
 }
 
 bool
 ParseHeaders(const uint8_t* aHeaderStart,
              int aTotalLength,
              ObexHeaderSet* aRetHandlerSet)
 {
   const uint8_t* ptr = aHeaderStart;
 
   while (ptr - aHeaderStart < aTotalLength) {
     ObexHeaderId headerId = (ObexHeaderId)*ptr++;
 
     uint16_t contentLength = 0;
-    uint8_t highByte, lowByte;
 
     // Defined in 2.1 OBEX Headers, IrOBEX 1.2
     switch (headerId >> 6)
     {
       case 0x00:
         // Null-terminated Unicode text, length prefixed with 2-byte
         // unsigned integer.
       case 0x01:
         // byte sequence, length prefixed with 2 byte unsigned integer.
-        highByte = *ptr++;
-        lowByte = *ptr++;
-        contentLength = (((uint16_t)highByte << 8) | lowByte) - 3;
+        contentLength = BigEndian::readUint16(ptr) - 3;
+        ptr += 2;
         break;
 
       case 0x02:
         // 1 byte quantity
         contentLength = 1;
         break;
 
       case 0x03:
--- a/dom/bluetooth/common/ObexBase.h
+++ b/dom/bluetooth/common/ObexBase.h
@@ -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/. */
 
 #ifndef mozilla_dom_bluetooth_ObexBase_h
 #define mozilla_dom_bluetooth_ObexBase_h
 
 #include "BluetoothCommon.h"
+#include "mozilla/Endian.h"
 #include "nsAutoPtr.h"
 #include "nsTArray.h"
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 const char FINAL_BIT = 0x80;
 
 /**
@@ -173,17 +174,17 @@ public:
          * the Name header is "a null terminated Unicode text string describing
          * the name of the object.", and that's the reason why we need to minus
          * 1 to get the real length of the file name.
          */
         int nameLength = mHeaders[i]->mDataLength / 2 - 1;
         uint8_t* ptr = mHeaders[i]->mData.get();
 
         for (int j = 0; j < nameLength; ++j) {
-          char16_t c = ((((uint32_t)ptr[j * 2]) << 8) | ptr[j * 2 + 1]);
+          char16_t c = BigEndian::readUint16(&ptr[j * 2]);
           aRetName += c;
         }
 
         break;
       }
     }
   }
 
@@ -206,20 +207,17 @@ public:
   void GetLength(uint32_t* aRetLength) const
   {
     int length = mHeaders.Length();
     *aRetLength = 0;
 
     for (int i = 0; i < length; ++i) {
       if (mHeaders[i]->mId == ObexHeaderId::Length) {
         uint8_t* ptr = mHeaders[i]->mData.get();
-        *aRetLength = ((uint32_t)ptr[0] << 24) |
-                      ((uint32_t)ptr[1] << 16) |
-                      ((uint32_t)ptr[2] << 8) |
-                      ((uint32_t)ptr[3]);
+        *aRetLength = BigEndian::readUint32(&ptr[0]);
         return;
       }
     }
   }
 
   void GetBody(uint8_t** aRetBody, int* aRetBodyLength) const
   {
     int length = mHeaders.Length();
--- a/dom/browser-element/BrowserElementChildPreload.js
+++ b/dom/browser-element/BrowserElementChildPreload.js
@@ -14,16 +14,21 @@ Cu.import("resource://gre/modules/Servic
 Cu.import("resource://gre/modules/BrowserElementPromptService.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/Microformats.js");
 Cu.import("resource://gre/modules/ExtensionContent.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "acs",
                                    "@mozilla.org/audiochannel/service;1",
                                    "nsIAudioChannelService");
+XPCOMUtils.defineLazyModuleGetter(this, "ManifestFinder",
+                                  "resource://gre/modules/ManifestFinder.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ManifestObtainer",
+                                  "resource://gre/modules/ManifestObtainer.jsm");
+
 
 var kLongestReturnedString = 128;
 
 function debug(msg) {
   //dump("BrowserElementChildPreload - " + msg + "\n");
 }
 
 function sendAsyncMsg(msg, data) {
@@ -284,17 +289,18 @@ BrowserElementChild.prototype = {
       "find-next": this._recvFindNext.bind(this),
       "clear-match": this._recvClearMatch.bind(this),
       "execute-script": this._recvExecuteScript,
       "get-audio-channel-volume": this._recvGetAudioChannelVolume,
       "set-audio-channel-volume": this._recvSetAudioChannelVolume,
       "get-audio-channel-muted": this._recvGetAudioChannelMuted,
       "set-audio-channel-muted": this._recvSetAudioChannelMuted,
       "get-is-audio-channel-active": this._recvIsAudioChannelActive,
-      "get-structured-data": this._recvGetStructuredData
+      "get-structured-data": this._recvGetStructuredData,
+      "get-web-manifest": this._recvGetWebManifest,
     }
 
     addMessageListener("browser-element-api:call", function(aMessage) {
       if (aMessage.data.msg_name in mmCalls) {
         return mmCalls[aMessage.data.msg_name].apply(self, arguments);
       }
     });
 
@@ -1522,17 +1528,36 @@ BrowserElementChild.prototype = {
   _recvIsAudioChannelActive: function(data) {
     debug("Received isAudioChannelActive message: (" + data.json.id + ")");
 
     let active = acs.isAudioChannelActive(content, data.json.args.audioChannel);
     sendAsyncMsg('got-is-audio-channel-active', {
       id: data.json.id, successRv: active
     });
   },
-
+  _recvGetWebManifest: Task.async(function* (data) {
+    debug(`Received GetWebManifest message: (${data.json.id})`);
+    let manifest = null;
+    let hasManifest = ManifestFinder.contentHasManifestLink(content);
+    if (hasManifest) {
+      try {
+        manifest = yield ManifestObtainer.contentObtainManifest(content);
+      } catch (e) {
+        sendAsyncMsg('got-web-manifest', {
+          id: data.json.id,
+          errorMsg: `Error fetching web manifest: ${e}.`,
+        });
+        return;
+      }
+    }
+    sendAsyncMsg('got-web-manifest', {
+      id: data.json.id,
+      successRv: manifest
+    });
+  }),
   _initFinder: function() {
     if (!this._finder) {
       try {
         this._findLimit = Services.prefs.getIntPref("accessibility.typeaheadfind.matchesCountLimit");
       } catch (e) {
         // Pref not available, assume 0, no match counting.
         this._findLimit = 0;
       }
--- a/dom/browser-element/BrowserElementParent.js
+++ b/dom/browser-element/BrowserElementParent.js
@@ -381,17 +381,18 @@ BrowserElementParent.prototype = {
       "caretstatechanged": this._handleCaretStateChanged,
       "findchange": this._handleFindChange,
       "execute-script-done": this._gotDOMRequestResult,
       "got-audio-channel-volume": this._gotDOMRequestResult,
       "got-set-audio-channel-volume": this._gotDOMRequestResult,
       "got-audio-channel-muted": this._gotDOMRequestResult,
       "got-set-audio-channel-muted": this._gotDOMRequestResult,
       "got-is-audio-channel-active": this._gotDOMRequestResult,
-      "got-structured-data": this._gotDOMRequestResult
+      "got-structured-data": this._gotDOMRequestResult,
+      "got-web-manifest": this._gotDOMRequestResult,
     };
 
     let mmSecuritySensitiveCalls = {
       "audioplaybackchange": this._fireEventFromMsg,
       "showmodalprompt": this._handleShowModalPrompt,
       "contextmenu": this._fireCtxMenuEvent,
       "securitychange": this._fireEventFromMsg,
       "locationchange": this._fireEventFromMsg,
@@ -1205,16 +1206,17 @@ BrowserElementParent.prototype = {
 
   isAudioChannelActive: function(aAudioChannel) {
     return this._sendDOMRequest('get-is-audio-channel-active',
                                 {audioChannel: aAudioChannel});
   },
 
   getStructuredData: defineDOMRequestMethod('get-structured-data'),
 
+  getWebManifest: defineDOMRequestMethod('get-web-manifest'),
   /**
    * Called when the visibility of the window which owns this iframe changes.
    */
   _ownerVisibilityChange: function() {
     this._sendAsyncMsg('owner-visibility-change',
                        {visible: !this._window.document.hidden});
   },
 
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/browserElement_getWebManifest.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the public domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+/*globals async, ok, is, SimpleTest, browserElementTestHelpers*/
+
+// Bug 1169633 - getWebManifest tests
+'use strict';
+SimpleTest.waitForExplicitFinish();
+browserElementTestHelpers.setEnabledPref(true);
+browserElementTestHelpers.addPermission();
+
+// request to load a manifest from a page that doesn't have a manifest.
+// The expected result to be null.
+var test1 = async(function* () {
+  var manifest = yield requestManifest('file_empty.html');
+  is(manifest, null, 'it should be null.');
+});
+
+// request to load a manifest from a page that has a manifest.
+// The expected manifest to have a property name whose value is 'pass'.
+var test2 = async(function* () {
+  var manifest = yield requestManifest('file_web_manifest.html');
+  is(manifest && manifest.name, 'pass', 'it should return a manifest with name pass.');
+});
+
+// Cause an exception by attempting to fetch a file URL,
+// expect onerror to be called.
+var test3 = async(function* () {
+  var gotError = false;
+  try {
+    yield requestManifest('file_illegal_web_manifest.html');
+  } catch (err) {
+    gotError = true;
+  }
+  ok(gotError, 'onerror was called on the DOMRequest.');
+});
+
+// Run the tests
+addEventListener('testready', () => {
+  Promise
+    .all([test1(), test2(), test3()])
+    .then(SimpleTest.finish);
+});
+
+function requestManifest(url) {
+  var iframe = document.createElement('iframe');
+  iframe.setAttribute('mozbrowser', 'true');
+  iframe.src = url;
+  document.body.appendChild(iframe);
+  return new Promise((resolve, reject) => {
+    iframe.addEventListener('mozbrowserloadend', function loadend() {
+      iframe.removeEventListener('mozbrowserloadend', loadend);
+      SimpleTest.executeSoon(() => {
+        var req = iframe.getWebManifest();
+        req.onsuccess = () => {
+          document.body.removeChild(iframe);
+          resolve(req.result);
+        };
+        req.onerror = () => {
+          document.body.removeChild(iframe);
+          reject(new Error(req.error));
+        };
+      });
+    });
+  });
+}
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/file_illegal_web_manifest.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<meta charset=utf-8>
+<head>
+<link rel="manifest" href="file://this_is_not_allowed!">
+</head>
+<h1>Support Page for Web Manifest Tests</h1>
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/file_web_manifest.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<meta charset=utf-8>
+<head>
+<link rel="manifest" href="file_web_manifest.json">
+</head>
+<h1>Support Page for Web Manifest Tests</h1>
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/file_web_manifest.json
@@ -0,0 +1,1 @@
+{"name": "pass"}
--- a/dom/browser-element/mochitest/mochitest-oop.ini
+++ b/dom/browser-element/mochitest/mochitest-oop.ini
@@ -114,8 +114,9 @@ disabled = bug 930449
 disabled = bug 924771
 [test_browserElement_oop_CloseApp.html]
 disabled = bug 924771
 [test_browserElement_oop_ExposableURI.html]
 disabled = bug 924771
 [test_browserElement_oop_GetContentDimensions.html]
 [test_browserElement_oop_AudioChannel.html]
 [test_browserElement_oop_SetNFCFocus.html]
+[test_browserElement_oop_getWebManifest.html]
--- a/dom/browser-element/mochitest/mochitest.ini
+++ b/dom/browser-element/mochitest/mochitest.ini
@@ -34,16 +34,17 @@ support-files =
   browserElement_ExposableURI.js
   browserElement_Find.js
   browserElement_FirstPaint.js
   browserElement_ForwardName.js
   browserElement_FrameWrongURI.js
   browserElement_GetScreenshot.js
   browserElement_GetScreenshotDppx.js
   browserElement_getStructuredData.js
+  browserElement_getWebManifest.js
   browserElement_Iconchange.js
   browserElement_LoadEvents.js
   browserElement_Manifestchange.js
   browserElement_Metachange.js
   browserElement_NextPaint.js
   browserElement_OpenNamed.js
   browserElement_OpenTab.js
   browserElement_OpenWindow.js
@@ -131,20 +132,24 @@ support-files =
   file_microdata_bad_itemref.html
   file_microdata_itemref.html
   file_microformats.html
   file_inputmethod.html
   file_post_request.html
   file_wyciwyg.html
   file_audio.html
   iframe_file_audio.html
+  file_web_manifest.html
+  file_web_manifest.json
+  file_illegal_web_manifest.html
 
 # Note: browserElementTestHelpers.js looks at the test's filename to determine
 # whether the test should be OOP.  "_oop_" signals OOP, "_inproc_" signals in
 # process.  Default is OOP.
+[test_browserElement_inproc_getWebManifest.html]
 [test_browserElement_NoAttr.html]
 [test_browserElement_NoPref.html]
 [test_browserElement_NoPermission.html]
 [test_browserElement_inproc_Alert.html]
 [test_browserElement_inproc_Viewmode.html]
 [test_browserElement_inproc_ThemeColor.html]
 skip-if = buildapp == 'b2g'
 [test_browserElement_inproc_AlertInFrame.html]
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/test_browserElement_inproc_getWebManifest.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for Bug 1169633</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/dom/browser-element/mochitest/browserElementTestHelpers.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script type="application/javascript;version=1.8"
+  src="async.js">
+</script>
+<script type="application/javascript;version=1.8"
+  src="browserElement_getWebManifest.js">
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/test_browserElement_oop_getWebManifest.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for Bug 1169633</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/dom/browser-element/mochitest/browserElementTestHelpers.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script type="application/javascript;version=1.8"
+  src="async.js"></script>
+<script type="application/javascript;version=1.8"
+  src="browserElement_getWebManifest.js">
+</script>
+</body>
+</html>
--- a/dom/browser-element/nsIBrowserElementAPI.idl
+++ b/dom/browser-element/nsIBrowserElementAPI.idl
@@ -4,34 +4,34 @@
  * 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 "nsISupports.idl"
 
 interface nsIDOMDOMRequest;
 interface nsIFrameLoader;
 
-[scriptable, function, uuid(c0c2dd9b-41ef-42dd-a4c1-e456619c1941)]
+[scriptable, function, uuid(00d0e19d-bd67-491f-8e85-b9905224d3bb)]
 interface nsIBrowserElementNextPaintListener : nsISupports
 {
   void recvNextPaint();
 };
 
 %{C++
 #define BROWSER_ELEMENT_API_CONTRACTID "@mozilla.org/dom/browser-element-api;1"
 #define BROWSER_ELEMENT_API_CID                                 \
     { 0x651db7e3, 0x1734, 0x4536,                               \
       { 0xb1, 0x5a, 0x5b, 0x3a, 0xe6, 0x44, 0x13, 0x4c } }
 %}
 
 /**
  * Interface to the BrowserElementParent implementation. All methods
  * but setFrameLoader throw when the remote process is dead.
  */
-[scriptable, uuid(9946695c-1ed3-4abb-bc60-6f8947fd5641)]
+[scriptable, uuid(1e098c3a-7d65-452d-a2b2-9ffd1b6e04bb)]
 interface nsIBrowserElementAPI : nsISupports
 {
   const long FIND_CASE_SENSITIVE = 0;
   const long FIND_CASE_INSENSITIVE = 1;
 
   const long FIND_FORWARD = 0;
   const long FIND_BACKWARD = 1;
 
@@ -97,16 +97,22 @@ interface nsIBrowserElementAPI : nsISupp
 
   nsIDOMDOMRequest isAudioChannelActive(in uint32_t audioChannel);
 
   void setNFCFocus(in boolean isFocus);
 
   nsIDOMDOMRequest executeScript(in DOMString script, in jsval options);
 
   /**
+   * Returns an object that represents a Web Manifest:
+   * http://w3c.github.io/manifest/
+   */
+  nsIDOMDOMRequest getWebManifest();
+
+  /**
    * Returns a JSON string representing Microdata objects on the page.
    * Format is described at:
    *   https://html.spec.whatwg.org/multipage/microdata.html#json
    *
    * Also contains hCard and hCalendar objects after converting them
    * to equivalent Microdata objects described at:
    *   https://html.spec.whatwg.org/multipage/microdata.html#vcard
    *   https://html.spec.whatwg.org/multipage/microdata.html#vevent
--- a/dom/canvas/WebGL2ContextUniforms.cpp
+++ b/dom/canvas/WebGL2ContextUniforms.cpp
@@ -307,32 +307,17 @@ WebGL2Context::GetUniformIndices(WebGLPr
         return;
 
     if (!ValidateObject("getUniformIndices: program", program))
         return;
 
     if (!uniformNames.Length())
         return;
 
-    GLuint progname = program->mGLName;
-    size_t count = uniformNames.Length();
-    nsTArray<GLuint>& arr = retval.SetValue();
-
-    MakeContextCurrent();
-
-    for (size_t n = 0; n < count; n++) {
-        NS_LossyConvertUTF16toASCII name(uniformNames[n]);
-        //        const GLchar* glname = name.get();
-        const GLchar* glname = nullptr;
-        name.BeginReading(glname);
-
-        GLuint index = 0;
-        gl->fGetUniformIndices(progname, 1, &glname, &index);
-        arr.AppendElement(index);
-    }
+    program->GetUniformIndices(uniformNames, retval);
 }
 
 void
 WebGL2Context::GetActiveUniforms(WebGLProgram* program,
                                  const dom::Sequence<GLuint>& uniformIndices,
                                  GLenum pname,
                                  dom::Nullable< nsTArray<GLint> >& retval)
 {
--- a/dom/canvas/WebGLProgram.cpp
+++ b/dom/canvas/WebGLProgram.cpp
@@ -754,16 +754,61 @@ WebGLProgram::GetUniformLocation(const n
         return nullptr;
 
     RefPtr<WebGLUniformLocation> locObj = new WebGLUniformLocation(mContext, LinkInfo(),
                                                                      loc, activeInfo);
     return locObj.forget();
 }
 
 void
+WebGLProgram::GetUniformIndices(const dom::Sequence<nsString>& uniformNames,
+                                dom::Nullable< nsTArray<GLuint> >& retval) const
+{
+    size_t count = uniformNames.Length();
+    nsTArray<GLuint>& arr = retval.SetValue();
+
+    gl::GLContext* gl = mContext->GL();
+    gl->MakeCurrent();
+
+    for (size_t i = 0; i < count; i++) {
+        const NS_LossyConvertUTF16toASCII userName(uniformNames[i]);
+
+        nsDependentCString baseUserName;
+        bool isArray;
+        size_t arrayIndex;
+        if (!ParseName(userName, &baseUserName, &isArray, &arrayIndex)) {
+            arr.AppendElement(LOCAL_GL_INVALID_INDEX);
+            continue;
+        }
+
+        const WebGLActiveInfo* activeInfo;
+        if (!LinkInfo()->FindUniform(baseUserName, &activeInfo)) {
+            arr.AppendElement(LOCAL_GL_INVALID_INDEX);
+            continue;
+        }
+
+        const nsCString& baseMappedName = activeInfo->mBaseMappedName;
+
+        nsAutoCString mappedName(baseMappedName);
+        if (isArray) {
+            mappedName.AppendLiteral("[");
+            mappedName.AppendInt(uint32_t(arrayIndex));
+            mappedName.AppendLiteral("]");
+        }
+
+        const GLchar* mappedNameBytes = mappedName.BeginReading();
+
+        GLuint index = 0;
+        gl->fGetUniformIndices(mGLName, 1, &mappedNameBytes, &index);
+        arr.AppendElement(index);
+    }
+}
+
+
+void
 WebGLProgram::UniformBlockBinding(GLuint uniformBlockIndex, GLuint uniformBlockBinding) const
 {
     if (!IsLinked()) {
         mContext->ErrorInvalidOperation("getActiveUniformBlockName: `program` must be linked.");
         return;
     }
 
     const webgl::LinkedProgramInfo* linkInfo = LinkInfo();
--- a/dom/canvas/WebGLProgram.h
+++ b/dom/canvas/WebGLProgram.h
@@ -156,16 +156,18 @@ public:
     GLuint GetUniformBlockIndex(const nsAString& name) const;
     void GetActiveUniformBlockName(GLuint uniformBlockIndex, nsAString& name) const;
     void GetActiveUniformBlockParam(GLuint uniformBlockIndex, GLenum pname,
                                     dom::Nullable<dom::OwningUnsignedLongOrUint32ArrayOrBoolean>& retval) const;
     void GetActiveUniformBlockActiveUniforms(JSContext* cx, GLuint uniformBlockIndex,
                                              dom::Nullable<dom::OwningUnsignedLongOrUint32ArrayOrBoolean>& retval,
                                              ErrorResult& rv) const;
     already_AddRefed<WebGLUniformLocation> GetUniformLocation(const nsAString& name) const;
+    void GetUniformIndices(const dom::Sequence<nsString>& uniformNames,
+                           dom::Nullable< nsTArray<GLuint> >& retval) const;
     void UniformBlockBinding(GLuint uniformBlockIndex, GLuint uniformBlockBinding) const;
 
     bool LinkProgram();
     bool UseProgram() const;
     void ValidateProgram() const;
 
     ////////////////
 
--- a/dom/canvas/WebGLShader.cpp
+++ b/dom/canvas/WebGLShader.cpp
@@ -352,18 +352,26 @@ WebGLShader::FindUniformByMappedName(con
     return true;
 }
 
 bool
 WebGLShader::FindUniformBlockByMappedName(const nsACString& mappedName,
                                           nsCString* const out_userName,
                                           bool* const out_isArray) const
 {
-    // TODO: Extract block information from shader validator.
-    return false;
+    if (!mValidator)
+        return false;
+
+    const std::string mappedNameStr(mappedName.BeginReading(), mappedName.Length());
+    std::string userNameStr;
+    if (!mValidator->FindUniformBlockByMappedName(mappedNameStr, &userNameStr))
+        return false;
+
+    *out_userName = userNameStr.c_str();
+    return true;
 }
 
 void
 WebGLShader::ApplyTransformFeedbackVaryings(GLuint prog,
                                             const std::vector<nsCString>& varyings,
                                             GLenum bufferMode,
                                             std::vector<std::string>* out_mappedVaryings) const
 {
@@ -374,19 +382,18 @@ WebGLShader::ApplyTransformFeedbackVaryi
     const size_t varyingsCount = varyings.size();
     std::vector<std::string> mappedVaryings;
 
     for (size_t i = 0; i < varyingsCount; i++) {
         const nsCString& userName = varyings[i];
         std::string userNameStr(userName.BeginReading());
 
         const std::string* mappedNameStr = &userNameStr;
-        // TODO: Are vertex->fragment shader varyings listed under attribs?
         if (mValidator)
-            mValidator->FindAttribMappedNameByUserName(userNameStr, &mappedNameStr);
+            mValidator->FindVaryingMappedNameByUserName(userNameStr, &mappedNameStr);
 
         mappedVaryings.push_back(*mappedNameStr);
     }
 
     // Temporary, tight packed array of string pointers into mappedVaryings.
     std::vector<const GLchar*> strings;
     strings.resize(varyingsCount);
     for (size_t i = 0; i < varyingsCount; i++) {
--- a/dom/canvas/WebGLShaderValidator.cpp
+++ b/dom/canvas/WebGLShaderValidator.cpp
@@ -356,29 +356,60 @@ ShaderValidator::FindAttribMappedNameByU
             *out_mappedName = &(itr->mappedName);
             return true;
         }
     }
 
     return false;
 }
 
+bool
+ShaderValidator::FindVaryingMappedNameByUserName(const std::string& userName,
+                                                 const std::string** const out_mappedName) const
+{
+    const std::vector<sh::Varying>& attribs = *ShGetVaryings(mHandle);
+    for (auto itr = attribs.begin(); itr != attribs.end(); ++itr) {
+        if (itr->name == userName) {
+            *out_mappedName = &(itr->mappedName);
+            return true;
+        }
+    }
+
+    return false;
+}
+
 // This must handle names like "foo.bar[0]".
 bool
 ShaderValidator::FindUniformByMappedName(const std::string& mappedName,
                                          std::string* const out_userName,
                                          bool* const out_isArray) const
 {
     const std::vector<sh::Uniform>& uniforms = *ShGetUniforms(mHandle);
     for (auto itr = uniforms.begin(); itr != uniforms.end(); ++itr) {
         const sh::ShaderVariable* found;
         if (!itr->findInfoByMappedName(mappedName, &found, out_userName))
             continue;
 
         *out_isArray = found->isArray();
         return true;
     }
 
+
+    return false;
+}
+
+bool
+ShaderValidator::FindUniformBlockByMappedName(const std::string& mappedName,
+                                              std::string* const out_userName) const
+{
+    const std::vector<sh::InterfaceBlock>& interfaces = *ShGetInterfaceBlocks(mHandle);
+    for (const auto& interface : interfaces) {
+        if (mappedName == interface.mappedName) {
+            *out_userName = interface.name;
+            return true;
+        }
+    }
+
     return false;
 }
 
 } // namespace webgl
 } // namespace mozilla
--- a/dom/canvas/WebGLShaderValidator.h
+++ b/dom/canvas/WebGLShaderValidator.h
@@ -45,17 +45,23 @@ public:
     size_t CalcNumSamplerUniforms() const;
 
     bool FindAttribUserNameByMappedName(const std::string& mappedName,
                                         const std::string** const out_userName) const;
 
     bool FindAttribMappedNameByUserName(const std::string& userName,
                                         const std::string** const out_mappedName) const;
 
+    bool FindVaryingMappedNameByUserName(const std::string& userName,
+                                         const std::string** const out_mappedName) const;
+
     bool FindUniformByMappedName(const std::string& mappedName,
                                  std::string* const out_userName,
                                  bool* const out_isArray) const;
+    bool FindUniformBlockByMappedName(const std::string& mappedName,
+                                      std::string* const out_userName) const;
+
 };
 
 } // namespace webgl
 } // namespace mozilla
 
 #endif // WEBGL_SHADER_VALIDATOR_H_
--- a/dom/html/nsBrowserElement.cpp
+++ b/dom/html/nsBrowserElement.cpp
@@ -773,9 +773,27 @@ nsBrowserElement::GetStructuredData(Erro
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
 
   return req.forget().downcast<DOMRequest>();
 }
 
+already_AddRefed<DOMRequest>
+nsBrowserElement::GetWebManifest(ErrorResult& aRv)
+{
+  NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+
+  nsCOMPtr<nsIDOMDOMRequest> req;
+  nsresult rv = mBrowserElementAPI->GetWebManifest(getter_AddRefs(req));
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
+  }
+
+  return req.forget().downcast<DOMRequest>();
+}
+
+
+
 } // namespace mozilla
--- a/dom/html/nsBrowserElement.h
+++ b/dom/html/nsBrowserElement.h
@@ -109,16 +109,18 @@ public:
                                                          ErrorResult& aRv);
 
   already_AddRefed<dom::DOMRequest> ExecuteScript(const nsAString& aScript,
                                                   const dom::BrowserElementExecuteScriptOptions& aOptions,
                                                   ErrorResult& aRv);
 
   already_AddRefed<dom::DOMRequest> GetStructuredData(ErrorResult& aRv);
 
+  already_AddRefed<dom::DOMRequest> GetWebManifest(ErrorResult& aRv);
+
   void SetNFCFocus(bool isFocus,
                    ErrorResult& aRv);
 
   // Helper
   static void GenerateAllowedAudioChannels(
                  nsPIDOMWindow* aWindow,
                  nsIFrameLoader* aFrameLoader,
                  nsIBrowserElementAPI* aAPI,
--- a/dom/html/test/browser_bug1108547.js
+++ b/dom/html/test/browser_bug1108547.js
@@ -24,86 +24,85 @@ function runPass(getterFile, finishedCal
 
       Services.obs.removeObserver(onStartup, topic);
       executeSoon(callback);
     }, topic, false);
   }
 
   // First, set the cookie in a normal window.
   gBrowser.selectedTab = gBrowser.addTab(rootDir + "file_bug1108547-1.html");
-  gBrowser.selectedBrowser.addEventListener("load", afterOpenCookieSetter, true);
+  BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(afterOpenCookieSetter);
 
   function afterOpenCookieSetter() {
-    gBrowser.selectedBrowser.removeEventListener("load", afterOpenCookieSetter, true);
     gBrowser.removeCurrentTab();
 
     // Now, open a private window.
     privateWin = OpenBrowserWindow({private: true});
       whenDelayedStartupFinished(privateWin, afterPrivateWindowOpened);
   }
 
   function afterPrivateWindowOpened() {
     // In the private window, open the getter file, and wait for a new tab to be opened.
     privateWin.gBrowser.selectedTab = privateWin.gBrowser.addTab(rootDir + getterFile);
     testBrowser = privateWin.gBrowser.selectedBrowser;
     privateWin.gBrowser.tabContainer.addEventListener("TabOpen", onNewTabOpened, true);
   }
 
+  function fetchResult() {
+    return ContentTask.spawn(testBrowser, null, function() {
+      return content.document.getElementById("result").textContent;
+    });
+  }
+
   function onNewTabOpened() {
     // When the new tab is opened, wait for it to load.
     privateWin.gBrowser.tabContainer.removeEventListener("TabOpen", onNewTabOpened, true);
-    privateWin.gBrowser.tabs[privateWin.gBrowser.tabs.length - 1].linkedBrowser.addEventListener("load", onNewTabLoaded, true);
+    BrowserTestUtils.browserLoaded(privateWin.gBrowser.tabs[privateWin.gBrowser.tabs.length - 1].linkedBrowser).then(fetchResult).then(onNewTabLoaded);
   }
 
-  function onNewTabLoaded() {
-    privateWin.gBrowser.tabs[privateWin.gBrowser.tabs.length - 1].linkedBrowser.removeEventListener("load", onNewTabLoaded, true);
-
+  function onNewTabLoaded(result) {
     // Now, ensure that the private tab doesn't have access to the cookie set in normal mode.
-    is(testBrowser.contentDocument.getElementById("result").textContent, "",
-       "Shouldn't have access to the cookies");
+    is(result, "", "Shouldn't have access to the cookies");
 
     // We're done with the private window, close it.
     privateWin.close();
 
     // Clear all cookies.
     Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager).removeAll();
 
     // Open a new private window, this time to set a cookie inside it.
     privateWin = OpenBrowserWindow({private: true});
       whenDelayedStartupFinished(privateWin, afterPrivateWindowOpened2);
   }
 
   function afterPrivateWindowOpened2() {
     // In the private window, open the setter file, and wait for it to load.
     privateWin.gBrowser.selectedTab = privateWin.gBrowser.addTab(rootDir + "file_bug1108547-1.html");
-    privateWin.gBrowser.selectedBrowser.addEventListener("load", afterOpenCookieSetter2, true);
+    BrowserTestUtils.browserLoaded(privateWin.gBrowser.selectedBrowser).then(afterOpenCookieSetter2);
   }
 
   function afterOpenCookieSetter2() {
     // We're done with the private window now, close it.
     privateWin.close();
 
     // Now try to read the cookie in a normal window, and wait for a new tab to be opened.
     gBrowser.selectedTab = gBrowser.addTab(rootDir + getterFile);
     testBrowser = gBrowser.selectedBrowser;
     gBrowser.tabContainer.addEventListener("TabOpen", onNewTabOpened2, true);
   }
 
   function onNewTabOpened2() {
     // When the new tab is opened, wait for it to load.
     gBrowser.tabContainer.removeEventListener("TabOpen", onNewTabOpened2, true);
-    gBrowser.tabs[gBrowser.tabs.length - 1].linkedBrowser.addEventListener("load", onNewTabLoaded2, true);
+    BrowserTestUtils.browserLoaded(gBrowser.tabs[gBrowser.tabs.length - 1].linkedBrowser).then(fetchResult).then(onNewTabLoaded2);
   }
 
-  function onNewTabLoaded2() {
-    gBrowser.tabs[gBrowser.tabs.length - 1].linkedBrowser.removeEventListener("load", onNewTabLoaded2, true);
-
+  function onNewTabLoaded2(result) {
     // Now, ensure that the normal tab doesn't have access to the cookie set in private mode.
-    is(testBrowser.contentDocument.getElementById("result").textContent, "",
-       "Shouldn't have access to the cookies");
+    is(result, "", "Shouldn't have access to the cookies");
 
     // Remove both of the tabs opened here.
     gBrowser.removeCurrentTab();
     gBrowser.removeCurrentTab();
 
     finishedCallback();
   }
 }
--- a/dom/html/test/test_bug392567.html
+++ b/dom/html/test/test_bug392567.html
@@ -73,14 +73,16 @@ function runTests()
   function processTestResult() {
     var expected = tests[currentTest][1].replace(/\$PARAMS/, "key=value" + currentTest);
     is(frame.location.href, expected, "Submitting to " + tests[currentTest][0]);
 
     setTimeout(runNextTest, 0);
   }
 }
 
-addLoadEvent(runTests);
+addLoadEvent(function() {
+   SpecialPowers.pushPrefEnv({"set": [["network.jar.block-remote-files", false]]}, runTests);
+});
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/interfaces/base/nsIServiceWorkerManager.idl
+++ b/dom/interfaces/base/nsIServiceWorkerManager.idl
@@ -29,17 +29,25 @@ interface nsIServiceWorkerRegistrationIn
   readonly attribute DOMString scope;
   readonly attribute DOMString scriptSpec;
   readonly attribute DOMString currentWorkerURL;
 
   readonly attribute DOMString activeCacheName;
   readonly attribute DOMString waitingCacheName;
 };
 
-[scriptable, builtinclass, uuid(10f80c8c-7bf5-479e-a8d8-12ef50c802e8)]
+[scriptable, uuid(9e523e7c-ad6f-4df0-8077-c74aebbc679d)]
+interface nsIServiceWorkerManagerListener : nsISupports
+{
+  void onRegister(in nsIServiceWorkerRegistrationInfo aInfo);
+
+  void onUnregister(in nsIServiceWorkerRegistrationInfo aInfo);
+};
+
+[scriptable, builtinclass, uuid(2f61820a-1e9a-4c16-bf1c-ce182c5f5d6d)]
 interface nsIServiceWorkerManager : nsISupports
 {
   /**
    * Registers a ServiceWorker with script loaded from `aScriptURI` to act as
    * the ServiceWorker for aScope.  Requires a valid entry settings object on
    * the stack. This means you must call this from content code 'within'
    * a window.
    *
@@ -140,13 +148,17 @@ interface nsIServiceWorkerManager : nsIS
                                   in AString aData,
                                   in AString aBehavior);
   [optional_argc] void sendPushEvent(in ACString aOriginAttributes,
                                      in ACString aScope,
                                      [optional] in uint32_t aDataLength,
                                      [optional, array, size_is(aDataLength)] in uint8_t aDataBytes);
   void sendPushSubscriptionChangeEvent(in ACString aOriginAttributes,
                                        in ACString scope);
+
+  void addListener(in nsIServiceWorkerManagerListener aListener);
+
+  void removeListener(in nsIServiceWorkerManagerListener aListener);
 };
 
 %{ C++
 #define SERVICEWORKERMANAGER_CONTRACTID "@mozilla.org/serviceworkers/manager;1"
 %}
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -214,16 +214,20 @@
 #ifdef MOZ_WIDGET_GONK
 #include "nsIVolume.h"
 #include "nsVolumeService.h"
 #include "nsIVolumeService.h"
 #include "SpeakerManagerService.h"
 using namespace mozilla::system;
 #endif
 
+#ifdef MOZ_WIDGET_GTK
+#include <gdk/gdk.h>
+#endif
+
 #ifdef MOZ_B2G_BT
 #include "BluetoothParent.h"
 #include "BluetoothService.h"
 #endif
 
 #include "mozilla/RemoteSpellCheckEngineParent.h"
 
 #ifdef MOZ_B2G_FM
@@ -1167,16 +1171,28 @@ ContentParent::RecvIsGMPPresentOnDisk(co
 bool
 ContentParent::RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv, uint32_t* aRunID)
 {
     *aRv = NS_OK;
     return mozilla::plugins::SetupBridge(aPluginId, this, false, aRv, aRunID);
 }
 
 bool
+ContentParent::RecvUngrabPointer(const uint32_t& aTime)
+{
+#if !defined(MOZ_WIDGET_GTK)
+    NS_RUNTIMEABORT("This message only makes sense on GTK platforms");
+    return false;
+#else
+    gdk_pointer_ungrab(aTime);
+    return true;
+#endif
+}
+
+bool
 ContentParent::RecvConnectPluginBridge(const uint32_t& aPluginId, nsresult* aRv)
 {
     *aRv = NS_OK;
     // We don't need to get the run ID for the plugin, since we already got it
     // in the first call to SetupBridge in RecvLoadPlugin, so we pass in a dummy
     // pointer and just throw it away.
     uint32_t dummy = 0;
     return mozilla::plugins::SetupBridge(aPluginId, this, true, aRv, &dummy);
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -189,16 +189,18 @@ public:
 
     virtual bool RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv, uint32_t* aRunID) override;
     virtual bool RecvConnectPluginBridge(const uint32_t& aPluginId, nsresult* aRv) override;
     virtual bool RecvGetBlocklistState(const uint32_t& aPluginId, uint32_t* aIsBlocklisted) override;
     virtual bool RecvFindPlugins(const uint32_t& aPluginEpoch,
                                  nsTArray<PluginTag>* aPlugins,
                                  uint32_t* aNewPluginEpoch) override;
 
+    virtual bool RecvUngrabPointer(const uint32_t& aTime) override;
+
     NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(ContentParent, nsIObserver)
 
     NS_DECL_CYCLE_COLLECTING_ISUPPORTS
     NS_DECL_NSIOBSERVER
     NS_DECL_NSIDOMGEOPOSITIONCALLBACK
     NS_DECL_NSIDOMGEOPOSITIONERRORCALLBACK
 
     /**
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -1129,15 +1129,22 @@ parent:
       returns (nsresult rv,
                bool windowOpened,
                FrameScriptInfo[] frameScripts,
                nsCString urlToLoad);
 
     sync GetDeviceStorageLocation(nsString type)
         returns (nsString path);
 
+    /**
+     * Tells the parent to ungrab the pointer on the default display.
+     *
+     * This is for GTK platforms where we have to ensure the pointer ungrab happens in the
+     * chrome process as that's the process that receives the pointer event.
+     */
+    sync UngrabPointer(uint32_t time);
 both:
      AsyncMessage(nsString aMessage, ClonedMessageData aData,
                   CpowEntry[] aCpows, Principal aPrincipal);
 };
 
 }
 }
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -995,30 +995,48 @@ MediaStreamGraphImpl::AllFinishedStreams
     if (stream->mFinished && !stream->mNotifiedFinished) {
       return false;
     }
   }
   return true;
 }
 
 void
-MediaStreamGraphImpl::UpdateGraph(GraphTime aEndBlockingDecisions)
+MediaStreamGraphImpl::RunMessageAfterProcessing(nsAutoPtr<ControlMessage> aMessage)
+{
+  MOZ_ASSERT(CurrentDriver()->OnThread());
+
+  if (mFrontMessageQueue.IsEmpty()) {
+    mFrontMessageQueue.AppendElement();
+  }
+
+  // Only one block is used for messages from the graph thread.
+  MOZ_ASSERT(mFrontMessageQueue.Length() == 1);
+  mFrontMessageQueue[0].mMessages.AppendElement(Move(aMessage));
+}
+
+void
+MediaStreamGraphImpl::RunMessagesInQueue()
 {
   // Calculate independent action times for each batch of messages (each
   // batch corresponding to an event loop task). This isolates the performance
   // of different scripts to some extent.
   for (uint32_t i = 0; i < mFrontMessageQueue.Length(); ++i) {
     nsTArray<nsAutoPtr<ControlMessage> >& messages = mFrontMessageQueue[i].mMessages;
 
     for (uint32_t j = 0; j < messages.Length(); ++j) {
       messages[j]->Run();
     }
   }
   mFrontMessageQueue.Clear();
-
+}
+
+void
+MediaStreamGraphImpl::UpdateGraph(GraphTime aEndBlockingDecisions)
+{
   MOZ_ASSERT(aEndBlockingDecisions >= mProcessedTime);
   // The next state computed time can be the same as the previous: it
   // means the driver would be have been blocking indefinitly, but the graph has
   // been woken up right after having been to sleep.
   MOZ_ASSERT(aEndBlockingDecisions >= mStateComputedTime);
 
   UpdateStreamOrder();
 
@@ -1199,28 +1217,35 @@ MediaStreamGraphImpl::UpdateMainThreadSt
   return true;
 }
 
 bool
 MediaStreamGraphImpl::OneIteration(GraphTime aStateEnd)
 {
   MaybeProduceMemoryReport();
 
+  // Process graph message from the main thread for this iteration.
+  RunMessagesInQueue();
+
   GraphTime stateEnd = std::min(aStateEnd, mEndTime);
   UpdateGraph(stateEnd);
 
   mStateComputedTime = stateEnd;
 
   Process();
 
   GraphTime oldProcessedTime = mProcessedTime;
   mProcessedTime = stateEnd;
 
   UpdateCurrentTimeForStreams(oldProcessedTime);
 
+  // Process graph messages queued from RunMessageAfterProcessing() on this
+  // thread during the iteration.
+  RunMessagesInQueue();
+
   return UpdateMainThreadState();
 }
 
 void
 MediaStreamGraphImpl::ApplyStreamUpdate(StreamUpdate* aUpdate)
 {
   mMonitor.AssertCurrentThreadOwns();
 
@@ -2826,24 +2851,21 @@ MediaStreamGraph::CreateAudioCaptureStre
                                            TrackID aTrackId)
 {
   AudioCaptureStream* stream = new AudioCaptureStream(aWrapper, aTrackId);
   AddStream(stream);
   return stream;
 }
 
 void
-MediaStreamGraph::AddStream(MediaStream* aStream, uint32_t aFlags)
+MediaStreamGraph::AddStream(MediaStream* aStream)
 {
   NS_ADDREF(aStream);
   MediaStreamGraphImpl* graph = static_cast<MediaStreamGraphImpl*>(this);
   aStream->SetGraphImpl(graph);
-  if (aFlags & ADD_STREAM_SUSPENDED) {
-    aStream->IncrementSuspendCount();
-  }
   graph->AppendMessage(new CreateMessage(aStream));
 }
 
 class GraphStartedRunnable final : public nsRunnable
 {
 public:
   GraphStartedRunnable(AudioNodeStream* aStream, MediaStreamGraph* aGraph)
   : mStream(aStream)
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -1197,23 +1197,20 @@ public:
    */
   ProcessedMediaStream* CreateTrackUnionStream(DOMMediaStream* aWrapper);
   /**
    * Create a stream that will mix all its audio input.
    */
   ProcessedMediaStream* CreateAudioCaptureStream(DOMMediaStream* aWrapper,
                                                  TrackID aTrackId);
 
-  enum {
-    ADD_STREAM_SUSPENDED = 0x01
-  };
   /**
    * Add a new stream to the graph.  Main thread.
    */
-  void AddStream(MediaStream* aStream, uint32_t aFlags = 0);
+  void AddStream(MediaStream* aStream);
 
   /* From the main thread, ask the MSG to send back an event when the graph
    * thread is running, and audio is being processed. */
   void NotifyWhenGraphStarted(AudioNodeStream* aNodeStream);
   /* From the main thread, suspend, resume or close an AudioContext.
    * aStreams are the streams of all the AudioNodes of the AudioContext that
    * need to be suspended or resumed. This can be empty if this is a second
    * consecutive suspend call and all the nodes are already suspended.
--- a/dom/media/MediaStreamGraphImpl.h
+++ b/dom/media/MediaStreamGraphImpl.h
@@ -33,18 +33,21 @@ class AudioOutputObserver;
 struct StreamUpdate
 {
   RefPtr<MediaStream> mStream;
   StreamTime mNextMainThreadCurrentTime;
   bool mNextMainThreadFinished;
 };
 
 /**
- * This represents a message passed from the main thread to the graph thread.
- * A ControlMessage always has a weak reference a particular affected stream.
+ * This represents a message run on the graph thread to modify stream or graph
+ * state.  These are passed from main thread to graph thread through
+ * AppendMessage(), or scheduled on the graph thread with
+ * RunMessageAfterProcessing().  A ControlMessage
+ * always has a weak reference to a particular affected stream.
  */
 class ControlMessage
 {
 public:
   explicit ControlMessage(MediaStream* aStream) : mStream(aStream)
   {
     MOZ_COUNT_CTOR(ControlMessage);
   }
@@ -53,16 +56,18 @@ public:
   {
     MOZ_COUNT_DTOR(ControlMessage);
   }
   // Do the action of this message on the MediaStreamGraph thread. Any actions
   // affecting graph processing should take effect at mProcessedTime.
   // All stream data for times < mProcessedTime has already been
   // computed.
   virtual void Run() = 0;
+  // RunDuringShutdown() is only relevant to messages generated on the main
+  // thread (for AppendMessage()).
   // When we're shutting down the application, most messages are ignored but
   // some cleanup messages should still be processed (on the main thread).
   // This must not add new control messages to the graph.
   virtual void RunDuringShutdown() {}
   MediaStream* GetStream() { return mStream; }
 
 protected:
   // We do not hold a reference to mStream. The graph will be holding
@@ -213,18 +218,22 @@ public:
    */
   bool ShouldUpdateMainThread();
   // The following methods are the various stages of RunThread processing.
   /**
    * Advance all stream state to mStateComputedTime.
    */
   void UpdateCurrentTimeForStreams(GraphTime aPrevCurrentTime);
   /**
-   * Process graph message for this iteration, update stream processing order,
-   * and recompute stream blocking until aEndBlockingDecisions.
+   * Process graph messages in mFrontMessageQueue.
+   */
+  void RunMessagesInQueue();
+  /**
+   * Update stream processing order and recompute stream blocking until
+   * aEndBlockingDecisions.
    */
   void UpdateGraph(GraphTime aEndBlockingDecisions);
 
   void SwapMessageQueues()
   {
     MOZ_ASSERT(CurrentDriver()->OnThread());
     MOZ_ASSERT(mFrontMessageQueue.IsEmpty());
     mMonitor.AssertCurrentThreadOwns();
@@ -238,16 +247,24 @@ public:
   /**
    * Extract any state updates pending in aStream, and apply them.
    */
   void ExtractPendingInput(SourceMediaStream* aStream,
                            GraphTime aDesiredUpToTime,
                            bool* aEnsureNextIteration);
 
   /**
+   * For use during ProcessedMediaStream::ProcessInput() or
+   * MediaStreamListener callbacks, when graph state cannot be changed.
+   * Schedules |aMessage| to run after processing, at a time when graph state
+   * can be changed.  Graph thread.
+   */
+  void RunMessageAfterProcessing(nsAutoPtr<ControlMessage> aMessage);
+
+  /**
    * Called when a suspend/resume/close operation has been completed, on the
    * graph thread.
    */
   void AudioContextOperationCompleted(MediaStream* aStream,
                                       void* aPromise,
                                       dom::AudioContextOperation aOperation);
 
   /**
--- a/dom/media/mediasource/TrackBuffersManager.cpp
+++ b/dom/media/mediasource/TrackBuffersManager.cpp
@@ -575,18 +575,18 @@ TrackBuffersManager::CodedFrameRemoval(T
   return dataRemoved;
 }
 
 void
 TrackBuffersManager::UpdateBufferedRanges()
 {
   MonitorAutoLock mon(mMonitor);
 
-  mVideoBufferedRanges = mVideoTracks.mBufferedRanges;
-  mAudioBufferedRanges = mAudioTracks.mBufferedRanges;
+  mVideoBufferedRanges = mVideoTracks.mSanitizedBufferedRanges;
+  mAudioBufferedRanges = mAudioTracks.mSanitizedBufferedRanges;
 
 #if DEBUG
   if (HasVideo()) {
     MSE_DEBUG("after video ranges=%s",
               DumpTimeRanges(mVideoTracks.mBufferedRanges).get());
   }
   if (HasAudio()) {
     MSE_DEBUG("after audio ranges=%s",
@@ -1683,22 +1683,23 @@ TrackBuffersManager::InsertFrames(TrackB
     }
   }
 
   TrackBuffer& data = trackBuffer.mBuffers.LastElement();
   data.InsertElementsAt(trackBuffer.mNextInsertionIndex.ref(), aSamples);
   trackBuffer.mNextInsertionIndex.ref() += aSamples.Length();
 
   // Update our buffered range with new sample interval.
+  trackBuffer.mBufferedRanges += aIntervals;
   // We allow a fuzz factor in our interval of half a frame length,
   // as fuzz is +/- value, giving an effective leeway of a full frame
   // length.
   TimeIntervals range(aIntervals);
   range.SetFuzz(trackBuffer.mLongestFrameDuration.ref() / 2);
-  trackBuffer.mBufferedRanges += range;
+  trackBuffer.mSanitizedBufferedRanges += range;
 }
 
 void
 TrackBuffersManager::RemoveFrames(const TimeIntervals& aIntervals,
                                   TrackData& aTrackData,
                                   uint32_t aStartIndex)
 {
   TrackBuffer& data = aTrackData.mBuffers.LastElement();
@@ -1779,19 +1780,22 @@ TrackBuffersManager::RemoveFrames(const 
       MSE_DEBUG("NextInsertionIndex got reset.");
     } else if (aTrackData.mNextInsertionIndex.ref() > lastRemovedIndex + 1) {
       aTrackData.mNextInsertionIndex.ref() -=
         lastRemovedIndex - firstRemovedIndex.ref() + 1;
     }
   }
 
   // Update our buffered range to exclude the range just removed.
-  removedIntervals.SetFuzz(TimeUnit::FromMicroseconds(maxSampleDuration/2));
   aTrackData.mBufferedRanges -= removedIntervals;
 
+  // Recalculate sanitized buffered ranges.
+  aTrackData.mSanitizedBufferedRanges = aTrackData.mBufferedRanges;
+  aTrackData.mSanitizedBufferedRanges.SetFuzz(TimeUnit::FromMicroseconds(maxSampleDuration/2));
+
   data.RemoveElementsAt(firstRemovedIndex.ref(),
                         lastRemovedIndex - firstRemovedIndex.ref() + 1);
 }
 
 void
 TrackBuffersManager::RecreateParser(bool aReuseInitData)
 {
   MOZ_ASSERT(OnTaskQueue());
--- a/dom/media/mediasource/TrackBuffersManager.h
+++ b/dom/media/mediasource/TrackBuffersManager.h
@@ -252,16 +252,19 @@ private:
     Maybe<size_t> mNextInsertionIndex;
     // Samples just demuxed, but not yet parsed.
     TrackBuffer mQueuedSamples;
     // We only manage a single track of each type at this time.
     nsTArray<TrackBuffer> mBuffers;
     // Track buffer ranges variable that represents the presentation time ranges
     // occupied by the coded frames currently stored in the track buffer.
     media::TimeIntervals mBufferedRanges;
+    // Sanitized mBufferedRanges with a fuzz of half a sample's duration applied
+    // This buffered ranges is the basis of what is exposed to the JS.
+    media::TimeIntervals mSanitizedBufferedRanges;
     // Byte size of all samples contained in this track buffer.
     uint32_t mSizeBuffer;
     // TrackInfo of the first metadata received.
     RefPtr<SharedTrackInfo> mInfo;
     // TrackInfo of the last metadata parsed (updated with each init segment.
     RefPtr<SharedTrackInfo> mLastInfo;
 
     // If set, position of the next sample to be retrieved by GetSample().
--- a/dom/media/test/crashtests/offline-buffer-source-ended-1.html
+++ b/dom/media/test/crashtests/offline-buffer-source-ended-1.html
@@ -1,15 +1,12 @@
 <!DOCTYPE html>
 <html class="reftest-wait">
 <script>
-// Currently this test fails unless an extra block is processed after the
-// BufferSource finishes. 12033 ≡ 1 (mod 128), but the test should pass even
-// when only 12001 samples are processed.
-var context = new window.OfflineAudioContext(1, 12033, 12000);
+var context = new window.OfflineAudioContext(1, 12001, 12000);
 
 var source = context.createBufferSource();
 source.buffer = context.createBuffer(1, 12000, context.sampleRate);
 source.onended = function(e) {
   document.documentElement.removeAttribute("class");
 }
 source.connect(context.destination);
 source.start(0);
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -1271,16 +1271,23 @@ PeerConnectionWrapper.prototype = {
         resolveEndOfTrickle(this.label);
         return;
       }
 
       info(this.label + ": iceCandidate = " + JSON.stringify(anEvent.candidate));
       ok(anEvent.candidate.candidate.length > 0, "ICE candidate contains candidate");
       ok(anEvent.candidate.sdpMid.length > 0, "SDP mid not empty");
 
+      // only check the m-section for the updated default addr that corresponds
+      // with this candidate.
+      var mSections = this.localDescription.sdp.split("\r\nm=");
+      sdputils.checkSdpCLineNotDefault(
+        mSections[anEvent.candidate.sdpMLineIndex+1], this.label
+      );
+
       ok(typeof anEvent.candidate.sdpMLineIndex === 'number', "SDP MLine Index needs to exist");
       this._local_ice_candidates.push(anEvent.candidate);
       candidateHandler(this.label, anEvent.candidate);
     };
   },
 
   checkLocalMediaTracks : function() {
     var observed = {};
--- a/dom/media/tests/mochitest/sdpUtils.js
+++ b/dom/media/tests/mochitest/sdpUtils.js
@@ -3,25 +3,32 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 var sdputils = {
 
 checkSdpAfterEndOfTrickle: function(sdp, testOptions, label) {
   info("EOC-SDP: " + JSON.stringify(sdp));
 
   ok(sdp.sdp.includes("a=end-of-candidates"), label + ": SDP contains end-of-candidates");
-  ok(!sdp.sdp.includes("c=IN IP4 0.0.0.0"), label + ": SDP contains non-zero IP c line");
+  sdputils.checkSdpCLineNotDefault(sdp.sdp, label);
 
   if (testOptions.rtcpmux) {
     ok(sdp.sdp.includes("a=rtcp-mux"), label + ": SDP contains rtcp-mux");
   } else {
     ok(sdp.sdp.includes("a=rtcp:"), label + ": SDP contains rtcp port");
   }
 },
 
+// takes sdp in string form (or possibly a fragment, say an m-section), and
+// verifies that the default 0.0.0.0 addr is not present.
+checkSdpCLineNotDefault: function(sdpStr, label) {
+  info("CLINE-NO-DEFAULT-ADDR-SDP: " + JSON.stringify(sdpStr));
+  ok(!sdpStr.includes("c=IN IP4 0.0.0.0"), label + ": SDP contains non-zero IP c line");
+},
+
 // Also remove mode 0 if it's offered
 // Note, we don't bother removing the fmtp lines, which makes a good test
 // for some SDP parsing issues.
 removeVP8: function(sdp) {
   var updated_sdp = sdp.replace("a=rtpmap:120 VP8/90000\r\n","");
   updated_sdp = updated_sdp.replace("RTP/SAVPF 120 126 97\r\n","RTP/SAVPF 126 97\r\n");
   updated_sdp = updated_sdp.replace("RTP/SAVPF 120 126\r\n","RTP/SAVPF 126\r\n");
   updated_sdp = updated_sdp.replace("a=rtcp-fb:120 nack\r\n","");
--- a/dom/media/webaudio/AnalyserNode.cpp
+++ b/dom/media/webaudio/AnalyserNode.cpp
@@ -64,46 +64,46 @@ public:
                             AudioBlock* aOutput,
                             bool* aFinished) override
   {
     *aOutput = aInput;
 
     if (aInput.IsNull()) {
       // If AnalyserNode::mChunks has only null chunks, then there is no need
       // to send further null chunks.
-      if (mChunksToProcess <= 0) {
-        if (mChunksToProcess != INT32_MIN) {
-          mChunksToProcess = INT32_MIN;
-          aStream->CheckForInactive();
-        }
+      if (mChunksToProcess == 0) {
         return;
       }
 
       --mChunksToProcess;
+      if (mChunksToProcess == 0) {
+        aStream->ScheduleCheckForInactive();
+      }
+
     } else {
       // This many null chunks will be required to empty AnalyserNode::mChunks.
       mChunksToProcess = CHUNK_COUNT;
     }
 
     RefPtr<TransferBuffer> transfer =
       new TransferBuffer(aStream, aInput.AsAudioChunk());
     NS_DispatchToMainThread(transfer);
   }
 
   virtual bool IsActive() const override
   {
-    return mChunksToProcess != INT32_MIN;
+    return mChunksToProcess != 0;
   }
 
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
   {
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
 
-  int32_t mChunksToProcess = INT32_MIN;
+  uint32_t mChunksToProcess = 0;
 };
 
 AnalyserNode::AnalyserNode(AudioContext* aContext)
   : AudioNode(aContext,
               1,
               ChannelCountMode::Max,
               ChannelInterpretation::Speakers)
   , mAnalysisBlock(2048)
--- a/dom/media/webaudio/AudioBufferSourceNode.cpp
+++ b/dom/media/webaudio/AudioBufferSourceNode.cpp
@@ -468,25 +468,16 @@ public:
   {
     if (mBufferSampleRate == 0) {
       // start() has not yet been called or no buffer has yet been set
       aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
       return;
     }
 
     StreamTime streamPosition = mDestination->GraphTimeToStreamTime(aFrom);
-    // We've finished if we've gone past mStop, or if we're past mDuration when
-    // looping is disabled.
-    if (streamPosition >= mStop ||
-        (!mLoop && mBufferPosition >= mBufferEnd && !mRemainingResamplerTail)) {
-      *aFinished = true;
-      aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
-      return;
-    }
-
     uint32_t channels = mBuffer ? mBuffer->GetChannels() : 0;
 
     UpdateSampleRateIfNeeded(channels, streamPosition);
 
     uint32_t written = 0;
     while (written < WEBAUDIO_BLOCK_SIZE) {
       if (mStop != STREAM_TIME_MAX &&
           streamPosition >= mStop) {
@@ -509,16 +500,23 @@ public:
       } else {
         if (mBufferPosition < mBufferEnd || mRemainingResamplerTail) {
           CopyFromBuffer(aOutput, channels, &written, &streamPosition, mBufferEnd);
         } else {
           FillWithZeroes(aOutput, channels, &written, &streamPosition, STREAM_TIME_MAX);
         }
       }
     }
+
+    // We've finished if we've gone past mStop, or if we're past mDuration when
+    // looping is disabled.
+    if (streamPosition >= mStop ||
+        (!mLoop && mBufferPosition >= mBufferEnd && !mRemainingResamplerTail)) {
+      *aFinished = true;
+    }
   }
 
   virtual bool IsActive() const override
   {
     // Whether buffer has been set and start() has been called.
     return mBufferSampleRate != 0;
   }
 
--- a/dom/media/webaudio/AudioNodeExternalInputStream.cpp
+++ b/dom/media/webaudio/AudioNodeExternalInputStream.cpp
@@ -28,18 +28,18 @@ AudioNodeExternalInputStream::Create(Med
                                      AudioNodeEngine* aEngine)
 {
   AudioContext* ctx = aEngine->NodeMainThread()->Context();
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aGraph->GraphRate() == ctx->SampleRate());
 
   RefPtr<AudioNodeExternalInputStream> stream =
     new AudioNodeExternalInputStream(aEngine, aGraph->GraphRate());
-  aGraph->AddStream(stream,
-    ctx->ShouldSuspendNewStream() ? MediaStreamGraph::ADD_STREAM_SUSPENDED : 0);
+  stream->mSuspendedCount += ctx->ShouldSuspendNewStream();
+  aGraph->AddStream(stream);
   return stream.forget();
 }
 
 /**
  * Copies the data in aInput to aOffsetInBlock within aBlock.
  * aBlock must have been allocated with AllocateInputBlock and have a channel
  * count that's a superset of the channels in aInput.
  */
--- a/dom/media/webaudio/AudioNodeStream.cpp
+++ b/dom/media/webaudio/AudioNodeStream.cpp
@@ -35,16 +35,17 @@ AudioNodeStream::AudioNodeStream(AudioNo
     mFlags(aFlags),
     mNumberOfInputChannels(2),
     mIsActive(aEngine->IsActive()),
     mMarkAsFinishedAfterThisBlock(false),
     mAudioParamStream(false),
     mPassThrough(false)
 {
   MOZ_ASSERT(NS_IsMainThread());
+  mSuspendedCount = !(mIsActive || mFlags & EXTERNAL_OUTPUT);
   mChannelCountMode = ChannelCountMode::Max;
   mChannelInterpretation = ChannelInterpretation::Speakers;
   // AudioNodes are always producing data
   mHasCurrentData = true;
   mLastChunks.SetLength(std::max(uint16_t(1), mEngine->OutputCount()));
   MOZ_COUNT_CTOR(AudioNodeStream);
 }
 
@@ -72,23 +73,23 @@ AudioNodeStream::Create(AudioContext* aC
 
   // MediaRecorders use an AudioNodeStream, but no AudioNode
   AudioNode* node = aEngine->NodeMainThread();
   MediaStreamGraph* graph = aGraph ? aGraph : aCtx->Graph();
   MOZ_ASSERT(graph->GraphRate() == aCtx->SampleRate());
 
   RefPtr<AudioNodeStream> stream =
     new AudioNodeStream(aEngine, aFlags, graph->GraphRate());
+  stream->mSuspendedCount += aCtx->ShouldSuspendNewStream();
   if (node) {
     stream->SetChannelMixingParametersImpl(node->ChannelCount(),
                                            node->ChannelCountModeValue(),
                                            node->ChannelInterpretationValue());
   }
-  graph->AddStream(stream,
-    aCtx->ShouldSuspendNewStream() ? MediaStreamGraph::ADD_STREAM_SUSPENDED : 0);
+  graph->AddStream(stream);
   return stream.forget();
 }
 
 size_t
 AudioNodeStream::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
 {
   size_t amount = 0;
 
@@ -571,17 +572,19 @@ AudioNodeStream::ProcessInput(GraphTime 
       }
     }
     for (uint16_t i = 0; i < outputCount; ++i) {
       NS_ASSERTION(mLastChunks[i].GetDuration() == WEBAUDIO_BLOCK_SIZE,
                    "Invalid WebAudio chunk size");
     }
     if (finished) {
       mMarkAsFinishedAfterThisBlock = true;
-      CheckForInactive();
+      if (mIsActive) {
+        ScheduleCheckForInactive();
+      }
     }
 
     if (mDisabledTrackIDs.Contains(static_cast<TrackID>(AUDIO_TRACK))) {
       for (uint32_t i = 0; i < outputCount; ++i) {
         mLastChunks[i].SetNull(WEBAUDIO_BLOCK_SIZE);
       }
     }
   }
@@ -688,44 +691,73 @@ AudioNodeStream::RemoveInput(MediaInputP
 void
 AudioNodeStream::SetActive()
 {
   if (mIsActive || mMarkAsFinishedAfterThisBlock) {
     return;
   }
 
   mIsActive = true;
+  if (!(mFlags & EXTERNAL_OUTPUT)) {
+    GraphImpl()->DecrementSuspendCount(this);
+  }
   if (IsAudioParamStream()) {
     // Consumers merely influence stream order.
     // They do not read from the stream.
     return;
   }
 
   for (const auto& consumer : mConsumers) {
     AudioNodeStream* ns = consumer->GetDestination()->AsAudioNodeStream();
     if (ns) {
       ns->IncrementActiveInputCount();
     }
   }
 }
 
+class AudioNodeStream::CheckForInactiveMessage final : public ControlMessage
+{
+public:
+  explicit CheckForInactiveMessage(AudioNodeStream* aStream) :
+    ControlMessage(aStream) {}
+  virtual void Run() override
+  {
+    auto ns = static_cast<AudioNodeStream*>(mStream);
+    ns->CheckForInactive();
+  }
+};
+
+void
+AudioNodeStream::ScheduleCheckForInactive()
+{
+  if (mActiveInputCount > 0 && !mMarkAsFinishedAfterThisBlock) {
+    return;
+  }
+
+  nsAutoPtr<CheckForInactiveMessage> message(new CheckForInactiveMessage(this));
+  GraphImpl()->RunMessageAfterProcessing(Move(message));
+}
+
 void
 AudioNodeStream::CheckForInactive()
 {
   if (((mActiveInputCount > 0 || mEngine->IsActive()) &&
        !mMarkAsFinishedAfterThisBlock) ||
       !mIsActive) {
     return;
   }
 
   mIsActive = false;
   mInputChunks.Clear(); // not required for foreseeable future
   for (auto& chunk : mLastChunks) {
     chunk.SetNull(WEBAUDIO_BLOCK_SIZE);
   }
+  if (!(mFlags & EXTERNAL_OUTPUT)) {
+    GraphImpl()->IncrementSuspendCount(this);
+  }
   if (IsAudioParamStream()) {
     return;
   }
 
   for (const auto& consumer : mConsumers) {
     AudioNodeStream* ns = consumer->GetDestination()->AsAudioNodeStream();
     if (ns) {
       ns->DecrementActiveInputCount();
--- a/dom/media/webaudio/AudioNodeStream.h
+++ b/dom/media/webaudio/AudioNodeStream.h
@@ -160,29 +160,37 @@ public:
    * SetActive() is called when either an active input is added or the engine
    * for a source node transitions from inactive to active.  This is not
    * called from engines for processing nodes because they only become active
    * when there are active input streams, in which case this stream is already
    * active.
    */
   void SetActive();
   /*
+   * ScheduleCheckForInactive() is called during stream processing when the
+   * engine transitions from active to inactive, or the stream finishes.  It
+   * schedules a call to CheckForInactive() after stream processing.
+   */
+  void ScheduleCheckForInactive();
+
+protected:
+  class AdvanceAndResumeMessage;
+  class CheckForInactiveMessage;
+
+  virtual void DestroyImpl() override;
+
+  /*
    * CheckForInactive() is called when the engine transitions from active to
    * inactive, or an active input is removed, or the stream finishes.  If the
    * stream is now inactive, then mInputChunks will be cleared and mLastChunks
    * will be set to null.  ProcessBlock() will not be called on the engine
    * again until SetActive() is called.
    */
   void CheckForInactive();
 
-protected:
-  class AdvanceAndResumeMessage;
-
-  virtual void DestroyImpl() override;
-
   void AdvanceOutputSegment();
   void FinishOutput();
   void AccumulateInputChunk(uint32_t aInputIndex, const AudioBlock& aChunk,
                             AudioBlock* aBlock,
                             nsTArray<float>* aDownmixBuffer);
   void UpMixDownMixChunk(const AudioBlock* aChunk, uint32_t aOutputChannelCount,
                          nsTArray<const float*>& aOutputChannels,
                          nsTArray<float>& aDownmixBuffer);
--- a/dom/media/webaudio/BiquadFilterNode.cpp
+++ b/dom/media/webaudio/BiquadFilterNode.cpp
@@ -144,17 +144,17 @@ public:
         if (mBiquads[i].hasTail()) {
           hasTail = true;
           break;
         }
       }
       if (!hasTail) {
         if (!mBiquads.IsEmpty()) {
           mBiquads.Clear();
-          aStream->CheckForInactive();
+          aStream->ScheduleCheckForInactive();
 
           RefPtr<PlayingRefChangeHandler> refchanged =
             new PlayingRefChangeHandler(aStream, PlayingRefChangeHandler::RELEASE);
           aStream->Graph()->
             DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget());
         }
 
         aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
--- a/dom/media/webaudio/ChannelMergerNode.cpp
+++ b/dom/media/webaudio/ChannelMergerNode.cpp
@@ -26,55 +26,51 @@ public:
   virtual void ProcessBlocksOnPorts(AudioNodeStream* aStream,
                                     const OutputChunks& aInput,
                                     OutputChunks& aOutput,
                                     bool* aFinished) override
   {
     MOZ_ASSERT(aInput.Length() >= 1, "Should have one or more input ports");
 
     // Get the number of output channels, and allocate it
-    size_t channelCount = 0;
-    for (uint16_t i = 0; i < InputCount(); ++i) {
-      channelCount += aInput[i].ChannelCount();
+    size_t channelCount = InputCount();
+    bool allNull = true;
+    for (size_t i = 0; i < channelCount; ++i) {
+      allNull &= aInput[i].IsNull();
     }
-    if (channelCount == 0) {
+    if (allNull) {
       aOutput[0].SetNull(WEBAUDIO_BLOCK_SIZE);
       return;
     }
-    channelCount = std::min(channelCount, WebAudioUtils::MaxChannelCount);
+
     aOutput[0].AllocateChannels(channelCount);
 
-    // Append each channel in each input to the output
-    size_t channelIndex = 0;
-    for (uint16_t i = 0; true; ++i) {
-      MOZ_ASSERT(i < InputCount());
-      for (size_t j = 0; j < aInput[i].ChannelCount(); ++j) {
+    for (size_t i = 0; i < channelCount; ++i) {
+      float* output = aOutput[0].ChannelFloatsForWrite(i);
+      if (aInput[i].IsNull()) {
+        PodZero(output, WEBAUDIO_BLOCK_SIZE);
+      } else {
         AudioBlockCopyChannelWithScale(
-            static_cast<const float*>(aInput[i].mChannelData[j]),
-            aInput[i].mVolume,
-            aOutput[0].ChannelFloatsForWrite(channelIndex));
-        ++channelIndex;
-        if (channelIndex >= channelCount) {
-          return;
-        }
+          static_cast<const float*>(aInput[i].mChannelData[0]),
+          aInput[i].mVolume, output);
       }
     }
   }
 
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
   {
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
 };
 
 ChannelMergerNode::ChannelMergerNode(AudioContext* aContext,
                                      uint16_t aInputCount)
   : AudioNode(aContext,
-              2,
-              ChannelCountMode::Max,
+              1,
+              ChannelCountMode::Explicit,
               ChannelInterpretation::Speakers)
   , mInputCount(aInputCount)
 {
   mStream = AudioNodeStream::Create(aContext,
                                     new ChannelMergerNodeEngine(this),
                                     AudioNodeStream::NO_STREAM_FLAGS);
 }
 
--- a/dom/media/webaudio/ConvolverNode.cpp
+++ b/dom/media/webaudio/ConvolverNode.cpp
@@ -116,17 +116,17 @@ public:
     if (aInput.IsNull()) {
       if (mLeftOverData > 0) {
         mLeftOverData -= WEBAUDIO_BLOCK_SIZE;
         input.AllocateChannels(1);
         WriteZeroesToAudioBlock(&input, 0, WEBAUDIO_BLOCK_SIZE);
       } else {
         if (mLeftOverData != INT32_MIN) {
           mLeftOverData = INT32_MIN;
-          aStream->CheckForInactive();
+          aStream->ScheduleCheckForInactive();
           RefPtr<PlayingRefChanged> refchanged =
             new PlayingRefChanged(aStream, PlayingRefChanged::RELEASE);
           aStream->Graph()->
             DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget());
         }
         aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
         return;
       }
--- a/dom/media/webaudio/DelayNode.cpp
+++ b/dom/media/webaudio/DelayNode.cpp
@@ -86,17 +86,17 @@ public:
           DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget());
       }
       mLeftOverData = mBuffer.MaxDelayTicks();
     } else if (mLeftOverData > 0) {
       mLeftOverData -= WEBAUDIO_BLOCK_SIZE;
     } else {
       if (mLeftOverData != INT32_MIN) {
         mLeftOverData = INT32_MIN;
-        aStream->CheckForInactive();
+        aStream->ScheduleCheckForInactive();
 
         // Delete our buffered data now we no longer need it
         mBuffer.Reset();
 
         RefPtr<PlayingRefChanged> refchanged =
           new PlayingRefChanged(aStream, PlayingRefChanged::RELEASE);
         aStream->Graph()->
           DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget());
--- a/dom/media/webaudio/FFTBlock.h
+++ b/dom/media/webaudio/FFTBlock.h
@@ -74,20 +74,21 @@ public:
   CreateInterpolatedBlock(const FFTBlock& block0,
                           const FFTBlock& block1, double interp);
 
   // Transform FFTSize() points of aData and store the result internally.
   void PerformFFT(const float* aData)
   {
     EnsureFFT();
 #if defined(MOZ_LIBAV_FFT)
-    AlignedTArray<FFTSample> complex(mFFTSize);
-    PodCopy(complex.Elements(), aData, mFFTSize);
-    av_rdft_calc(mAvRDFT, complex.Elements());
-    PodCopy((FFTSample*)mOutputBuffer.Elements(), complex.Elements(), mFFTSize);
+    PodCopy(mOutputBuffer.Elements()->f, aData, mFFTSize);
+    av_rdft_calc(mAvRDFT, mOutputBuffer.Elements()->f);
+    // Recover packed Nyquist.
+    mOutputBuffer[mFFTSize / 2].r = mOutputBuffer[0].i;
+    mOutputBuffer[0].i = 0.0f;
 #else
 #ifdef BUILD_ARM_NEON
     if (mozilla::supports_neon()) {
       omxSP_FFTFwd_RToCCS_F32_Sfs(aData, mOutputBuffer.Elements()->f, mOmxFFT);
     } else
 #endif
     {
       kiss_fftr(mKissFFT, aData, &(mOutputBuffer.Elements()->c));
@@ -104,25 +105,23 @@ public:
   // Inverse-transform internal frequency data and store the resulting
   // FFTSize() points in |aDataOut|.  If frequency data has not already been
   // scaled, then the output will need scaling by 1/FFTSize().
   void GetInverseWithoutScaling(float* aDataOut)
   {
     EnsureIFFT();
 #if defined(MOZ_LIBAV_FFT)
     {
-      PodCopy(aDataOut, (float*)mOutputBuffer.Elements(), mFFTSize);
-      av_rdft_calc(mAvIRDFT, aDataOut);
-      // TODO: Once bug 877662 lands, change this to use SSE.
       // Even though this function doesn't scale, the libav forward transform
       // gives a value that needs scaling by 2 in order for things to turn out
       // similar to how we expect from kissfft/openmax.
-      for (uint32_t i = 0; i < mFFTSize; ++i) {
-        aDataOut[i] *= 2.0;
-      }
+      AudioBufferCopyWithScale(mOutputBuffer.Elements()->f, 2.0f,
+                               aDataOut, mFFTSize);
+      aDataOut[1] = 2.0f * mOutputBuffer[mFFTSize/2].r; // Packed Nyquist
+      av_rdft_calc(mAvIRDFT, aDataOut);
     }
 #else
 #ifdef BUILD_ARM_NEON
     if (mozilla::supports_neon()) {
       omxSP_FFTInv_CCSToR_F32_Sfs(mOutputBuffer.Elements()->f, aDataOut, mOmxIFFT);
       // There is no function that computes de inverse FFT without scaling, so
       // we have to scale back up here. Bug 1158741.
       AudioBufferInPlaceScale(aDataOut, mFFTSize, mFFTSize);
@@ -131,20 +130,27 @@ public:
     {
       kiss_fftri(mKissIFFT, &(mOutputBuffer.Elements()->c), aDataOut);
     }
 #endif
   }
 
   void Multiply(const FFTBlock& aFrame)
   {
+    uint32_t halfSize = mFFTSize / 2;
+    // DFTs are not packed.
+    MOZ_ASSERT(mOutputBuffer[0].i == 0);
+    MOZ_ASSERT(mOutputBuffer[halfSize].i == 0);
+    MOZ_ASSERT(aFrame.mOutputBuffer[0].i == 0);
+    MOZ_ASSERT(aFrame.mOutputBuffer[halfSize].i == 0);
+
     BufferComplexMultiply(mOutputBuffer.Elements()->f,
                           aFrame.mOutputBuffer.Elements()->f,
                           mOutputBuffer.Elements()->f,
-                          mFFTSize / 2 + 1);
+                          halfSize + 1);
   }
 
   // Perform a forward FFT on |aData|, assuming zeros after dataSize samples,
   // and pre-scale the generated internal frequency domain coefficients so
   // that GetInverseWithoutScaling() can be used to transform to the time
   // domain.  This is useful for convolution kernels.
   void PadAndMakeScaledDFT(const float* aData, size_t dataSize)
   {
--- a/dom/media/webaudio/OscillatorNode.cpp
+++ b/dom/media/webaudio/OscillatorNode.cpp
@@ -309,49 +309,46 @@ public:
     MOZ_ASSERT(mSource == aStream, "Invalid source stream");
 
     StreamTime ticks = mDestination->GraphTimeToStreamTime(aFrom);
     if (mStart == -1) {
       ComputeSilence(aOutput);
       return;
     }
 
-    if (ticks >= mStop) {
-      // We've finished playing.
+    if (ticks + WEBAUDIO_BLOCK_SIZE <= mStart || ticks >= mStop) {
       ComputeSilence(aOutput);
-      *aFinished = true;
-      return;
-    }
-    if (ticks + WEBAUDIO_BLOCK_SIZE <= mStart) {
-      // We're not playing yet.
-      ComputeSilence(aOutput);
-      return;
+
+    } else {
+      aOutput->AllocateChannels(1);
+      float* output = aOutput->ChannelFloatsForWrite(0);
+
+      uint32_t start, end;
+      FillBounds(output, ticks, start, end);
+
+      // Synthesize the correct waveform.
+      switch(mType) {
+        case OscillatorType::Sine:
+          ComputeSine(output, ticks, start, end);
+          break;
+        case OscillatorType::Square:
+        case OscillatorType::Triangle:
+        case OscillatorType::Sawtooth:
+        case OscillatorType::Custom:
+          ComputeCustom(output, ticks, start, end);
+          break;
+        default:
+          ComputeSilence(aOutput);
+      };
     }
 
-    aOutput->AllocateChannels(1);
-    float* output = aOutput->ChannelFloatsForWrite(0);
-
-    uint32_t start, end;
-    FillBounds(output, ticks, start, end);
-
-    // Synthesize the correct waveform.
-    switch(mType) {
-      case OscillatorType::Sine:
-        ComputeSine(output, ticks, start, end);
-        break;
-      case OscillatorType::Square:
-      case OscillatorType::Triangle:
-      case OscillatorType::Sawtooth:
-      case OscillatorType::Custom:
-        ComputeCustom(output, ticks, start, end);
-        break;
-      default:
-        ComputeSilence(aOutput);
-    };
-
+    if (ticks + WEBAUDIO_BLOCK_SIZE >= mStop) {
+      // We've finished playing.
+      *aFinished = true;
+    }
   }
 
   virtual bool IsActive() const override
   {
     // start() has been called.
     return mStart != -1;
   }
 
--- a/dom/media/webaudio/PannerNode.cpp
+++ b/dom/media/webaudio/PannerNode.cpp
@@ -145,17 +145,17 @@ public:
       // tail-time reference was added.  Even if the model is now equalpower,
       // the reference will need to be removed.
       if (mLeftOverData > 0 &&
           mPanningModelFunction == &PannerNodeEngine::HRTFPanningFunction) {
         mLeftOverData -= WEBAUDIO_BLOCK_SIZE;
       } else {
         if (mLeftOverData != INT_MIN) {
           mLeftOverData = INT_MIN;
-          aStream->CheckForInactive();
+          aStream->ScheduleCheckForInactive();
           mHRTFPanner->reset();
 
           RefPtr<PlayingRefChangeHandler> refchanged =
             new PlayingRefChangeHandler(aStream, PlayingRefChangeHandler::RELEASE);
           aStream->Graph()->
             DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget());
         }
         aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
--- a/dom/media/webaudio/StereoPannerNode.cpp
+++ b/dom/media/webaudio/StereoPannerNode.cpp
@@ -86,25 +86,26 @@ public:
         samples[i] = 0.f;
       }
     }
   }
 
   void UpmixToStereoIfNeeded(const AudioBlock& aInput, AudioBlock* aOutput)
   {
     if (aInput.ChannelCount() == 2) {
-      *aOutput = aInput;
+      const float* inputL = static_cast<const float*>(aInput.mChannelData[0]);
+      const float* inputR = static_cast<const float*>(aInput.mChannelData[1]);
+      float* outputL = aOutput->ChannelFloatsForWrite(0);
+      float* outputR = aOutput->ChannelFloatsForWrite(1);
+
+      AudioBlockCopyChannelWithScale(inputL, aInput.mVolume, outputL);
+      AudioBlockCopyChannelWithScale(inputR, aInput.mVolume, outputR);
     } else {
       MOZ_ASSERT(aInput.ChannelCount() == 1);
-      aOutput->AllocateChannels(2);
-      const float* input = static_cast<const float*>(aInput.mChannelData[0]);
-      for (uint32_t channel = 0; channel < 2; channel++) {
-        float* output = aOutput->ChannelFloatsForWrite(channel);
-        PodCopy(output, input, WEBAUDIO_BLOCK_SIZE);
-      }
+      GainMonoToStereo(aInput, aOutput, aInput.mVolume, aInput.mVolume);
     }
   }
 
   virtual void ProcessBlock(AudioNodeStream* aStream,
                             GraphTime aFrom,
                             const AudioBlock& aInput,
                             AudioBlock* aOutput,
                             bool *aFinished) override
@@ -115,17 +116,17 @@ public:
     bool monoToStereo = aInput.ChannelCount() == 1;
 
     if (aInput.IsNull()) {
       // If input is silent, so is the output
       SetToSilentStereoBlock(aOutput);
     } else if (mPan.HasSimpleValue()) {
       float panning = mPan.GetValue();
       // If the panning is 0.0, we can simply copy the input to the
-      // output, up-mixing to stereo if needed.
+      // output with gain applied, up-mixing to stereo if needed.
       if (panning == 0.0f) {
         UpmixToStereoIfNeeded(aInput, aOutput);
       } else {
         // Optimize the case where the panning is constant for this processing
         // block: we can just apply a constant gain on the left and right
         // channel
         float gainL, gainR;
 
--- a/dom/media/webaudio/test/mochitest.ini
+++ b/dom/media/webaudio/test/mochitest.ini
@@ -91,16 +91,17 @@ skip-if = toolkit == 'android' # bug 105
 [test_channelMergerNode.html]
 [test_channelMergerNodeWithVolume.html]
 [test_channelSplitterNode.html]
 [test_channelSplitterNodeWithVolume.html]
 skip-if = (android_version == '18' && debug) # bug 1158417
 [test_convolverNode.html]
 [test_convolverNode_mono_mono.html]
 [test_convolverNodeChannelCount.html]
+[test_convolverNodeDelay.html]
 [test_convolverNodePassThrough.html]
 [test_convolverNodeWithGain.html]
 [test_currentTime.html]
 [test_decodeMultichannel.html]
 [test_decodeAudioDataPromise.html]
 [test_delayNode.html]
 [test_delayNodeAtMax.html]
 [test_delayNodeChannelChanges.html]
--- a/dom/media/webaudio/test/test_channelMergerNode.html
+++ b/dom/media/webaudio/test/test_channelMergerNode.html
@@ -7,47 +7,47 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 var gTest = {
   length: 2048,
-  numberOfChannels: 8,
+  numberOfChannels: 6,
   createGraph: function(context) {
     var buffers = [];
-    for (var j = 0; j < 4; ++j) {
+    for (var j = 0; j < 6; ++j) {
       var buffer = context.createBuffer(2, 2048, context.sampleRate);
       for (var i = 0; i < 2048; ++i) {
         buffer.getChannelData(0)[i] = Math.sin(440 * 2 * (j + 1) * Math.PI * i / context.sampleRate);
         // Second channel is silent
       }
       buffers.push(buffer);
     }
-    var emptyBuffer = context.createBuffer(1, 2048, context.sampleRate);
 
     var merger = context.createChannelMerger();
-    is(merger.channelCount, 2, "merger node has 2 input channels by default");
-    is(merger.channelCountMode, "max", "Correct channelCountMode for the merger node");
+    is(merger.channelCount, 1, "merger node has 1 input channels");
+    is(merger.channelCountMode, "explicit", "Correct channelCountMode for the merger node");
     is(merger.channelInterpretation, "speakers", "Correct channelCountInterpretation for the merger node");
 
-    for (var i = 0; i < 4; ++i) {
+    for (var i = 0; i < 6; ++i) {
       var source = context.createBufferSource();
       source.buffer = buffers[i];
       source.connect(merger, 0, i);
       source.start(0);
     }
+
     return merger;
   },
   createExpectedBuffers: function(context) {
-    var expectedBuffer = context.createBuffer(8, 2048, context.sampleRate);
-    for (var i = 0; i < 4; ++i) {
+    var expectedBuffer = context.createBuffer(6, 2048, context.sampleRate);
+    for (var i = 0; i < 6; ++i) {
       for (var j = 0; j < 2048; ++j) {
-        expectedBuffer.getChannelData(i * 2)[j] = Math.sin(440 * 2 * (i + 1) * Math.PI * j / context.sampleRate);
+        expectedBuffer.getChannelData(i)[j] = 0.5 * Math.sin(440 * 2 * (i + 1) * Math.PI * j / context.sampleRate);
       }
     }
     return expectedBuffer;
   },
 };
 
 runTest();
 
--- a/dom/media/webaudio/test/test_channelMergerNodeWithVolume.html
+++ b/dom/media/webaudio/test/test_channelMergerNodeWithVolume.html
@@ -7,50 +7,50 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 var gTest = {
   length: 2048,
-  numberOfChannels: 8,
+  numberOfChannels: 6,
   createGraph: function(context) {
     var buffers = [];
-    for (var j = 0; j < 4; ++j) {
+    for (var j = 0; j < 6; ++j) {
       var buffer = context.createBuffer(2, 2048, context.sampleRate);
       for (var i = 0; i < 2048; ++i) {
         buffer.getChannelData(0)[i] = Math.sin(440 * 2 * (j + 1) * Math.PI * i / context.sampleRate);
         // Second channel is silent
       }
       buffers.push(buffer);
     }
 
     var merger = context.createChannelMerger();
-    is(merger.channelCount, 2, "merger node has 2 input channels by default");
-    is(merger.channelCountMode, "max", "Correct channelCountMode for the merger node");
+    is(merger.channelCount, 1, "merger node has 1 input channels");
+    is(merger.channelCountMode, "explicit", "Correct channelCountMode for the merger node");
     is(merger.channelInterpretation, "speakers", "Correct channelCountInterpretation for the merger node");
 
-    for (var i = 0; i < 4; ++i) {
+    for (var i = 0; i < 6; ++i) {
       var source = context.createBufferSource();
       source.buffer = buffers[i];
       var gain = context.createGain();
       gain.gain.value = 0.5;
       source.connect(gain);
       gain.connect(merger, 0, i);
       source.start(0);
     }
 
     return merger;
   },
   createExpectedBuffers: function(context) {
-    var expectedBuffer = context.createBuffer(8, 2048, context.sampleRate);
-    for (var i = 0; i < 4; ++i) {
+    var expectedBuffer = context.createBuffer(6, 2048, context.sampleRate);
+    for (var i = 0; i < 6; ++i) {
       for (var j = 0; j < 2048; ++j) {
-        expectedBuffer.getChannelData(i * 2)[j] = Math.sin(440 * 2 * (i + 1) * Math.PI * j / context.sampleRate) / 2;
+        expectedBuffer.getChannelData(i)[j] = 0.5 * 0.5 * Math.sin(440 * 2 * (i + 1) * Math.PI * j / context.sampleRate);
       }
     }
     return expectedBuffer;
   },
 };
 
 runTest();
 
new file mode 100644
--- /dev/null
+++ b/dom/media/webaudio/test/test_convolverNodeDelay.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<title>Test convolution to delay a triangle pulse</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+const sampleRate = 48000;
+const LENGTH = 12800;
+// tolerate 16-bit math.
+const EPSILON = 1.0 / Math.pow(2, 15);
+
+// Triangle pulse
+var sourceBuffer = new OfflineAudioContext(1, 1, sampleRate).
+  createBuffer(1, 2 * 128, sampleRate);
+var channelData = sourceBuffer.getChannelData(0);
+for (var i = 0; i < 128; ++i) {
+  channelData[i] = i/128;
+  channelData[128 + i] = 1.0 - i/128;
+}
+
+function test_delay_index(delayIndex) {
+
+  var context = new OfflineAudioContext(2, LENGTH, sampleRate);
+
+  var merger = context.createChannelMerger(2);
+  merger.connect(context.destination);
+
+  var impulse = context.createBuffer(1, delayIndex + 1, sampleRate);
+  impulse.getChannelData(0)[delayIndex] = 1.0;
+  var convolver = context.createConvolver();
+  convolver.normalize = false;
+  convolver.buffer = impulse;
+  convolver.connect(merger, 0, 0);
+
+  var delayTime = delayIndex/sampleRate;
+  var delay = context.createDelay(delayTime || 1/sampleRate);
+  delay.delayTime.value = delayTime;
+  delay.connect(merger, 0, 1);
+
+  var source = context.createBufferSource();
+  source.buffer = sourceBuffer;
+  source.connect(convolver);
+  source.connect(delay);
+  source.start(0);
+
+  return context.startRendering().
+    then((buffer) => {
+      var convolverOutput = buffer.getChannelData(0);
+      var delayOutput = buffer.getChannelData(1);
+      var maxDiff = 0.0;
+      var maxIndex = 0;
+      for (var i = 0; i < buffer.length; ++i) {
+        var diff = Math.abs(convolverOutput[i] - delayOutput[i]);
+        if (diff > maxDiff) {
+          maxDiff = diff;
+          maxIndex = i;
+        }
+      }
+      // The convolver should produce similar output to the delay.
+      assert_approx_equals(convolverOutput[maxIndex], delayOutput[maxIndex],
+                           EPSILON, "output at " + maxIndex);
+    });
+}
+
+// The 5/4 ratio provides sampling across a range of delays and offsets within
+// blocks.
+for (var delayIndex = 0;
+     delayIndex < LENGTH;
+     delayIndex = Math.floor((5 * (delayIndex + 1)) / 4)) {
+  promise_test(test_delay_index.bind(null, delayIndex),
+               "Delay " + delayIndex);
+}
+</script>
--- a/dom/media/webaudio/test/test_stereoPannerNode.html
+++ b/dom/media/webaudio/test/test_stereoPannerNode.html
@@ -130,16 +130,36 @@ var tests = [
   },
   function stereoPanningNoop(ctx, panner) {
     var stereoSource = ctx.createBufferSource();
     stereoSource.connect(panner);
     stereoSource.buffer = stereoBuffer;
     stereoSource.start(0);
     return expectedBufferNoop();
   },
+  function monoPanningNoopWithGain(ctx, panner) {
+    var monoSource = ctx.createBufferSource();
+    var gain = ctx.createGain();
+    gain.gain.value = GAIN;
+    monoSource.connect(gain);
+    gain.connect(panner);
+    monoSource.buffer = monoBuffer;
+    monoSource.start(0);
+    return expectedBufferNoop(GAIN);
+  },
+  function stereoPanningNoopWithGain(ctx, panner) {
+    var stereoSource = ctx.createBufferSource();
+    var gain = ctx.createGain();
+    gain.gain.value = GAIN;
+    stereoSource.connect(gain);
+    gain.connect(panner);
+    stereoSource.buffer = stereoBuffer;
+    stereoSource.start(0);
+    return expectedBufferNoop(GAIN);
+  },
   function stereoPanningAutomation(ctx, panner) {
     var stereoSource = ctx.createBufferSource();
     stereoSource.connect(panner);
     stereoSource.buffer = stereoBuffer;
     panner.pan.setValueAtTime(0.1, 0.0);
     stereoSource.start(0);
     return expectedBufferForStereo();
   },
--- a/dom/plugins/ipc/PluginInstanceParent.cpp
+++ b/dom/plugins/ipc/PluginInstanceParent.cpp
@@ -48,16 +48,17 @@
 #include "mozilla/plugins/PluginSurfaceParent.h"
 #include "nsClassHashtable.h"
 #include "nsHashKeys.h"
 #include "nsIWidget.h"
 #include "nsPluginNativeWindow.h"
 #include "PluginQuirks.h"
 extern const wchar_t* kFlashFullscreenClass;
 #elif defined(MOZ_WIDGET_GTK)
+#include "mozilla/dom/ContentChild.h"
 #include <gdk/gdk.h>
 #elif defined(XP_MACOSX)
 #include <ApplicationServices/ApplicationServices.h>
 #endif // defined(XP_MACOSX)
 
 // This is the pref used to determine whether to use Shumway on a Flash object
 // (when Shumway is enabled).
 static const char kShumwayWhitelistPref[] = "shumway.swf.whitelist";
@@ -1229,17 +1230,25 @@ PluginInstanceParent::NPP_HandleEvent(vo
 
     case ButtonPress:
         // Release any active pointer grab so that the plugin X client can
         // grab the pointer if it wishes.
         Display *dpy = DefaultXDisplay();
 #  ifdef MOZ_WIDGET_GTK
         // GDK attempts to (asynchronously) track whether there is an active
         // grab so ungrab through GDK.
-        gdk_pointer_ungrab(npevent->xbutton.time);
+        //
+        // This call needs to occur in the same process that receives the event in
+        // the first place (chrome process)
+        if (XRE_IsContentProcess()) {
+          dom::ContentChild* cp = dom::ContentChild::GetSingleton();
+          cp->SendUngrabPointer(npevent->xbutton.time);
+        } else {
+          gdk_pointer_ungrab(npevent->xbutton.time);
+        }
 #  else
         XUngrabPointer(dpy, npevent->xbutton.time);
 #  endif
         // Wait for the ungrab to complete.
         XSync(dpy, False);
         break;
     }
 #endif
--- a/dom/security/nsContentSecurityManager.cpp
+++ b/dom/security/nsContentSecurityManager.cpp
@@ -5,17 +5,17 @@
 #include "nsContentUtils.h"
 #include "nsCORSListenerProxy.h"
 #include "nsIStreamListener.h"
 
 #include "mozilla/dom/Element.h"
 
 NS_IMPL_ISUPPORTS(nsContentSecurityManager, nsIContentSecurityManager)
 
-nsresult
+static nsresult
 ValidateSecurityFlags(nsILoadInfo* aLoadInfo)
 {
   nsSecurityFlags securityMode = aLoadInfo->GetSecurityMode();
 
   if (securityMode != nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS &&
       securityMode != nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED &&
       securityMode != nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS &&
       securityMode != nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
@@ -38,17 +38,17 @@ static bool SchemeIs(nsIURI* aURI, const
 {
   nsCOMPtr<nsIURI> baseURI = NS_GetInnermostURI(aURI);
   NS_ENSURE_TRUE(baseURI, false);
 
   bool isScheme = false;
   return NS_SUCCEEDED(baseURI->SchemeIs(aScheme, &isScheme)) && isScheme;
 }
 
-nsresult
+static nsresult
 DoCheckLoadURIChecks(nsIURI* aURI, nsILoadInfo* aLoadInfo)
 {
   nsresult rv = NS_OK;
 
   nsCOMPtr<nsIPrincipal> loadingPrincipal = aLoadInfo->LoadingPrincipal();
   uint32_t flags = nsIScriptSecurityManager::STANDARD;
   if (aLoadInfo->GetAllowChrome()) {
     flags |= nsIScriptSecurityManager::ALLOW_CHROME;
@@ -68,21 +68,33 @@ DoCheckLoadURIChecks(nsIURI* aURI, nsILo
            CheckLoadURIWithPrincipal(triggeringPrincipal,
                                      aURI,
                                      flags);
     NS_ENSURE_SUCCESS(rv, rv);
   }
   return NS_OK;
 }
 
-nsresult
+static bool
+URIHasFlags(nsIURI* aURI, uint32_t aURIFlags)
+{
+  bool hasFlags;
+  nsresult rv = NS_URIChainHasFlags(aURI, aURIFlags, &hasFlags);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  return hasFlags;
+}
+
+static nsresult
 DoSOPChecks(nsIURI* aURI, nsILoadInfo* aLoadInfo)
 {
-  if (aLoadInfo->GetAllowChrome() && SchemeIs(aURI, "chrome")) {
-    // Enforce same-origin policy, except to chrome.
+  if (aLoadInfo->GetAllowChrome() &&
+      (URIHasFlags(aURI, nsIProtocolHandler::URI_IS_UI_RESOURCE) ||
+       SchemeIs(aURI, "moz-safe-about"))) {
+    // UI resources are allowed.
     return DoCheckLoadURIChecks(aURI, aLoadInfo);
   }
 
   nsIPrincipal* loadingPrincipal = aLoadInfo->LoadingPrincipal();
   bool sameOriginDataInherits =
     aLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS;
 
   if (sameOriginDataInherits &&
@@ -91,17 +103,17 @@ DoSOPChecks(nsIURI* aURI, nsILoadInfo* a
     return NS_OK;
   }
 
   return loadingPrincipal->CheckMayLoad(aURI,
                                         true, // report to console
                                         sameOriginDataInherits);
 }
 
-nsresult
+static nsresult
 DoCORSChecks(nsIChannel* aChannel, nsILoadInfo* aLoadInfo,
              nsCOMPtr<nsIStreamListener>& aInAndOutListener)
 {
   MOZ_RELEASE_ASSERT(aInAndOutListener, "can not perform CORS checks without a listener");
   nsIPrincipal* loadingPrincipal = aLoadInfo->LoadingPrincipal();
   RefPtr<nsCORSListenerProxy> corsListener =
     new nsCORSListenerProxy(aInAndOutListener,
                             loadingPrincipal,
@@ -110,17 +122,17 @@ DoCORSChecks(nsIChannel* aChannel, nsILo
   // lets use  DataURIHandling::Allow for now and then decide on callsite basis. see also:
   // http://mxr.mozilla.org/mozilla-central/source/dom/security/nsCORSListenerProxy.h#33
   nsresult rv = corsListener->Init(aChannel, DataURIHandling::Allow);
   NS_ENSURE_SUCCESS(rv, rv);
   aInAndOutListener = corsListener;
   return NS_OK;
 }
 
-nsresult
+static nsresult
 DoContentSecurityChecks(nsIURI* aURI, nsILoadInfo* aLoadInfo)
 {
   nsContentPolicyType contentPolicyType =
     aLoadInfo->GetExternalContentPolicyType();
   nsContentPolicyType internalContentPolicyType =
     aLoadInfo->InternalContentPolicyType();
   nsCString mimeTypeGuess;
   nsCOMPtr<nsINode> requestingContext = nullptr;
--- a/dom/security/test/cors/test_CrossSiteXHR_origin.html
+++ b/dom/security/test/cors/test_CrossSiteXHR_origin.html
@@ -1,17 +1,17 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
   <title>Test for Cross Site XMLHttpRequest</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>        
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
-<body onload="gen.next()">
+<body>
 <p id="display">
 <iframe id=loader></iframe>
 </p>
 <div id="content" style="display: none">
   
 </div>
 <pre id="test">
 <script class="testbody" type="application/javascript;version=1.8">
@@ -158,12 +158,17 @@ function runTest() {
     }
   }
 
   SimpleTest.finish();
 
   yield undefined;
 }
 
+addLoadEvent(function() {
+  SpecialPowers.pushPrefEnv({"set": [["network.jar.block-remote-files", false]]}, function() {
+    gen.next();
+  });
+});
 </script>
 </pre>
 </body>
 </html>
--- a/dom/svg/DOMSVGLengthList.cpp
+++ b/dom/svg/DOMSVGLengthList.cpp
@@ -365,45 +365,44 @@ DOMSVGLengthList::GetItemAt(uint32_t aIn
   return result.forget();
 }
 
 void
 DOMSVGLengthList::MaybeInsertNullInAnimValListAt(uint32_t aIndex)
 {
   MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal");
 
-  DOMSVGLengthList* animVal = mAList->mAnimVal;
-
-  if (!animVal || mAList->IsAnimating()) {
-    // No animVal list wrapper, or animVal not a clone of baseVal
+  if (!AnimListMirrorsBaseList()) {
     return;
   }
 
+  DOMSVGLengthList* animVal = mAList->mAnimVal;
+
+  MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal");
   MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(),
              "animVal list not in sync!");
-
   MOZ_ALWAYS_TRUE(animVal->mItems.InsertElementAt(aIndex, nullptr, fallible));
 
   UpdateListIndicesFromIndex(animVal->mItems, aIndex + 1);
 }
 
 void
 DOMSVGLengthList::MaybeRemoveItemFromAnimValListAt(uint32_t aIndex)
 {
   MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal");
 
+  if (!AnimListMirrorsBaseList()) {
+    return;
+  }
+
   // This needs to be a strong reference; otherwise, the RemovingFromList call
   // below might drop the last reference to animVal before we're done with it.
   RefPtr<DOMSVGLengthList> animVal = mAList->mAnimVal;
 
-  if (!animVal || mAList->IsAnimating()) {
-    // No animVal list wrapper, or animVal not a clone of baseVal
-    return;
-  }
-
+  MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal");
   MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(),
              "animVal list not in sync!");
 
   if (animVal->mItems[aIndex]) {
     animVal->mItems[aIndex]->RemovingFromList();
   }
   animVal->mItems.RemoveElementAt(aIndex);
 
--- a/dom/svg/DOMSVGLengthList.h
+++ b/dom/svg/DOMSVGLengthList.h
@@ -93,16 +93,22 @@ public:
 
   /**
    * Returns true if our attribute is animating (in which case our animVal is
    * not simply a mirror of our baseVal).
    */
   bool IsAnimating() const {
     return mAList->IsAnimating();
   }
+  /**
+   * Returns true if there is an animated list mirroring the base list.
+   */
+  bool AnimListMirrorsBaseList() const {
+    return mAList->mAnimVal && !mAList->IsAnimating();
+  }
 
   uint32_t NumberOfItems() const
   {
     if (IsAnimValList()) {
       Element()->FlushAnimations();
     }
     return LengthNoFlush();
   }
--- a/dom/svg/DOMSVGNumberList.cpp
+++ b/dom/svg/DOMSVGNumberList.cpp
@@ -344,45 +344,44 @@ DOMSVGNumberList::GetItemAt(uint32_t aIn
   return result.forget();
 }
 
 void
 DOMSVGNumberList::MaybeInsertNullInAnimValListAt(uint32_t aIndex)
 {
   MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal");
 
-  DOMSVGNumberList* animVal = mAList->mAnimVal;
-
-  if (!animVal || mAList->IsAnimating()) {
-    // No animVal list wrapper, or animVal not a clone of baseVal
+  if (!AnimListMirrorsBaseList()) {
     return;
   }
 
+  DOMSVGNumberList* animVal = mAList->mAnimVal;
+
+  MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal");
   MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(),
              "animVal list not in sync!");
-
   MOZ_ALWAYS_TRUE(animVal->mItems.InsertElementAt(aIndex, nullptr, fallible));
 
   UpdateListIndicesFromIndex(animVal->mItems, aIndex + 1);
 }
 
 void
 DOMSVGNumberList::MaybeRemoveItemFromAnimValListAt(uint32_t aIndex)
 {
   MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal");
 
+  if (!AnimListMirrorsBaseList()) {
+    return;
+  }
+
   // This needs to be a strong reference; otherwise, the RemovingFromList call
   // below might drop the last reference to animVal before we're done with it.
   RefPtr<DOMSVGNumberList> animVal = mAList->mAnimVal;
 
-  if (!animVal || mAList->IsAnimating()) {
-    // No animVal list wrapper, or animVal not a clone of baseVal
-    return;
-  }
-
+  MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal");
   MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(),
              "animVal list not in sync!");
 
   if (animVal->mItems[aIndex]) {
     animVal->mItems[aIndex]->RemovingFromList();
   }
   animVal->mItems.RemoveElementAt(aIndex);
 
--- a/dom/svg/DOMSVGNumberList.h
+++ b/dom/svg/DOMSVGNumberList.h
@@ -93,16 +93,22 @@ public:
 
   /**
    * Returns true if our attribute is animating (in which case our animVal is
    * not simply a mirror of our baseVal).
    */
   bool IsAnimating() const {
     return mAList->IsAnimating();
   }
+  /**
+   * Returns true if there is an animated list mirroring the base list.
+   */
+  bool AnimListMirrorsBaseList() const {
+    return mAList->mAnimVal && !mAList->IsAnimating();
+  }
 
   uint32_t NumberOfItems() const
   {
     if (IsAnimValList()) {
       Element()->FlushAnimations();
     }
     return LengthNoFlush();
   }
--- a/dom/svg/DOMSVGPathSegList.cpp
+++ b/dom/svg/DOMSVGPathSegList.cpp
@@ -237,16 +237,23 @@ DOMSVGPathSegList::InternalListWillChang
 }
 
 bool
 DOMSVGPathSegList::AttrIsAnimating() const
 {
   return InternalAList().IsAnimating();
 }
 
+bool
+DOMSVGPathSegList::AnimListMirrorsBaseList() const
+{
+  return GetDOMWrapperIfExists(InternalAList().GetAnimValKey()) &&
+           !AttrIsAnimating();
+}
+ 
 SVGPathData&
 DOMSVGPathSegList::InternalList() const
 {
   SVGAnimatedPathSegList *alist = mElement->GetAnimPathSegList();
   return mIsAnimValList && alist->IsAnimating() ? *alist->mAnimVal : alist->mBaseVal;
 }
 
 SVGAnimatedPathSegList&
@@ -521,61 +528,52 @@ DOMSVGPathSegList::GetItemAt(uint32_t aI
 void
 DOMSVGPathSegList::
   MaybeInsertNullInAnimValListAt(uint32_t aIndex,
                                  uint32_t aInternalIndex,
                                  uint32_t aArgCountForItem)
 {
   MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal");
 
-  if (AttrIsAnimating()) {
-    // animVal not a clone of baseVal
+  if (!AnimListMirrorsBaseList()) {
     return;
   }
 
   // The anim val list is in sync with the base val list
   DOMSVGPathSegList *animVal =
     GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
-  if (!animVal) {
-    // No animVal list wrapper
-    return;
-  }
 
+  MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal");
   MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(),
              "animVal list not in sync!");
-
   MOZ_ALWAYS_TRUE(animVal->mItems.InsertElementAt(aIndex,
                                                   ItemProxy(nullptr,
                                                             aInternalIndex),
                                                   fallible));
 
   animVal->UpdateListIndicesFromIndex(aIndex + 1, 1 + aArgCountForItem);
 }
 
 void
 DOMSVGPathSegList::
   MaybeRemoveItemFromAnimValListAt(uint32_t aIndex,
                                    int32_t aArgCountForItem)
 {
   MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal");
 
-  if (AttrIsAnimating()) {
-    // animVal not a clone of baseVal
+  if (!AnimListMirrorsBaseList()) {
     return;
   }
 
   // This needs to be a strong reference; otherwise, the RemovingFromList call
   // below might drop the last reference to animVal before we're done with it.
   RefPtr<DOMSVGPathSegList> animVal =
     GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
-  if (!animVal) {
-    // No animVal list wrapper
-    return;
-  }
 
+  MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal");
   MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(),
              "animVal list not in sync!");
 
   if (animVal->ItemAt(aIndex)) {
     animVal->ItemAt(aIndex)->RemovingFromList();
   }
   animVal->mItems.RemoveElementAt(aIndex);
 
--- a/dom/svg/DOMSVGPathSegList.h
+++ b/dom/svg/DOMSVGPathSegList.h
@@ -123,16 +123,20 @@ public:
    */
   void InternalListWillChangeTo(const SVGPathData& aNewValue);
 
   /**
    * Returns true if our attribute is animating (in which case our animVal is
    * not simply a mirror of our baseVal).
    */
   bool AttrIsAnimating() const;
+  /**
+   * Returns true if there is an animated list mirroring the base list.
+   */
+  bool AnimListMirrorsBaseList() const;
 
   uint32_t NumberOfItems() const
   {
     if (IsAnimValList()) {
       Element()->FlushAnimations();
     }
     return LengthNoFlush();
   }
--- a/dom/svg/DOMSVGPointList.cpp
+++ b/dom/svg/DOMSVGPointList.cpp
@@ -180,16 +180,23 @@ DOMSVGPointList::InternalListWillChangeT
 }
 
 bool
 DOMSVGPointList::AttrIsAnimating() const
 {
   return InternalAList().IsAnimating();
 }
 
+bool
+DOMSVGPointList::AnimListMirrorsBaseList() const
+{
+  return GetDOMWrapperIfExists(InternalAList().GetAnimValKey()) &&
+           !AttrIsAnimating();
+}
+
 SVGPointList&
 DOMSVGPointList::InternalList() const
 {
   SVGAnimatedPointList *alist = mElement->GetAnimatedPointList();
   return mIsAnimValList && alist->IsAnimating() ? *alist->mAnimVal : alist->mBaseVal;
 }
 
 SVGAnimatedPointList&
@@ -412,56 +419,47 @@ DOMSVGPointList::GetItemAt(uint32_t aInd
   return result.forget();
 }
 
 void
 DOMSVGPointList::MaybeInsertNullInAnimValListAt(uint32_t aIndex)
 {
   MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal");
 
-  if (AttrIsAnimating()) {
-    // animVal not a clone of baseVal
+  if (!AnimListMirrorsBaseList()) {
     return;
   }
 
   // The anim val list is in sync with the base val list
   DOMSVGPointList *animVal =
     GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
-  if (!animVal) {
-    // No animVal list wrapper
-    return;
-  }
 
+  MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal");
   MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(),
              "animVal list not in sync!");
-
   MOZ_ALWAYS_TRUE(animVal->mItems.InsertElementAt(aIndex, nullptr, fallible));
 
   UpdateListIndicesFromIndex(animVal->mItems, aIndex + 1);
 }
 
 void
 DOMSVGPointList::MaybeRemoveItemFromAnimValListAt(uint32_t aIndex)
 {
   MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal");
 
-  if (AttrIsAnimating()) {
-    // animVal not a clone of baseVal
+  if (!AnimListMirrorsBaseList()) {
     return;
   }
 
   // This needs to be a strong reference; otherwise, the RemovingFromList call
   // below might drop the last reference to animVal before we're done with it.
   RefPtr<DOMSVGPointList> animVal =
     GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
-  if (!animVal) {
-    // No animVal list wrapper
-    return;
-  }
 
+  MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal");
   MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(),
              "animVal list not in sync!");
 
   if (animVal->mItems[aIndex]) {
     animVal->mItems[aIndex]->RemovingFromList();
   }
   animVal->mItems.RemoveElementAt(aIndex);
 
--- a/dom/svg/DOMSVGPointList.h
+++ b/dom/svg/DOMSVGPointList.h
@@ -125,16 +125,20 @@ public:
    */
   void InternalListWillChangeTo(const SVGPointList& aNewValue);
 
   /**
    * Returns true if our attribute is animating (in which case our animVal is
    * not simply a mirror of our baseVal).
    */
   bool AttrIsAnimating() const;
+  /**
+   * Returns true if there is an animated list mirroring the base list.
+   */
+  bool AnimListMirrorsBaseList() const;
 
   uint32_t NumberOfItems() const
   {
     if (IsAnimValList()) {
       Element()->FlushAnimations();
     }
     return LengthNoFlush();
   }
--- a/dom/svg/DOMSVGTransformList.cpp
+++ b/dom/svg/DOMSVGTransformList.cpp
@@ -391,45 +391,44 @@ DOMSVGTransformList::GetItemAt(uint32_t 
   return result.forget();
 }
 
 void
 DOMSVGTransformList::MaybeInsertNullInAnimValListAt(uint32_t aIndex)
 {
   MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal");
 
-  DOMSVGTransformList* animVal = mAList->mAnimVal;
-
-  if (!animVal || mAList->IsAnimating()) {
-    // No animVal list wrapper, or animVal not a clone of baseVal
+  if (!AnimListMirrorsBaseList()) {
     return;
   }
 
+  DOMSVGTransformList* animVal = mAList->mAnimVal;
+
+  MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal");
   MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(),
              "animVal list not in sync!");
-
   MOZ_ALWAYS_TRUE(animVal->mItems.InsertElementAt(aIndex, nullptr, fallible));
 
   UpdateListIndicesFromIndex(animVal->mItems, aIndex + 1);
 }
 
 void
 DOMSVGTransformList::MaybeRemoveItemFromAnimValListAt(uint32_t aIndex)
 {
   MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal");
 
+  if (!AnimListMirrorsBaseList()) {
+    return;
+  }
+
   // This needs to be a strong reference; otherwise, the RemovingFromList call
   // below might drop the last reference to animVal before we're done with it.
   RefPtr<DOMSVGTransformList> animVal = mAList->mAnimVal;
 
-  if (!animVal || mAList->IsAnimating()) {
-    // No animVal list wrapper, or animVal not a clone of baseVal
-    return;
-  }
-
+  MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal");
   MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(),
              "animVal list not in sync!");
 
   if (animVal->mItems[aIndex]) {
     animVal->mItems[aIndex]->RemovingFromList();
   }
   animVal->mItems.RemoveElementAt(aIndex);
 
--- a/dom/svg/DOMSVGTransformList.h
+++ b/dom/svg/DOMSVGTransformList.h
@@ -86,16 +86,22 @@ public:
 
   /**
    * Returns true if our attribute is animating (in which case our animVal is
    * not simply a mirror of our baseVal).
    */
   bool IsAnimating() const {
     return mAList->IsAnimating();
   }
+  /**
+   * Returns true if there is an animated list mirroring the base list.
+   */
+  bool AnimListMirrorsBaseList() const {
+    return mAList->mAnimVal && !mAList->IsAnimating();
+  }
 
   uint32_t NumberOfItems() const
   {
     if (IsAnimValList()) {
       Element()->FlushAnimations();
     }
     return LengthNoFlush();
   }
--- a/dom/tests/mochitest/fetch/test_fetch_cors.js
+++ b/dom/tests/mochitest/fetch/test_fetch_cors.js
@@ -1074,17 +1074,17 @@ function testCrossOriginCredentials() {
       }
     });
   }
 
   runATest(tests, 0);
   return finalPromise;
 }
 
-function testRedirects() {
+function testCORSRedirects() {
   var origin = "http://mochi.test:8888";
 
   var tests = [
            { pass: 1,
              method: "GET",
              hops: [{ server: "http://example.com",
                       allowOrigin: origin
                     },
@@ -1413,16 +1413,108 @@ function testRedirects() {
         ok(e instanceof TypeError, "Exception should be TypeError for " + test.toSource());
       });
     })(request, test));
   }
 
   return Promise.all(fetches);
 }
 
+function testNoCORSRedirects() {
+  var origin = "http://mochi.test:8888";
+
+  var tests = [
+           { pass: 1,
+             method: "GET",
+             hops: [{ server: "http://example.com",
+                    },
+                    ],
+           },
+           { pass: 1,
+             method: "GET",
+             hops: [{ server: origin,
+                    },
+                    { server: "http://example.com",
+                    },
+                    ],
+           },
+           { pass: 1,
+             method: "GET",
+             hops: [{ server: origin,
+                    },
+                    { server: "http://example.com",
+                    },
+                    { server: origin,
+                    }
+                    ],
+           },
+           { pass: 1,
+             method: "POST",
+             body: 'upload body here',
+             hops: [{ server: origin
+                    },
+                    { server: "http://example.com",
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "DELETE",
+             hops: [{ server: origin
+                    },
+                    { server: "http://example.com",
+                    },
+                    ],
+           },
+           ];
+
+  var fetches = [];
+  for (test of tests) {
+    req = {
+      url: test.hops[0].server + corsServerPath + "hop=1&hops=" +
+           escape(test.hops.toSource()),
+      method: test.method,
+      headers: test.headers,
+      body: test.body,
+    };
+
+    if (test.pass) {
+      if (test.body)
+        req.url += "&body=" + escape(test.body);
+    }
+
+    fetches.push((function(req, test) {
+      return new Promise(function(resolve, reject) {
+        resolve(new Request(req.url, { mode: 'no-cors',
+                                       method: req.method,
+                                       headers: req.headers,
+                                       body: req.body }));
+      }).then(function(request) {
+        return fetch(request);
+      }).then(function(res) {
+        ok(test.pass, "Expected test to pass for " + test.toSource());
+        // All requests are cross-origin no-cors, we should always have
+        // an opaque response here.  All values on the opaque response
+        // should be hidden.
+        is(res.type, 'opaque', 'wrong response type for ' + test.toSource());
+        is(res.status, 0, "wrong status in test for " + test.toSource());
+        is(res.statusText, "", "wrong status text for " + test.toSource());
+        is(res.url, '', 'wrong response url for ' + test.toSource());
+        return res.text().then(function(v) {
+          is(v, "", "wrong responseText in test for " + test.toSource());
+        });
+      }, function(e) {
+        ok(!test.pass, "Expected test failure for " + test.toSource());
+        ok(e instanceof TypeError, "Exception should be TypeError for " + test.toSource());
+      });
+    })(req, test));
+  }
+
+  return Promise.all(fetches);
+}
+
 function testReferrer() {
   var referrer;
   if (self && self.location) {
     referrer = self.location.href;
   } else {
     referrer = document.documentURI;
   }
 
@@ -1441,12 +1533,13 @@ function runTest() {
   testNoCorsCtor();
 
   return Promise.resolve()
     .then(testModeSameOrigin)
     .then(testModeNoCors)
     .then(testModeCors)
     .then(testSameOriginCredentials)
     .then(testCrossOriginCredentials)
-    .then(testRedirects)
+    .then(testCORSRedirects)
+    .then(testNoCORSRedirects)
     .then(testReferrer)
     // Put more promise based tests here.
 }
--- a/dom/tests/mochitest/whatwg/test_postMessage_jar.html
+++ b/dom/tests/mochitest/whatwg/test_postMessage_jar.html
@@ -10,19 +10,16 @@ https://bugzilla.mozilla.org/show_bug.cg
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <base href="http://mochi.test:8888/" />
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=430251">Mozilla Bug 430251</a>
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 
-<iframe src="jar:http://mochi.test:8888/tests/dom/tests/mochitest/whatwg/postMessage.jar!/postMessage_jar.html"
-        name="kid"></iframe>
-
 <pre id="test">
 <script class="testbody" type="application/javascript">
 /** Test for Bug 430251 **/
 
 SimpleTest.waitForExplicitFinish();
 
 function receiveMessage(evt)
 {
@@ -32,18 +29,24 @@ function receiveMessage(evt)
   is(evt.data, "finish-test", "wrong data");
   is(evt.lastEventId, "", "wrong lastEventId");
 
   SimpleTest.finish();
 }
 
 window.addEventListener("message", receiveMessage, false);
 
-function run()
-{
-  window.frames.kid.postMessage("start-test", "http://mochi.test:8888");
-}
+addLoadEvent(function() {
+  SpecialPowers.pushPrefEnv({"set": [["network.jar.block-remote-files", false]]}, function() {
+    var iframe = document.createElement('iframe');
+    iframe.setAttribute('src', 'jar:http://mochi.test:8888/tests/dom/tests/mochitest/whatwg/postMessage.jar!/postMessage_jar.html');
+    iframe.setAttribute('name', 'kid');
+    document.getElementById("content").appendChild(iframe);
 
-window.addEventListener("load", run, false);
+    iframe.onload = function() {
+      window.frames.kid.postMessage("start-test", "http://mochi.test:8888");
+    }
+  });
+});
 </script>
 </pre>
 </body>
 </html>
--- a/dom/webidl/BrowserElement.webidl
+++ b/dom/webidl/BrowserElement.webidl
@@ -174,9 +174,15 @@ interface BrowserElementPrivileged {
    CheckAllPermissions="browser browser:universalxss"]
   DOMRequest executeScript(DOMString script,
                            optional BrowserElementExecuteScriptOptions options);
 
   [Throws,
    Pref="dom.mozBrowserFramesEnabled",
    CheckAllPermissions="browser"]
   DOMRequest getStructuredData();
+
+  [Throws,
+   Pref="dom.mozBrowserFramesEnabled",
+   CheckAllPermissions="browser"]
+  DOMRequest getWebManifest();
+
 };
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -308,17 +308,17 @@ NS_IMPL_ISUPPORTS0(ServiceWorkerJob)
 void
 ServiceWorkerJob::Done(nsresult aStatus)
 {
   if (NS_WARN_IF(NS_FAILED(aStatus))) {
 #ifdef DEBUG
     nsAutoCString errorName;
     GetErrorName(aStatus, errorName);
 #endif
-    NS_WARNING(nsPrintfCString("ServiceWorkerJob failed with error: %s\n",
+    NS_WARNING(nsPrintfCString("ServiceWorkerJob failed with error: %s",
                                errorName.get()).get());
   }
 
   if (mQueue) {
     mQueue->Done(this);
   }
 }
 
@@ -2705,31 +2705,34 @@ ServiceWorkerManager::AddScopeAndRegistr
   }
 
   for (uint32_t i = 0; i < data->mOrderedScopes.Length(); ++i) {
     const nsCString& current = data->mOrderedScopes[i];
 
     // Perfect match!
     if (aScope.Equals(current)) {
       data->mInfos.Put(aScope, aInfo);
+      swm->NotifyListenersOnRegister(aInfo);
       return;
     }
 
     // Sort by length, with longest match first.
     // /foo/bar should be before /foo/
     // Similarly /foo/b is between the two.
     if (StringBeginsWith(aScope, current)) {
       data->mOrderedScopes.InsertElementAt(i, aScope);
       data->mInfos.Put(aScope, aInfo);
+      swm->NotifyListenersOnRegister(aInfo);
       return;
     }
   }
 
   data->mOrderedScopes.AppendElement(aScope);
   data->mInfos.Put(aScope, aInfo);
+  swm->NotifyListenersOnRegister(aInfo);
 }
 
 /* static */ bool
 ServiceWorkerManager::FindScopeForPath(const nsACString& aScopeKey,
                                        const nsACString& aPath,
                                        RegistrationDataPerPrincipal** aData,
                                        nsACString& aMatch)
 {
@@ -2786,18 +2789,22 @@ ServiceWorkerManager::RemoveScopeAndRegi
     return;
   }
 
   RegistrationDataPerPrincipal* data;
   if (!swm->mRegistrationInfos.Get(scopeKey, &data)) {
     return;
   }
 
+  RefPtr<ServiceWorkerRegistrationInfo> info;
+  data->mInfos.Get(aRegistration->mScope, getter_AddRefs(info));
+
   data->mInfos.Remove(aRegistration->mScope);
   data->mOrderedScopes.RemoveElement(aRegistration->mScope);
+  swm->NotifyListenersOnUnregister(info);
 
   swm->MaybeRemoveRegistrationInfo(scopeKey);
 }
 
 void
 ServiceWorkerManager::MaybeRemoveRegistrationInfo(const nsACString& aScopeKey)
 {
   RegistrationDataPerPrincipal* data;
@@ -3873,16 +3880,44 @@ ServiceWorkerManager::RemoveAllRegistrat
         RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
         swm->ForceUnregister(data, reg);
       }
     }
   }
 }
 
 NS_IMETHODIMP
+ServiceWorkerManager::AddListener(nsIServiceWorkerManagerListener* aListener)
+{
+  AssertIsOnMainThread();
+
+  if (mListeners.Contains(aListener)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  mListeners.AppendElement(aListener);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ServiceWorkerManager::RemoveListener(nsIServiceWorkerManagerListener* aListener)
+{
+  AssertIsOnMainThread();
+
+  if (!mListeners.Contains(aListener)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  mListeners.RemoveElement(aListener);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 ServiceWorkerManager::Observe(nsISupports* aSubject,
                               const char* aTopic,
                               const char16_t* aData)
 {
   if (strcmp(aTopic, PURGE_SESSION_HISTORY) == 0) {
     MOZ_ASSERT(XRE_IsParentProcess());
     RemoveAll();
     PropagateRemoveAll();
@@ -3993,16 +4028,36 @@ ServiceWorkerManager::PropagateUnregiste
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
 void
+ServiceWorkerManager::NotifyListenersOnRegister(
+                                        nsIServiceWorkerRegistrationInfo* aInfo)
+{
+  nsTArray<nsCOMPtr<nsIServiceWorkerManagerListener>> listeners(mListeners);
+  for (size_t index = 0; index < listeners.Length(); ++index) {
+    listeners[index]->OnRegister(aInfo);
+  }
+}
+
+void
+ServiceWorkerManager::NotifyListenersOnUnregister(
+                                        nsIServiceWorkerRegistrationInfo* aInfo)
+{
+  nsTArray<nsCOMPtr<nsIServiceWorkerManagerListener>> listeners(mListeners);
+  for (size_t index = 0; index < listeners.Length(); ++index) {
+    listeners[index]->OnUnregister(aInfo);
+  }
+}
+
+void
 ServiceWorkerInfo::AppendWorker(ServiceWorker* aWorker)
 {
   MOZ_ASSERT(aWorker);
 #ifdef DEBUG
   nsAutoString workerURL;
   aWorker->GetScriptURL(workerURL);
   MOZ_ASSERT(workerURL.Equals(NS_ConvertUTF8toUTF16(mScriptSpec)));
 #endif
--- a/dom/workers/ServiceWorkerManager.h
+++ b/dom/workers/ServiceWorkerManager.h
@@ -592,15 +592,23 @@ private:
   RemoveAllRegistrations(OriginAttributes* aParams);
 
   RefPtr<ServiceWorkerManagerChild> mActor;
 
   struct PendingOperation;
   nsTArray<PendingOperation> mPendingOperations;
 
   bool mShuttingDown;
+
+  nsTArray<nsCOMPtr<nsIServiceWorkerManagerListener>> mListeners;
+
+  void
+  NotifyListenersOnRegister(nsIServiceWorkerRegistrationInfo* aRegistration);
+
+  void
+  NotifyListenersOnUnregister(nsIServiceWorkerRegistrationInfo* aRegistration);
 };
 
 } // namespace workers
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_workers_serviceworkermanager_h
--- a/dom/workers/ServiceWorkerScriptCache.cpp
+++ b/dom/workers/ServiceWorkerScriptCache.cpp
@@ -348,17 +348,17 @@ public:
   MaybeCompare()
   {
     AssertIsOnMainThread();
 
     if (!mNetworkFinished || (mCC && !mCacheFinished)) {
       return;
     }
 
-    if (NS_WARN_IF(!mCC || !mInCache)) {
+    if (!mCC || !mInCache) {
       ComparisonFinished(NS_OK, false);
       return;
     }
 
     ComparisonFinished(NS_OK, mCC->Buffer().Equals(mCN->Buffer()));
   }
 
   // This class manages 2 promises: 1 is to retrieve Cache object, and 2 is to
--- a/dom/workers/test/serviceworkers/browser_force_refresh.js
+++ b/dom/workers/test/serviceworkers/browser_force_refresh.js
@@ -7,49 +7,63 @@ var gTestRoot = getRootDirectory(gTestPa
 function refresh() {
   EventUtils.synthesizeKey('R', { accelKey: true });
 }
 
 function forceRefresh() {
   EventUtils.synthesizeKey('R', { accelKey: true, shiftKey: true });
 }
 
+function frameScript() {
+  function eventHandler(event) {
+    sendAsyncMessage("test:event", {type: event.type});
+  }
+
+  // These are tab-local, so no need to unregister them.
+  addEventListener('base-load', eventHandler, true, true);
+  addEventListener('base-register', eventHandler, true, true);
+  addEventListener('base-sw-ready', eventHandler, true, true);
+  addEventListener('cached-load', eventHandler, true, true);
+}
+
 function test() {
   waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({'set': [['dom.serviceWorkers.enabled', true],
                                      ['dom.serviceWorkers.exemptFromPerDomainMax', true],
                                      ['dom.serviceWorkers.testing.enabled', true],
                                      ['dom.serviceWorkers.interception.enabled', true],
                                      ['dom.caches.enabled', true]]},
                             function() {
     var url = gTestRoot + 'browser_base_force_refresh.html';
-    var tab = gBrowser.addTab(url);
+    var tab = gBrowser.addTab();
     gBrowser.selectedTab = tab;
 
+    tab.linkedBrowser.messageManager.loadFrameScript("data:,(" + encodeURIComponent(frameScript) + ")()", true);
+    gBrowser.loadURI(url);
+
     var cachedLoad = false;
 
-    function eventHandler(event) {
-      if (event.type === 'base-load') {
+    function eventHandler(msg) {
+      if (msg.data.type === 'base-load') {
         if (cachedLoad) {
+          tab.linkedBrowser.messageManager.removeMessageListener("test:event", eventHandler);
+
           gBrowser.removeTab(tab);
           executeSoon(finish);
         }
-      } else if (event.type === 'base-register') {
+      } else if (msg.data.type === 'base-register') {
         ok(!cachedLoad, 'cached load should not occur before base register');
         refresh();
-      } else if (event.type === 'base-sw-ready') {
+      } else if (msg.data.type === 'base-sw-ready') {
         ok(!cachedLoad, 'cached load should not occur before base ready');
         refresh();
-      } else if (event.type === 'cached-load') {
+      } else if (msg.data.type === 'cached-load') {
         ok(!cachedLoad, 'cached load should not occur twice');
         cachedLoad = true;
         forceRefresh();
       }
 
       return;
     }
 
-    addEventListener('base-load', eventHandler, true, true);
-    addEventListener('base-register', eventHandler, true, true);
-    addEventListener('base-sw-ready', eventHandler, true, true);
-    addEventListener('cached-load', eventHandler, true, true);
+    tab.linkedBrowser.messageManager.addMessageListener("test:event", eventHandler);
   });
 }
--- a/dom/workers/test/serviceworkers/chrome.ini
+++ b/dom/workers/test/serviceworkers/chrome.ini
@@ -1,10 +1,13 @@
 [DEFAULT]
 skip-if = buildapp == 'b2g' || os == 'android'
 support-files =
   app/*
   app2/*
+  serviceworkermanager_iframe.html
+  worker.js
 
 [test_aboutserviceworkers.html]
 skip-if = true #bug 1193319
 [test_app_installation.html]
 [test_privateBrowsing.html]
+[test_serviceworkermanager.xul]
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/serviceworkermanager_iframe.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <script>
+      window.onmessage = function (event) {
+        if (event.data !== "register") {
+          return;
+        }
+        var promise = navigator.serviceWorker.register("worker.js");
+        window.onmessage = function (event) {
+          if (event.data !== "unregister") {
+            return;
+          }
+          promise.then(function (registration) {
+            registration.unregister();
+          });
+          window.onmessage = null;
+        };
+      };
+    </script>
+  </head>
+  <body>
+    This is a test page.
+  </body>
+<html>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_serviceworkermanager.xul
@@ -0,0 +1,111 @@
+<?xml version="1.0"?>
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="Test for ServiceWorkerManager"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        onload="test();">
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+  <script type="application/javascript">
+  <![CDATA[
+
+    let { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+    Cu.import("resource://gre/modules/Task.jsm");
+
+    let swm = Cc["@mozilla.org/serviceworkers/manager;1"].
+              getService(Ci.nsIServiceWorkerManager);
+
+    function waitForIframeLoad(iframe) {
+      return new Promise(function (resolve) {
+        iframe.onload = resolve;
+      });
+    }
+
+    function waitForRegister(scope) {
+      return new Promise(function (resolve) {
+        let listener = {
+          onRegister: function (registration) {
+            if (registration.scope !== scope) {
+              return;
+            }
+            swm.removeListener(listener);
+            resolve(registration);
+          }
+        };
+        swm.addListener(listener);
+      });
+    }
+
+    function waitForUnregister(scope) {
+      return new Promise(function (resolve) {
+        let listener = {
+          onUnregister: function (registration) {
+            if (registration.scope !== scope) {
+              return;
+            }
+            swm.removeListener(listener);
+            resolve(registration);
+          }
+        };
+        swm.addListener(listener);
+      });
+    }
+
+    let EXAMPLE_URL = "https://example.com/chrome/dom/workers/test/serviceworkers/";
+    let IFRAME_URL = EXAMPLE_URL + "serviceworkermanager_iframe.html";
+
+    function test() {
+      SimpleTest.waitForExplicitFinish();
+
+      SpecialPowers.pushPrefEnv({'set': [
+        ["dom.serviceWorkers.enabled", true],
+        ["dom.serviceWorkers.testing.enabled", true],
+      ]}, function () {
+        Task.spawn(function *() {
+          let registrations = swm.getAllRegistrations();
+          is(registrations.length, 0);
+
+          let iframe = $("iframe");
+          let promise = waitForIframeLoad(iframe);
+          iframe.src = IFRAME_URL;
+          yield promise;
+
+          info("Check that the service worker manager notifies its listeners " +
+               "when a service worker is registered.");
+          promise = waitForRegister(EXAMPLE_URL);
+          iframe.contentWindow.postMessage("register", "*");
+          let registration = yield promise;
+
+          registrations = swm.getAllRegistrations();
+          is(registrations.length, 1);
+          is(registrations.queryElementAt(0, Ci.nsIServiceWorkerRegistrationInfo),
+             registration);
+
+          info("Check that the service worker manager notifies its listeners " +
+               "when a service worker is unregistered.");
+          promise = waitForUnregister(EXAMPLE_URL);
+          iframe.contentWindow.postMessage("unregister", "*");
+          registration = yield promise;
+
+          registrations = swm.getAllRegistrations();
+          is(registrations.length, 0);
+
+          SimpleTest.finish();
+        });
+      }); 
+    }
+
+  ]]>
+  </script>
+
+  <body xmlns="http://www.w3.org/1999/xhtml">
+    <p id="display"></p>
+    <div id="content" style="display:none;"></div>
+    <pre id="test"></pre>
+    <iframe id="iframe"></iframe>
+  </body>
+  <label id="test-result"/>
+</window>
--- a/dom/workers/test/test_bug1063538.html
+++ b/dom/workers/test/test_bug1063538.html
@@ -33,15 +33,17 @@ function runTest() {
   };
 
   worker.postMessage(true);
 }
 
 SimpleTest.waitForExplicitFinish();
 
 addLoadEvent(function() {
-  SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], runTest);
+  SpecialPowers.pushPrefEnv({"set": [["network.jar.block-remote-files", false]]}, function() {
+    SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], runTest);
+  });
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/xbl/nsXBLService.cpp
+++ b/dom/xbl/nsXBLService.cpp
@@ -838,30 +838,47 @@ nsXBLService::GetBinding(nsIContent* aBo
     }
 
     NS_ADDREF(*aResult = newBinding);
   }
 
   return NS_OK;
 }
 
+static bool
+IsSystemOrChromeURLPrincipal(nsIPrincipal* aPrincipal)
+{
+  if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
+    return true;
+  }
+
+  nsCOMPtr<nsIURI> uri;
+  aPrincipal->GetURI(getter_AddRefs(uri));
+  NS_ENSURE_TRUE(uri, false);
+
+  bool isChrome = false;
+  return NS_SUCCEEDED(uri->SchemeIs("chrome", &isChrome)) && isChrome;
+}
+
 nsresult
 nsXBLService::LoadBindingDocumentInfo(nsIContent* aBoundElement,
                                       nsIDocument* aBoundDocument,
                                       nsIURI* aBindingURI,
                                       nsIPrincipal* aOriginPrincipal,
                                       bool aForceSyncLoad,
                                       nsXBLDocumentInfo** aResult)
 {
   NS_PRECONDITION(aBindingURI, "Must have a binding URI");
   NS_PRECONDITION(!aOriginPrincipal || aBoundDocument,
                   "If we're doing a security check, we better have a document!");
 
   *aResult = nullptr;
-  if (aOriginPrincipal && !nsContentUtils::IsSystemPrincipal(aOriginPrincipal)) {
+  // Allow XBL in unprivileged documents if it's specified in a privileged or
+  // chrome: stylesheet. This allows themes to specify XBL bindings.
+  if (aOriginPrincipal && !IsSystemOrChromeURLPrincipal(aOriginPrincipal)) {
     NS_ENSURE_TRUE(!aBoundDocument || aBoundDocument->AllowXULXBL(),
                    NS_ERROR_XBL_BLOCKED);
   }
 
   RefPtr<nsXBLDocumentInfo> info;
 
   nsCOMPtr<nsIURI> documentURI;
   nsresult rv = aBindingURI->CloneIgnoringRef(getter_AddRefs(documentURI));
--- a/gfx/2d/DrawTargetD2D1.cpp
+++ b/gfx/2d/DrawTargetD2D1.cpp
@@ -279,36 +279,43 @@ DrawTargetD2D1::MaskSurface(const Patter
                             SourceSurface *aMask,
                             Point aOffset,
                             const DrawOptions &aOptions)
 {
   MarkChanged();
 
   RefPtr<ID2D1Bitmap> bitmap;
 
-  RefPtr<ID2D1Image> image = GetImageForSurface(aMask, ExtendMode::CLAMP);
+  Matrix mat = Matrix::Translation(aOffset);
+  RefPtr<ID2D1Image> image = GetImageForSurface(aMask, mat, ExtendMode::CLAMP, nullptr);
+
+  MOZ_ASSERT(!mat.HasNonTranslation());
+  aOffset.x = mat._31;
+  aOffset.y = mat._32;
 
   if (!image) {
     gfxWarning() << "Failed to get image for surface.";
     return;
   }
 
   PrepareForDrawing(aOptions.mCompositionOp, aSource);
 
   // FillOpacityMask only works if the antialias mode is MODE_ALIASED
   mDC->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
 
-  IntSize size = aMask->GetSize();
-  Rect maskRect = Rect(0.f, 0.f, Float(size.width), Float(size.height));
-  image->QueryInterface((ID2D1Bitmap**)&bitmap);
+  image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap));
   if (!bitmap) {
     gfxWarning() << "FillOpacityMask only works with Bitmap source surfaces.";
     return;
   }
 
+  IntSize size = IntSize(bitmap->GetSize().width, bitmap->GetSize().height);
+
+  Rect maskRect = Rect(0.f, 0.f, Float(size.width), Float(size.height));
+
   Rect dest = Rect(aOffset.x, aOffset.y, Float(size.width), Float(size.height));
   RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aSource, aOptions.mAlpha);
   mDC->FillOpacityMask(bitmap, brush, D2D1_OPACITY_MASK_CONTENT_GRAPHICS, D2DRect(dest), D2DRect(maskRect));
 
   mDC->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
 
   FinalizeDrawing(aOptions.mCompositionOp, aSource);
 }
new file mode 100644
--- /dev/null
+++ b/gfx/graphite2/ChangeLog
@@ -0,0 +1,181 @@
+1.3.4
+    . Transition from Mercurial to Git
+    . Bug fixes
+        . Fix Collision Kerning ignoring some diacritics
+        . Handle pass bits 16-31 to speed up fonts with > 16 passes
+        . Various minor fuzz bug fixes
+        . Make Coverity happy
+        . Add GR_FALLTHROUGH macro for clang c++11
+
+1.3.3
+    . Slight speed up in Collision Avoidance
+    . Remove dead bidi code
+    . Bug fixes
+        . Between pass bidi reorderings and at the end
+        . Decompressor fuzz bugs
+        . Other fuzz bugs
+
+1.3.2
+    . Remove full bidi. All segments are assumed to be single directioned.
+    . Bug fixes:
+        . Decompressor corner cases
+        . Various fuzz bugs
+
+1.3.1
+    . Deprecation warning: Full bidi support is about to be deprecated. Make contact
+      if this impacts you.
+    . Change compression block format slightly to conform to LZ4
+    . Bug fixes:
+        . Handle mono direction text with diacritics consistently. Fonts
+          now see the direction they expect consistently and bidi now
+          gives expected results.
+        . Fixed lots of fuzz bugs
+        . Coverity cleanups
+        . Build now works for clang and/or asan and/or afl etc.
+
+1.3.0
+    . Add collision avoidance
+        . Shift Collider
+        . Kern Collider
+        . Octabox outlines and subboxes
+    . Add compressed Silf and Glat table support
+    . Bug fixes:
+        . Stop loops forming in the child, sibling tree
+        . Handle bidi mirroring correctly if no bidi occurring
+
+1.2.4
+    . Face failure now has error code reporting via debug logging
+        . can now call gr_start_logging(NULL, fname)
+        . gr2fonttest --alltrace added
+    . Format 14 table support
+        . Not done. To be handled entirely in the compiler
+    . Bidi support for Unicode 6.3 Isolating direction controls
+    . Fonts no longer require a glyf/loca table. In such cases the bounding box is always 0.
+    . Clang ASAN build support added for testing.
+    . Handle out of memory sanely.
+    . Documentation improvements
+    . Bug fixes:
+        . Enforce fonts having to store glyph attributes by monotonically increasing attribute number
+        . zeropadding was not getting called on feature tags
+        . automatic associations for unassociated characters
+        . use direct engine on Mac
+        . various extreme case reading 1 past the end errors fixed
+        . remove tabs from sources so that it becomes readable again
+
+1.2.3
+    . Bug fixes only:
+        . fix byte swapping when testing cmap subtable lengths
+        . work around armel compilation problems with conditional operators
+        . fix pseudoglyph support for advance and bbox
+
+1.2.2
+    . Add support for passKeySlot (makes Charis 2x faster) up to 32 passes
+    . Add telemetry output to json if enabled in build GRAPHITE2_TELEMETRY
+    . Shrink font memory footprint particularly in the fsm
+    . Add -S to comparerenderer
+    . Bug fixes:
+        . Fix shift.x being reversed for rtl text
+        . Fix faulty fallback justification
+        . Fix bad cmap handling
+        . Support compiling on old Solaris where bidi attributes clash with register names
+        . Follow the crowd in using Windows.h
+
+1.2.1
+    . Bug fixes:
+        . Allow glyph reattachment
+        . Allow signed glyph attributes
+        . Various build problems with MacOS, old gcc versions, etc.
+        . Various overrun read errors fixed
+
+1.2.0
+    . API Changes:
+        . Added Windows friendly gr_start_logging and gr_stop_logging, now per face
+        . Added gr_make_face_with_ops, gr_make_face_with_seg_cache_and_ops
+        . Added gr_make_font_with_ops
+        . Added gr_face_is_char_supported
+        . Added gr_face_info to give info to apps about face capabilities
+        . Deprecated gr_make_face, gr_make_face_with_seg_cache, gr_make_font_with_advance_fn
+        . Deprecated graphite_start_logging and graphite_stop_logging
+            . These functions are stubbed now and do nothing, but do compile and link.
+        . Bump API version to 3
+    . Add C# wrapper to contrib
+    . Handle justification information in a font and do something useful with it
+    . Builds clang clean (has done for a while)
+    . Bug fixes
+    . Windows build and bug fixes
+    . Add extra information to json debug output
+    . Added windows build documentation
+    . Added freetype sample code and test
+
+1.1.3
+    . Default build has GRAPHITE2_COMPARE_RENDERER to OFF to reduce dependencies
+    . Builds on Mac with clang
+    . Debug output improvements
+    . Tidy up perl wrappers
+    . Fuzz tester improvements
+    . Various bug fixes for bad font handling
+
+1.1.2
+    . Support feature ids < 4 chars when space padded for inclusion in FF 14.
+    . More fuzztesting and removal of causes of valgrind bad reads and sigabrts
+    . Remove contrib/android into its own repo (http://hg.palaso.org/grandroid)
+    . Update comparerenderer to latest harfbuzzng api
+
+1.1.1
+    . Missing Log.h included
+    . perl wrappers updated
+
+1.1.0
+    . Refactored debug output to use json
+    . Renamed VM_MACHINE_TYPE to GRAPHITE2_VM_TYPE
+    . Renamed DISABLE_SEGCACHE to GRAPHITE2_NSEGCACE
+    . Renamed DISBALE_FILE_FACE to GRAPHITE2_NFILEFACE
+    . Renamed ENABLE_COMPARE_RENDERER to GRAPHTIE2_COMPARE_RENDERER
+    . Renamed DOXYGEN_CONFIG to GRAPHITE2_DOXYGEN_CONFIG
+    . Renamed GR2_CUSTOM_HEADER to GRAPHITE2_CUSTOM_HEADER
+    . Renamed GR2_EXPORTING to GRAPHITE2_EXPORTING
+    . Added GRAPHITE2_STATIC for static only builds
+    . Added GRAPHITE2_NTRACING to compile out tracing code
+    . Documented GRAPHITE2_{EXPORTING,STATIC,NTRACING} in hacking.txt
+    . Bump libtool version to 2.1.0
+    . dumb font rendering works
+    . slot user attributes are now signed rather than unsigned
+    . add support for long class maps
+    . Rename perl library to avoid nameclash on Windows
+    . Various robustness fixes
+    . Moved internal .h files into src/inc
+    . Parallelise fuzztest
+    . General build improvements, particularly on Windows
+
+1.0.3
+    . Fix UTF16 surrogate support
+    . script and lang tags may be space padded or null padded
+    . Remove need for WORDS_BIGENDIAN, do it all automatically
+    . Remove all #include <new>. Use CLASS_NEW_DELETE instead.
+    . Fix comparerenderer to work with current hbng
+    . Add valgrind to fuzztest to ensure good memory use at all times
+    . Fix new fuzztest exposed bugs.
+    . Fix bugs exposed by Mozilla security review
+    . Add continuous integration build on Windows support
+
+1.0.2
+    . Fix Windows build
+    . Comparerenderer uses hbng enforcing ot rendering
+    . Add Bidi .hasChar support and refactor mirroring code
+    . Make cmake default Release rather than debug
+    . Don't compile in a boat load of TtfUtil that isn't used, saving 15% of binary
+    . Chase the FSF around its latest office moves
+    . WORDS_BIGENDIAN is set at the top so tests now pass on ppc, etc.
+    . More words in the manual
+
+1.0.1
+    . Release is the default build in cmake now.
+    . Refactor cmake build to not rebuild things so much.
+    . Include a missing file
+    . Remove -nostdlibs, making gcc happy everywhere
+    . Update comparerenderer to latest hbng interface
+    . Add changelog
+
+1.0.0
+    . First major release of perfect code!
+
new file mode 100644
--- /dev/null
+++ b/gfx/graphite2/LICENSE
@@ -0,0 +1,510 @@
+
+                  GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+	51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations
+below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it
+becomes a de-facto standard.  To achieve this, non-free programs must
+be allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+                  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control
+compilation and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at least
+    three years, to give the same user the materials specified in
+    Subsection 6a, above, for a charge no more than the cost of
+    performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply, and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License
+may add an explicit geographical distribution limitation excluding those
+countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+                            NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms
+of the ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.
+It is safest to attach them to the start of each source file to most
+effectively convey the exclusion of warranty; and each file should
+have at least the "copyright" line and a pointer to where the full
+notice is found.
+
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or
+your school, if any, to sign a "copyright disclaimer" for the library,
+if necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James
+  Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
+
new file mode 100644
--- /dev/null
+++ b/gfx/graphite2/README.md
@@ -0,0 +1,32 @@
+# Graphite engine
+
+## What is Graphite?
+
+Graphite is a system that can be used to create “smart fonts” capable of displaying writing systems with various complex behaviors. A smart font contains not only letter shapes but also additional instructions indicating how to combine and position the letters in complex ways.
+
+Graphite was primarily developed to provide the flexibility needed for minority languages which often need to be written according to slightly different rules than well-known languages that use the same script.
+
+Examples of complex script behaviors Graphite can handle include:
+
+* contextual shaping
+* ligatures
+* reordering
+* split glyphs
+* bidirectionality
+* stacking diacritics
+* complex positioning
+* shape aware kerning
+* automatic diacritic collision avoidance
+
+See [examples of scripts with complex rendering](http://scripts.sil.org/CmplxRndExamples).
+
+## Graphite system overview
+The Graphite system consists of:
+
+* A rule-based programming language [Graphite Description Language](http://scripts.sil.org/cms/scripts/page.php?site_id=projects&item_id=graphite_devFont#gdl) (GDL) that can be used to describe the behavior of a writing system
+* A compiler for that language
+* A rendering engine that can serve as the layout component of a text-processing application
+
+Graphite renders TrueType fonts that have been extended by means of compiling a GDL program.
+
+Further technical information is available on the [Graphite technical overview](http://scripts.sil.org/cms/scripts/page.php?site_id=projects&item_id=graphite_techAbout) page.
--- a/gfx/graphite2/README.mozilla
+++ b/gfx/graphite2/README.mozilla
@@ -1,6 +1,3 @@
-This directory contains the Graphite2 library from http://hg.palaso.org/graphitedev
-
-Current version derived from upstream changeset ff457b44c490
-
-See gfx/graphite2/moz-gr-update.sh for update procedure.
-
+This directory contains the Graphite2 library release 1.3.4 from
+https://github.com/silnrsi/graphite/releases/download/1.3.4/graphite2-minimal-1.3.4.tgz
+See ./gfx/graphite2/moz-gr-update.sh for update procedure.
--- a/gfx/graphite2/include/graphite2/Font.h
+++ b/gfx/graphite2/include/graphite2/Font.h
@@ -25,17 +25,17 @@
     either version 2 of the License or (at your option) any later version.
 */
 #pragma once
 
 #include "graphite2/Types.h"
 
 #define GR2_VERSION_MAJOR   1
 #define GR2_VERSION_MINOR   3
-#define GR2_VERSION_BUGFIX  3
+#define GR2_VERSION_BUGFIX  4
 
 #ifdef __cplusplus
 extern "C"
 {
 #endif
 
 typedef struct gr_face          gr_face;
 typedef struct gr_font          gr_font;
--- a/gfx/graphite2/moz-gr-update.sh
+++ b/gfx/graphite2/moz-gr-update.sh
@@ -1,35 +1,49 @@
 #!/bin/bash
 
 # Script used to update the Graphite2 library in the mozilla source tree
 
 # This script lives in gfx/graphite2, along with the library source,
 # but must be run from the top level of the mozilla-central tree.
 
-# It expects to find a checkout of the graphite2 tree in a directory "graphitedev"
-# alongside the current mozilla tree that is to be updated.
-# Expect error messages from the copy commands if this is not found!
+# Run as
+#
+#    ./gfx/graphite2/moz-gr-update.sh RELEASE
+#
+# where RELEASE is the graphite2 release to be used, e.g. "1.3.4".
+
+RELEASE=$1
+
+if [ "x$RELEASE" == "x" ]
+then
+    echo "Must provide the version number to be used."
+    exit 1
+fi
 
-# copy the source and headers
-cp -R ../graphitedev/src/* gfx/graphite2/src
-cp ../graphitedev/include/graphite2/* gfx/graphite2/include/graphite2
+TARBALL="https://github.com/silnrsi/graphite/releases/download/$RELEASE/graphite2-minimal-$RELEASE.tgz"
+
+foo=`basename $0`
+TMPFILE=`mktemp -t ${foo}` || exit 1
 
-# record the upstream changeset that was used
-CHANGESET=$(cd ../graphitedev/ && hg log | head -n 1 | cut -d : -f 1,3 | sed -e 's/:/ /')
-echo "This directory contains the Graphite2 library from http://hg.palaso.org/graphitedev\n" > gfx/graphite2/README.mozilla
-echo "Current version derived from upstream" $CHANGESET >> gfx/graphite2/README.mozilla
-echo "\nSee" $0 "for update procedure.\n" >> gfx/graphite2/README.mozilla
+curl -L "$TARBALL" -o "$TMPFILE"
+tar -x -z -C gfx/graphite2/ --strip-components 1 -f "$TMPFILE" || exit 1
+rm "$TMPFILE"
+
+echo "This directory contains the Graphite2 library release $RELEASE from" > gfx/graphite2/README.mozilla
+echo "$TARBALL" >> gfx/graphite2/README.mozilla
+echo ""
+echo "See" $0 "for update procedure." >> gfx/graphite2/README.mozilla
 
 # fix up includes because of bug 721839 (cstdio) and bug 803066 (Windows.h)
-find gfx/graphite2/ -name "*.cpp" -exec perl -p -i -e "s/<cstdio>/<stdio.h>/;s/Windows.h/windows.h/;" {} \;
-find gfx/graphite2/ -name "*.h" -exec perl -p -i -e "s/<cstdio>/<stdio.h>/;s/Windows.h/windows.h/;" {} \;
+#find gfx/graphite2/ -name "*.cpp" -exec perl -p -i -e "s/<cstdio>/<stdio.h>/;s/Windows.h/windows.h/;" {} \;
+#find gfx/graphite2/ -name "*.h" -exec perl -p -i -e "s/<cstdio>/<stdio.h>/;s/Windows.h/windows.h/;" {} \;
 
 # summarize what's been touched
-echo Updated to $CHANGESET.
+echo Updated to $RELEASE.
 echo Here is what changed in the gfx/graphite2 directory:
 echo
 
 hg stat gfx/graphite2
 
 echo
 echo If gfx/graphite2/src/files.mk has changed, please make corresponding
 echo changes to gfx/graphite2/src/moz.build
--- a/gfx/graphite2/src/CMakeLists.txt
+++ b/gfx/graphite2/src/CMakeLists.txt
@@ -109,16 +109,20 @@ set_target_properties(graphite2 PROPERTI
 if  (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
     set_target_properties(graphite2 PROPERTIES 
         COMPILE_FLAGS   "-Wall -Wextra -Wno-unknown-pragmas -Wendif-labels -Wshadow -Wctor-dtor-privacy -Wnon-virtual-dtor -fno-rtti -fno-exceptions -fvisibility=hidden -fvisibility-inlines-hidden -fno-stack-protector"
         LINK_FLAGS      "-nodefaultlibs ${GRAPHITE_LINK_FLAGS}" 
         LINKER_LANGUAGE C)
     if (CMAKE_COMPILER_IS_GNUCXX)
         add_definitions(-Wdouble-promotion)
     endif (CMAKE_COMPILER_IS_GNUCXX)
+    message(STATUS "Compiler ID is: ${CMAKE_CXX_COMPILER_ID}")
+    if (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
+        add_definitions(-Wimplicit-fallthrough)
+    endif (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
     if (${CMAKE_CXX_COMPILER} MATCHES  ".*mingw.*")
         target_link_libraries(graphite2 kernel32 msvcr90 mingw32 gcc user32)
     else (${CMAKE_CXX_COMPILER} MATCHES  ".*mingw.*")
         if (GRAPHITE2_ASAN)
             target_link_libraries(graphite2 c gcc_s)
         else (GRAPHITE2_ASAN)
             target_link_libraries(graphite2 c gcc)
         endif (GRAPHITE2_ASAN)
@@ -126,17 +130,17 @@ if  (${CMAKE_SYSTEM_NAME} STREQUAL "Linu
         nolib_test(stdc++ $<TARGET_SONAME_FILE:graphite2>)
     endif (${CMAKE_CXX_COMPILER} MATCHES  ".*mingw.*")
     set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "")
     CREATE_LIBTOOL_FILE(graphite2 "/lib${LIB_SUFFIX}")
 endif (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
 
 if  (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
     set_target_properties(graphite2 PROPERTIES 
-        COMPILE_FLAGS   "-Wall -Wextra -Wno-unknown-pragmas -Wendif-labels -Wshadow -Wno-ctor-dtor-privacy -Wno-non-virtual-dtor -fno-rtti -fno-exceptions -fvisibility=hidden -fvisibility-inlines-hidden -fno-stack-protector"
+        COMPILE_FLAGS   "-Wall -Wextra -Wno-unknown-pragmas -Wimplicit-fallthrough -Wendif-labels -Wshadow -Wno-ctor-dtor-privacy -Wno-non-virtual-dtor -fno-rtti -fno-exceptions -fvisibility=hidden -fvisibility-inlines-hidden -fno-stack-protector"
         LINK_FLAGS      "-nodefaultlibs" 
         LINKER_LANGUAGE C)
     target_link_libraries(graphite2 c)
     include(Graphite)
     nolib_test(stdc++ $<TARGET_SONAME_FILE:graphite2>)
     set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "")
     CREATE_LIBTOOL_FILE(graphite2 "/lib${LIB_SUFFIX}")
 endif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
--- a/gfx/graphite2/src/Code.cpp
+++ b/gfx/graphite2/src/Code.cpp
@@ -37,17 +37,17 @@ of the License or (at your option) any l
 #include "inc/Code.h"
 #include "inc/Face.h"
 #include "inc/GlyphFace.h"
 #include "inc/GlyphCache.h"
 #include "inc/Machine.h"
 #include "inc/Rule.h"
 #include "inc/Silf.h"
 
-#include <stdio.h>
+#include <cstdio>
 
 #ifdef NDEBUG
 #ifdef __GNUC__
 #pragma GCC diagnostic ignored "-Wunused-parameter"
 #endif
 #endif
 
 
@@ -416,16 +416,17 @@ opcode Machine::Code::decoder::fetch_opc
             }
             break;
         case PUSH_IGLYPH_ATTR :// not implemented
             ++_stack_depth;
             break;
         case POP_RET :
             if (--_stack_depth < 0)
                 failure(underfull_stack);
+            GR_FALLTHROUGH;
             // no break
         case RET_ZERO :
         case RET_TRUE :
             break;
         case IATTR_SET :
         case IATTR_ADD :
         case IATTR_SUB :
             if (--_stack_depth < 0)
@@ -499,42 +500,45 @@ void Machine::Code::decoder::analyse_opc
     case INSERT :
       _analysis.contexts[_analysis.slotref].flags.inserted = true;
       _code._modify = true;
       break;
     case PUT_SUBS_8BIT_OBS :    // slotref on 1st parameter
     case PUT_SUBS : 
       _code._modify = true;
       _analysis.set_changed(0);
+      GR_FALLTHROUGH;
       // no break
     case PUT_COPY :
     {
       if (arg[0] != 0) { _analysis.set_changed(0); _code._modify = true; }
       if (arg[0] <= 0 && -arg[0] <= _analysis.slotref - _analysis.contexts[_analysis.slotref].flags.inserted)
         _analysis.set_ref(arg[0], true);
       else if (arg[0] > 0)
         _analysis.set_ref(arg[0], true);
       break;
     }
     case PUSH_ATT_TO_GATTR_OBS : // slotref on 2nd parameter
         if (_code._constraint) return;
+        GR_FALLTHROUGH;
         // no break
     case PUSH_GLYPH_ATTR_OBS :
     case PUSH_SLOT_ATTR :
     case PUSH_GLYPH_METRIC :
     case PUSH_ATT_TO_GLYPH_METRIC :
     case PUSH_ISLOT_ATTR :
     case PUSH_FEAT :
       if (arg[1] <= 0 && -arg[1] <= _analysis.slotref - _analysis.contexts[_analysis.slotref].flags.inserted)
         _analysis.set_ref(arg[1], true);
       else if (arg[1] > 0)
         _analysis.set_ref(arg[1], true);
       break;
     case PUSH_ATT_TO_GLYPH_ATTR :
         if (_code._constraint) return;
+        GR_FALLTHROUGH;
         // no break
     case PUSH_GLYPH_ATTR :
       if (arg[2] <= 0 && -arg[2] <= _analysis.slotref - _analysis.contexts[_analysis.slotref].flags.inserted)
         _analysis.set_ref(arg[2], true);
       else if (arg[2] > 0)
         _analysis.set_ref(arg[2], true);
       break;
     case ASSOC :                // slotrefs in varargs
@@ -589,17 +593,20 @@ bool Machine::Code::decoder::emit_opcode
             data_skip  = instr_skip - (_code._instr_count - ctxt_start);
             instr_skip = _code._instr_count - ctxt_start;
             _max.bytecode = curr_end;
 
             _rule_length = 1;
             _pre_context = 0;
         }
         else
+        {
+            _pre_context = 0;
             return false;
+        }
     }
     
     return bool(_code);
 }
 
 
 void Machine::Code::decoder::apply_analysis(instr * const code, instr * code_end)
 {
--- a/gfx/graphite2/src/Collider.cpp
+++ b/gfx/graphite2/src/Collider.cpp
@@ -891,27 +891,27 @@ bool KernCollider::initSlot(Segment *seg
         int smin = max(0, int((bs.yi + toffset) / _sliceWidth));
         int smax = min(numSlices - 1, int((bs.ya + toffset) / _sliceWidth + 1));
         for (int i = smin; i <= smax; ++i)
         {
             float t;
             float y = _miny - 1 + (i + .5f) * _sliceWidth; // vertical center of slice
             if ((dir & 1) && x < _edges[i])
             {
-                t = get_edge(seg, s, currShift, y, _sliceWidth, false);
+                t = get_edge(seg, s, c->shift(), y, _sliceWidth, false);
                 if (t < _edges[i])
                 {
                     _edges[i] = t;
                     if (t < _xbound)
                         _xbound = t;
                 }
             }
             else if (!(dir & 1) && x > _edges[i])
             {
-                t = get_edge(seg, s, currShift, y, _sliceWidth, true);
+                t = get_edge(seg, s, c->shift(), y, _sliceWidth, true);
                 if (t > _edges[i])
                 {
                     _edges[i] = t;
                     if (t > _xbound)
                         _xbound = t;
                 }
             }
         }
@@ -988,25 +988,26 @@ Position KernCollider::resolve(GR_MAYBE_
 #if !defined GRAPHITE2_NTRACING
     if (dbgout)
     {
         *dbgout << json::object // slot
                 << "slot" << objectid(dslot(seg, _target))
 				<< "gid" << _target->gid()
                 << "margin" << _margin
                 << "limit" << _limit
+                << "miny" << _miny
+                << "maxy" << _maxy
+                << "slicewidth" << _sliceWidth
                 << "target" << json::object
                     << "origin" << _target->origin()
                     //<< "currShift" << _currShift
                     << "offsetPrev" << _offsetPrev
                     << "bbox" << seg->theGlyphBBoxTemporary(_target->gid())
                     << "slantBox" << seg->getFace()->glyphs().slant(_target->gid())
                     << "fix" << "kern"
-                    << "slices" << _edges.size()
-                    << "sliceWidth" << _sliceWidth
                     << json::close; // target object
         
         *dbgout << "slices" << json::array;
         for (int is = 0; is < (int)_edges.size(); is++)
         {
             *dbgout << json::flat << json::object 
                 << "i" << is 
                 << "targetEdge" << _edges[is]
--- a/gfx/graphite2/src/Decompressor.cpp
+++ b/gfx/graphite2/src/Decompressor.cpp
@@ -80,17 +80,17 @@ int lz4::decompress(void const *in, size
         match_dist = 0;
     
     while (read_sequence(src, src_end, literal, literal_len, match_len, match_dist))
     {
         if (literal_len != 0)
         {
             // Copy in literal. At this point the last full sequence must be at
             // least MINMATCH + 5 from the end of the output buffer.
-            if (dst + align(literal_len) > dst_end - MINMATCH+5)
+            if (dst + align(literal_len) > dst_end - (MINMATCH+5))
                 return -1;
             dst = overrun_copy(dst, literal, literal_len);
         }
         
         // Copy, possibly repeating, match from earlier in the
         //  decoded output.
         u8 const * const pcpy = dst - match_dist;
         if (pcpy < static_cast<u8*>(out)
--- a/gfx/graphite2/src/Face.cpp
+++ b/gfx/graphite2/src/Face.cpp
@@ -325,41 +325,42 @@ Error Face::Table::decompress()
     switch(compression(hdr >> 27))
     {
     case NONE: return e;
 
     case LZ4:
     {
         uncompressed_size  = hdr & 0x07ffffff;
         uncompressed_table = gralloc<byte>(uncompressed_size);
-        //TODO: Coverty: 1315803: FORWARD_NULL
         if (!e.test(!uncompressed_table, E_OUTOFMEM))
-            //TODO: Coverty: 1315800: CHECKED_RETURN
+            // coverity[forward_null : FALSE] - uncompressed_table has been checked so can't be null
+            // coverity[checked_return : FALSE] - we test e later
             e.test(lz4::decompress(p, _sz - 2*sizeof(uint32), uncompressed_table, uncompressed_size) != signed(uncompressed_size), E_SHRINKERFAILED);
         break;
     }
 
     default:
         e.error(E_BADSCHEME);
     };
 
     // Check the uncompressed version number against the original.
     if (!e)
-        //TODO: Coverty: 1315800: CHECKED_RETURN
+        // coverity[forward_null : FALSE] - uncompressed_table has already been tested so can't be null
+        // coverity[checked_return : FALSE] - we test e later
         e.test(be::peek<uint32>(uncompressed_table) != version, E_SHRINKERFAILED);
 
     // Tell the provider to release the compressed form since were replacing
     //   it anyway.
     releaseBuffers();
 
     if (e)
     {
         free(uncompressed_table);
         uncompressed_table = 0;
         uncompressed_size  = 0;
     }
 
     _p = uncompressed_table;
-    _sz = uncompressed_size + sizeof(uint32);
+    _sz = uncompressed_size;
     _compressed = true;
 
     return e;
 }
--- a/gfx/graphite2/src/GlyphCache.cpp
+++ b/gfx/graphite2/src/GlyphCache.cpp
@@ -294,17 +294,23 @@ GlyphCache::Loader::Loader(const Face & 
         _num_glyphs_attributes = static_cast<unsigned short>(tmpnumgattrs);
         p = m_pGlat;
         version = be::read<uint32>(p);
         if (version >= 0x00040000)       // reject Glat tables that are too new
         {
             _head = Face::Table();
             return;
         }
-        _has_boxes = (version == 0x00030000);
+        else if (version >= 0x00030000)
+        {
+            unsigned int glatflags = be::read<uint32>(p);
+            _has_boxes = glatflags & 1;
+            // delete this once the compiler is fixed
+            _has_boxes = true;
+        }
     }
 }
 
 inline
 GlyphCache::Loader::operator bool () const throw()
 {
     return _head && _hhea && _hmtx && !(bool(_glyf) != bool(_loca));
 }
--- a/gfx/graphite2/src/Intervals.cpp
+++ b/gfx/graphite2/src/Intervals.cpp
@@ -150,23 +150,25 @@ void Zones::remove(float x, float xm)
         const uint8 oca = i->outcode(x),
                     ocb = i->outcode(xm);
         if ((oca & ocb) != 0)   continue;
 
         switch (oca ^ ocb)  // What kind of overlap?
         {
         case 0:     // i completely covers e
             if (separated(i->x, x))  { i = _exclusions.insert(i,i->split_at(x)); ++i; }
+            GR_FALLTHROUGH;
             // no break
         case 1:     // i overlaps on the rhs of e
             i->left_trim(xm);
             return;
         case 2:     // i overlaps on the lhs of e
             i->xm = x;
             if (separated(i->x, i->xm)) break;
+            GR_FALLTHROUGH;
             // no break
         case 3:     // e completely covers i
             i = _exclusions.erase(i);
             --i;
             break;
         }
 
         ie = _exclusions.end();
--- a/gfx/graphite2/src/Justifier.cpp
+++ b/gfx/graphite2/src/Justifier.cpp
@@ -160,17 +160,17 @@ float Segment::justify(Slot *pSlot, cons
                 }
                 else
                 {
                     float max = uint16(s->getJustify(this, i, 1));
                     if (i == 0) max += s->just();
                     if (-pref > max) pref = -max;
                     else tWeight += w;
                 }
-                int actual = step ? int(pref / step) * step : int(pref);
+                int actual = int(pref / step) * step;
 
                 if (actual)
                 {
                     error += diffpw * w - actual;
                     if (i == 0)
                         s->just(s->just() + actual);
                     else
                         s->setJustify(this, i, 4, actual);
--- a/gfx/graphite2/src/Pass.cpp
+++ b/gfx/graphite2/src/Pass.cpp
@@ -121,17 +121,18 @@ bool Pass::readPass(const byte * const p
     m_numColumns = be::read<uint16>(p);
     numRanges = be::read<uint16>(p);
     be::skip<uint16>(p, 3); // skip searchRange, entrySelector & rangeShift.
     assert(p - pass_start == 40);
     // Perform some sanity checks.
     if ( e.test(m_numTransition > m_numStates, E_BADNUMTRANS)
             || e.test(m_numSuccess > m_numStates, E_BADNUMSUCCESS)
             || e.test(m_numSuccess + m_numTransition < m_numStates, E_BADNUMSTATES)
-            || e.test(m_numRules && numRanges == 0, E_NORANGES))
+            || e.test(m_numRules && numRanges == 0, E_NORANGES)
+            || e.test(m_numColumns > 0x7FFF, E_BADNUMCOLUMNS))
         return face.error(e);
 
     m_successStart = m_numStates - m_numSuccess;
     // test for beyond end - 1 to account for reading uint16
     if (e.test(p + numRanges * 6 - 2 > pass_end, E_BADPASSLENGTH)) return face.error(e);
     m_numGlyphs = be::peek<uint16>(p + numRanges * 6 - 4) + 1;
     // Calculate the start of various arrays.
     const byte * const ranges = p;
--- a/gfx/graphite2/src/SegCacheEntry.cpp
+++ b/gfx/graphite2/src/SegCacheEntry.cpp
@@ -56,28 +56,29 @@ SegCacheEntry::SegCacheEntry(const uint1
         {
             if (s->m_justs == 0)    continue;
             ++num_justs;
         }
         m_justs = gralloc<byte>(sizeof_sjust * num_justs);
     }
     const Slot * slot = seg->first();
     m_glyph = new Slot[glyphCount];
-    m_attr = gralloc<int16>(glyphCount * seg->numAttrs());
+    int attrSize = seg->numAttrs() + (seg->hasCollisionInfo() ? (sizeof(SlotCollision) + 1) / 2 : 0);
+    m_attr = gralloc<int16>(glyphCount * attrSize);
     if (!m_glyph || (!m_attr && seg->numAttrs())) return;
     m_glyphLength = glyphCount;
     Slot * slotCopy = m_glyph;
     m_glyph->prev(NULL);
 
     uint16 pos = 0;
     while (slot)
     {
-        slotCopy->userAttrs(m_attr + pos * seg->numAttrs());
+        slotCopy->userAttrs(m_attr + pos * attrSize);
         slotCopy->m_justs = m_justs ? reinterpret_cast<SlotJustify *>(m_justs + justs_pos++ * sizeof_sjust) : 0;
-        slotCopy->set(*slot, -static_cast<int32>(charOffset), seg->numAttrs(), seg->silf()->numJustLevels(), length);
+        slotCopy->set(*slot, -static_cast<int32>(charOffset), attrSize, seg->silf()->numJustLevels(), length);
         slotCopy->index(pos);
         if (slot->firstChild())
             slotCopy->m_child = m_glyph + slot->firstChild()->index();
         if (slot->attachedTo())
             slotCopy->attachTo(m_glyph + slot->attachedTo()->index());
         if (slot->nextSibling())
             slotCopy->m_sibling = m_glyph + slot->nextSibling()->index();
         slot = slot->next();
--- a/gfx/graphite2/src/Segment.cpp
+++ b/gfx/graphite2/src/Segment.cpp
@@ -41,28 +41,27 @@ of the License or (at your option) any l
 
 
 using namespace graphite2;
 
 Segment::Segment(unsigned int numchars, const Face* face, uint32 script, int textDir)
 : m_freeSlots(NULL),
   m_freeJustifies(NULL),
   m_charinfo(new CharInfo[numchars]),
-  m_collisions(NULL),
   m_face(face),
   m_silf(face->chooseSilf(script)),
   m_first(NULL),
   m_last(NULL),
   m_bufSize(numchars + 10),
   m_numGlyphs(numchars),
   m_numCharinfo(numchars),
   m_passBits(m_silf->aPassBits() ? -1 : 0),
   m_defaultOriginal(0),
   m_dir(textDir),
-  m_flags(0)
+  m_flags(((m_silf->flags() & 0x20) != 0) << 1)
 {
     freeSlot(newSlot());
     m_bufSize = log_binary(numchars)+1;
 }
 
 Segment::~Segment()
 {
     for (SlotRope::iterator i = m_slots.begin(); i != m_slots.end(); ++i)
@@ -171,21 +170,27 @@ Slot *Segment::newSlot()
         // check that the segment doesn't grow indefinintely
         if (m_numGlyphs > m_numCharinfo * MAX_SEG_GROWTH_FACTOR)
             return NULL;
         int numUser = m_silf->numUser();
 #if !defined GRAPHITE2_NTRACING
         if (m_face->logger()) ++numUser;
 #endif
         Slot *newSlots = grzeroalloc<Slot>(m_bufSize);
-        int16 *newAttrs = grzeroalloc<int16>(numUser * m_bufSize);
-        if (!newSlots || !newAttrs) return NULL;
+        int attrSize = numUser + (hasCollisionInfo() ? ((sizeof(SlotCollision) + 1) / 2) : 0);
+        int16 *newAttrs = grzeroalloc<int16>(m_bufSize * attrSize);
+        if (!newSlots || !newAttrs)
+        {
+            free(newSlots);
+            free(newAttrs);
+            return NULL;
+        }
         for (size_t i = 0; i < m_bufSize; i++)
         {
-            ::new (newSlots + i) Slot(newAttrs + i * numUser);
+            ::new (newSlots + i) Slot(newAttrs + i * attrSize);
             newSlots[i].next(newSlots + i + 1);
         }
         newSlots[m_bufSize - 1].next(NULL);
         newSlots[0].next(NULL);
         m_slots.push_back(newSlots);
         m_userAttrs.push_back(newAttrs);
         m_freeSlots = (m_bufSize > 1)? newSlots + 1 : NULL;
         return newSlots;
@@ -204,17 +209,18 @@ void Segment::freeSlot(Slot *aSlot)
         aSlot->attachedTo()->removeChild(aSlot);
     while (aSlot->firstChild())
     {
         aSlot->firstChild()->attachTo(NULL);
         aSlot->removeChild(aSlot->firstChild());
     }
     // reset the slot incase it is reused
     ::new (aSlot) Slot(aSlot->userAttrs());
-    memset(aSlot->userAttrs(), 0, m_silf->numUser() * sizeof(int16));
+    int attrSize = m_silf->numUser() + (hasCollisionInfo() ? ((sizeof(SlotCollision) + 1) / 2) : 0);
+    memset(aSlot->userAttrs(), 0, attrSize * sizeof(int16));
     // Update generation counter for debug
 #if !defined GRAPHITE2_NTRACING
     if (m_face->logger())
         ++aSlot->userAttrs()[m_silf->numUser()];
 #endif
     // update next pointer
     if (!m_freeSlots)
         aSlot->next(NULL);
@@ -293,23 +299,24 @@ void Segment::splice(size_t offset, size
         }
     }
 
     endSlot = endSlot->next();
     assert(numGlyphs == length);
     assert(offset + numChars <= m_numCharinfo);
     Slot * indexmap[eMaxSpliceSize*3];
     assert(numGlyphs < sizeof indexmap/sizeof *indexmap);
+    int attrSize = m_silf->numUser() + (hasCollisionInfo() ? ((sizeof(SlotCollision) + 1) / 2) : 0);
     Slot * slot = startSlot;
     for (uint16 i=0; i < numGlyphs; slot = slot->next(), ++i)
         indexmap[i] = slot;
 
     for (slot = startSlot; slot != endSlot; slot = slot->next(), srcSlot = srcSlot->next())
     {
-        slot->set(*srcSlot, offset, m_silf->numUser(), m_silf->numJustLevels(), numChars);
+        slot->set(*srcSlot, offset, attrSize, m_silf->numJustLevels(), numChars);
         if (srcSlot->attachedTo())  slot->attachTo(indexmap[srcSlot->attachedTo()->index()]);
         if (srcSlot->nextSibling()) slot->m_sibling = indexmap[srcSlot->nextSibling()->index()];
         if (srcSlot->firstChild())  slot->m_child = indexmap[srcSlot->firstChild()->index()];
     }
 }
 #endif // GRAPHITE2_NSEGCACHE
 
 // reverse the slots but keep diacritics in their same position after their bases
@@ -511,19 +518,12 @@ void Segment::doMirror(uint16 aMirror)
         unsigned short g = glyphAttr(s->gid(), aMirror);
         if (g && (!(dir() & 4) || !glyphAttr(s->gid(), aMirror + 1)))
             s->setGlyph(this, g);
     }
 }
 
 bool Segment::initCollisions()
 {
-    if (m_collisions) free(m_collisions);
-    Slot *p = m_first;
-    m_collisions = gralloc<SlotCollision>(slotCount());
-    if (!m_collisions) return false;
-    for (unsigned short i = 0; i < slotCount(); ++i)
-    {
-        ::new (m_collisions + p->index()) SlotCollision(this, p);
-        p = p->next();
-    }
+    for (Slot *p = m_first; p; p = p->next())
+        ::new (collisionInfo(p)) SlotCollision(this, p);
     return true;
 }
--- a/gfx/graphite2/src/Silf.cpp
+++ b/gfx/graphite2/src/Silf.cpp
@@ -389,17 +389,17 @@ bool Silf::runGraphite(Segment *seg, uin
                 seg->positionSlots(0, 0, 0, m_dir);
                 for(Slot * s = seg->first(); s; s = s->next())
                     *dbgout     << dslot(seg, s);
                 *dbgout         << json::close
                             << "rules"  << json::array << json::close
                             << json::close;
             }
 #endif
-            if (seg->currdir() != m_dir)
+            if (seg->currdir() != (m_dir & 1))
                 seg->reverseSlots();
             if (m_aMirror && (seg->dir() & 3) == 3)
                 seg->doMirror(m_aMirror);
         --i;
         lbidi = lastPass;
         --lastPass;
         continue;
         }
--- a/gfx/graphite2/src/Slot.cpp
+++ b/gfx/graphite2/src/Slot.cpp
@@ -41,17 +41,17 @@ Slot::Slot(int16 *user_attrs) :
     m_position(0, 0), m_shift(0, 0), m_advance(0, 0),
     m_attach(0, 0), m_with(0, 0), m_just(0.),
     m_flags(0), m_attLevel(0), m_bidiCls(-1), m_bidiLevel(0), 
     m_userAttr(user_attrs), m_justs(NULL)
 {
 }
 
 // take care, this does not copy any of the GrSlot pointer fields
-void Slot::set(const Slot & orig, int charOffset, size_t numUserAttr, size_t justLevels, size_t numChars)
+void Slot::set(const Slot & orig, int charOffset, size_t sizeAttr, size_t justLevels, size_t numChars)
 {
     // leave m_next and m_prev unchanged
     m_glyphid = orig.m_glyphid;
     m_realglyphid = orig.m_realglyphid;
     m_original = orig.m_original + charOffset;
     if (charOffset + int(orig.m_before) < 0)
         m_before = 0;
     else
@@ -68,17 +68,17 @@ void Slot::set(const Slot & orig, int ch
     m_advance = orig.m_advance;
     m_attach = orig.m_attach;
     m_with = orig.m_with;
     m_flags = orig.m_flags;
     m_attLevel = orig.m_attLevel;
     m_bidiCls = orig.m_bidiCls;
     m_bidiLevel = orig.m_bidiLevel;
     if (m_userAttr && orig.m_userAttr)
-        memcpy(m_userAttr, orig.m_userAttr, numUserAttr * sizeof(*m_userAttr));
+        memcpy(m_userAttr, orig.m_userAttr, sizeAttr * sizeof(*m_userAttr));
     if (m_justs && orig.m_justs)
         memcpy(m_justs, orig.m_justs, SlotJustify::size_of(justLevels));
 }
 
 void Slot::update(int /*numGrSlots*/, int numCharInfo, Position &relpos)
 {
     m_before += numCharInfo;
     m_after += numCharInfo;
@@ -464,17 +464,21 @@ void Slot::setGlyph(Segment *seg, uint16
     const GlyphFace *aGlyph = theGlyph;
     if (m_realglyphid)
     {
         aGlyph = seg->getFace()->glyphs().glyphSafe(m_realglyphid);
         if (!aGlyph) aGlyph = theGlyph;
     }
     m_advance = Position(aGlyph->theAdvance().x, 0.);
     if (seg->silf()->aPassBits())
+    {
         seg->mergePassBits(theGlyph->attrs()[seg->silf()->aPassBits()]);
+        if (seg->silf()->numPasses() > 16)
+            seg->mergePassBits(theGlyph->attrs()[seg->silf()->aPassBits()+1] << 16);
+    }
 }
 
 void Slot::floodShift(Position adj)
 {
     m_position += adj;
     if (m_child) m_child->floodShift(adj);
     if (m_sibling) m_sibling->floodShift(adj);
 }
@@ -496,17 +500,18 @@ Slot * Slot::nextInCluster(const Slot *s
 {
     Slot *base;
     if (s->firstChild())
         return s->firstChild();
     else if (s->nextSibling())
         return s->nextSibling();
     while ((base = s->attachedTo()))
     {
-        if (base->firstChild() == s && base->nextSibling())
+        // if (base->firstChild() == s && base->nextSibling())
+        if (base->nextSibling())
             return base->nextSibling();
         s = base;
     }
     return NULL;
 }
 
 bool Slot::isChildOf(const Slot *base) const
 {
--- a/gfx/graphite2/src/TtfUtil.cpp
+++ b/gfx/graphite2/src/TtfUtil.cpp
@@ -847,33 +847,33 @@ const void * FindCmapSubtable(const void
     {
         if (be::swap(pTable->encoding[i].platform_id) == nPlatformId &&
                 (nEncodingId == -1 || be::swap(pTable->encoding[i].platform_specific_id) == nEncodingId))
         {
             uint32 offset = be::swap(pTable->encoding[i].offset);
             const uint8 * pRtn = reinterpret_cast<const uint8 *>(pCmap) + offset;
             if (length)
             {
-                if (offset + 2 > length) return NULL;
+                if (offset > length - 2) return NULL;
                 uint16 format = be::read<uint16>(pRtn);
                 if (format == 4)
                 {
-                    if (offset + 4 > length) return NULL;
+                    if (offset > length - 4) return NULL;
                     uint16 subTableLength = be::peek<uint16>(pRtn);
                     if (i + 1 == csuPlatforms)
                     {
                         if (subTableLength > length - offset)
                             return NULL;
                     }
                     else if (subTableLength > be::swap(pTable->encoding[i+1].offset))
                         return NULL;
                 }
                 if (format == 12)
                 {
-                    if (offset + 6 > length) return NULL;
+                    if (offset > length - 6) return NULL;
                     uint32 subTableLength = be::peek<uint32>(pRtn);
                     if (i + 1 == csuPlatforms)
                     {
                         if (subTableLength > length - offset)
                             return NULL;
                     }
                     else if (subTableLength > be::swap(pTable->encoding[i+1].offset))
                         return NULL;
@@ -1205,27 +1205,27 @@ size_t LocaLookup(gid16 nGlyphId,
         const void * pHead) // throw (std::out_of_range)
 {
     const Sfnt::FontHeader * pTable = reinterpret_cast<const Sfnt::FontHeader *>(pHead);
     size_t res = -2;
 
     // CheckTable verifies the index_to_loc_format is valid
     if (be::swap(pTable->index_to_loc_format) == Sfnt::FontHeader::ShortIndexLocFormat)
     { // loca entries are two bytes and have been divided by two
-        if (nGlyphId < (lLocaSize >> 1) - 1) // allow sentinel value to be accessed
+        if (lLocaSize > 1 && nGlyphId + 1u < lLocaSize >> 1) // allow sentinel value to be accessed
         {
             const uint16 * pShortTable = reinterpret_cast<const uint16 *>(pLoca);
             res = be::peek<uint16>(pShortTable + nGlyphId) << 1;
             if (res == static_cast<size_t>(be::peek<uint16>(pShortTable + nGlyphId + 1) << 1))
                 return -1;
         }
     }
     else if (be::swap(pTable->index_to_loc_format) == Sfnt::FontHeader::LongIndexLocFormat)
     { // loca entries are four bytes
-        if (nGlyphId < (lLocaSize >> 2) - 1)
+        if (lLocaSize > 3 && nGlyphId + 1u < lLocaSize >> 2)
         {
             const uint32 * pLongTable = reinterpret_cast<const uint32 *>(pLoca);
             res = be::peek<uint32>(pLongTable + nGlyphId);
             if (res == static_cast<size_t>(be::peek<uint32>(pLongTable + nGlyphId + 1)))
                 return -1;
         }
     }
 
--- a/gfx/graphite2/src/gr_logging.cpp
+++ b/gfx/graphite2/src/gr_logging.cpp
@@ -19,17 +19,17 @@
     Suite 500, Boston, MA 02110-1335, USA or visit their web page on the 
     internet at http://www.fsf.org/licenses/lgpl.html.
 
 Alternatively, the contents of this file may be used under the terms of the
 Mozilla Public License (http://mozilla.org/MPL) or the GNU General Public
 License, as published by the Free Software Foundation, either version 2
 of the License or (at your option) any later version.
 */
-#include <stdio.h>
+#include <cstdio>
 
 #include "graphite2/Log.h"
 #include "inc/debug.h"
 #include "inc/CharInfo.h"
 #include "inc/Slot.h"
 #include "inc/Segment.h"
 #include "inc/json.h"
 #include "inc/Collider.h"
--- a/gfx/graphite2/src/inc/Error.h
+++ b/gfx/graphite2/src/inc/Error.h
@@ -110,16 +110,17 @@ enum errors {
     E_BADSTATE = 49,        // Bad state transition referencing an illegal state
     E_BADRULEMAPPING = 50,  // The structure of the rule mapping is bad
     E_BADRANGE = 51,        // Bad column range structure including a glyph in more than one column
     E_BADRULENUM = 52,      // A reference to a rule is out of range (too high)
     E_BADACOLLISION = 53,   // Bad Silf table collision attribute number (too high)
     E_BADEMPTYPASS = 54,    // Can't have empty passes (no rules) except for collision passes
     E_BADSILFVERSION = 55,  // The Silf table has a bad version (probably too high)
     E_BADCOLLISIONPASS = 56,    // Collision flags set on a non positioning pass
+    E_BADNUMCOLUMNS = 57,   // Arbitrarily limit number of columns in fsm
 // Code errors
     E_CODEFAILURE = 60,     // Base code error. The following subcodes must align with Machine::Code::status_t in Code.h
     E_CODEALLOC = 61,       // Out of memory
     E_INVALIDOPCODE = 62,   // Invalid op code
     E_UNIMPOPCODE = 63,     // Unimplemented op code encountered
     E_OUTOFRANGECODE = 64,  // Code argument out of range
     E_BADJUMPCODE = 65,     // Code jumps past end of op codes
     E_CODEBADARGS = 66,     // Code arguments exhausted
--- a/gfx/graphite2/src/inc/Face.h
+++ b/gfx/graphite2/src/inc/Face.h
@@ -21,17 +21,17 @@
 
 Alternatively, the contents of this file may be used under the terms of the
 Mozilla Public License (http://mozilla.org/MPL) or the GNU General Public
 License, as published by the Free Software Foundation, either version 2
 of the License or (at your option) any later version.
 */
 #pragma once
 
-#include <stdio.h>
+#include <cstdio>
 
 #include "graphite2/Font.h"
 
 #include "inc/Main.h"
 #include "inc/FeatureMap.h"
 #include "inc/TtfUtil.h"
 #include "inc/Silf.h"
 #include "inc/Error.h"
--- a/gfx/graphite2/src/inc/FileFace.h
+++ b/gfx/graphite2/src/inc/FileFace.h
@@ -27,17 +27,17 @@ of the License or (at your option) any l
 #pragma once
 
 //#include "inc/FeatureMap.h"
 //#include "inc/GlyphsCache.h"
 //#include "inc/Silf.h"
 
 #ifndef GRAPHITE2_NFILEFACE
 
-#include <stdio.h>
+#include <cstdio>
 #include <cassert>
 
 #include "graphite2/Font.h"
 
 #include "inc/Main.h"
 #include "inc/TtfTypes.h"
 #include "inc/TtfUtil.h"
 
--- a/gfx/graphite2/src/inc/Main.h
+++ b/gfx/graphite2/src/inc/Main.h
@@ -115,18 +115,32 @@ inline T max(const T a, const T b)
     void * operator new   (size_t, void * p) throw() { return p; } \
     void * operator new[] (size_t size) {return gralloc<byte>(size);} \
     void * operator new[] (size_t, void * p) throw() { return p; } \
     void operator delete   (void * p) throw() { free(p);} \
     void operator delete   (void *, void *) throw() {} \
     void operator delete[] (void * p)throw() { free(p); } \
     void operator delete[] (void *, void *) throw() {}
 
-#ifdef __GNUC__
+#if defined(__GNUC__)  || defined(__clang__)
 #define GR_MAYBE_UNUSED __attribute__((unused))
 #else
 #define GR_MAYBE_UNUSED
 #endif
 
+#if defined(__clang__) && __cplusplus >= 201103L
+   /* clang's fallthrough annotations are only available starting in C++11. */
+    #define GR_FALLTHROUGH [[clang::fallthrough]]
+#elif defined(_MSC_VER)
+   /*
+    * MSVC's __fallthrough annotations are checked by /analyze (Code Analysis):
+    * https://msdn.microsoft.com/en-us/library/ms235402%28VS.80%29.aspx
+    */
+    #include <sal.h>
+    #define GR_FALLTHROUGH __fallthrough
+#else
+    #define GR_FALLTHROUGH /* fallthrough */
+#endif
+
 #ifdef _MSC_VER
 #pragma warning(disable: 4800)
 #pragma warning(disable: 4355)
 #endif
--- a/gfx/graphite2/src/inc/Segment.h
+++ b/gfx/graphite2/src/inc/Segment.h
@@ -82,17 +82,18 @@ class Segment
 {
     // Prevent copying of any kind.
     Segment(const Segment&);
     Segment& operator=(const Segment&);
 
 public:
 
     enum {
-        SEG_INITCOLLISIONS = 1
+        SEG_INITCOLLISIONS = 1,
+        SEG_HASCOLLISIONS = 2
     };
 
     unsigned int slotCount() const { return m_numGlyphs; }      //one slot per glyph
     void extendLength(int num) { m_numGlyphs += num; }
     Position advance() const { return m_advance; }
     bool runGraphite() { if (m_silf) return m_face->runGraphite(this, m_silf); else return true;};
     void chooseSilf(uint32 script) { m_silf = m_face->chooseSilf(script); }
     const Silf *silf() const { return m_silf; }
@@ -153,18 +154,18 @@ public:
     int8 getSlotBidiClass(Slot *s) const;
     void doMirror(uint16 aMirror);
     Slot *addLineEnd(Slot *nSlot);
     void delLineEnd(Slot *s);
     bool hasJustification() const { return m_justifies.size() != 0; }
     void reverseSlots();
 
     bool isWhitespace(const int cid) const;
-    bool hasCollisionInfo() const { return m_collisions != 0; }
-    SlotCollision *collisionInfo(const Slot *s) const { return m_collisions ? m_collisions + s->index() : NULL; }
+    bool hasCollisionInfo() const { return (m_flags & SEG_HASCOLLISIONS); }
+    SlotCollision *collisionInfo(const Slot *s) const { return hasCollisionInfo() ? reinterpret_cast<SlotCollision *>(s->userAttrs() + m_silf->numUser()) : 0; }
 
     CLASS_NEW_DELETE
 
 public:       //only used by: GrSegment* makeAndInitialize(const GrFont *font, const GrFace *face, uint32 script, const FeaturesHandle& pFeats/*must not be IsNull*/, encform enc, const void* pStart, size_t nChars, int dir);
     bool read_text(const Face *face, const Features* pFeats/*must not be NULL*/, gr_encform enc, const void*pStart, size_t nChars);
     void finalise(const Font *font, bool reverse=false);
     float justify(Slot *pSlot, const Font *font, float width, enum justFlags flags, Slot *pFirst, Slot *pLast);
     bool initCollisions();
@@ -173,17 +174,16 @@ private:
     Position        m_advance;          // whole segment advance
     SlotRope        m_slots;            // Vector of slot buffers
     AttributeRope   m_userAttrs;        // Vector of userAttrs buffers
     JustifyRope     m_justifies;        // Slot justification info buffers
     FeatureList     m_feats;            // feature settings referenced by charinfos in this segment
     Slot          * m_freeSlots;        // linked list of free slots
     SlotJustify   * m_freeJustifies;    // Slot justification blocks free list
     CharInfo      * m_charinfo;         // character info, one per input character
-    SlotCollision * m_collisions;       // Array of SlotCollisions for each slot
     const Face    * m_face;             // GrFace
     const Silf    * m_silf;
     Slot          * m_first;            // first slot in segment
     Slot          * m_last;             // last slot in segment
     unsigned int    m_bufSize,          // how big a buffer to create when need more slots
                     m_numGlyphs,
                     m_numCharinfo,      // size of the array and number of input characters
                     m_passBits;         // if bit set then skip pass
@@ -192,17 +192,17 @@ private:
     uint8           m_flags;            // General purpose flags
 };
 
 inline
 int8 Segment::getSlotBidiClass(Slot *s) const
 {
     int8 res = s->getBidiClass();
     if (res != -1) return res;
-    res = glyphAttr(s->gid(), m_silf->aBidi());
+    res = int8(glyphAttr(s->gid(), m_silf->aBidi()));
     s->setBidiClass(res);
     return res;
 }
 
 inline
 void Segment::finalise(const Font *font, bool reverse)
 {
     if (!m_first) return;
--- a/gfx/graphite2/src/inc/Sparse.h
+++ b/gfx/graphite2/src/inc/Sparse.h
@@ -122,16 +122,17 @@ sparse::sparse(I attr, const I last)
                                                  + n_values);
 
     if (m_array.values == 0)
     {
         free(m_array.values); m_array.map=0;
         return;
     }
 
+    // coverity[forward_null : FALSE] Since m_array is union and m_array.values is not NULL
     chunk * ci = m_array.map;
     ci->offset = (m_nchunks*sizeof(chunk) + sizeof(mapped_type)-1)/sizeof(mapped_type);
     mapped_type * vi = m_array.values + ci->offset;
     for (; attr != last; ++attr, ++vi)
     {
         const typename std::iterator_traits<I>::value_type v = *attr;
         if (v.second == 0)  { --vi; continue; }
 
--- a/gfx/graphite2/src/inc/UtfCodec.h
+++ b/gfx/graphite2/src/inc/UtfCodec.h
@@ -126,19 +126,22 @@ public:
     static uchar_t get(const codeunit_t * cp, int8 & l) throw()
     {
         const int8 seq_sz = sz_lut[*cp >> 4];
         uchar_t u = *cp & mask_lut[seq_sz];
         l = 1;
         bool toolong = false;
 
         switch(seq_sz) {
-            case 4:     u <<= 6; u |= *++cp & 0x3F; if (*cp >> 6 != 2) break; ++l; toolong  = (u < 0x10); // no break
-            case 3:     u <<= 6; u |= *++cp & 0x3F; if (*cp >> 6 != 2) break; ++l; toolong |= (u < 0x20); // no break
-            case 2:     u <<= 6; u |= *++cp & 0x3F; if (*cp >> 6 != 2) break; ++l; toolong |= (u < 0x80); // no break
+            case 4:     u <<= 6; u |= *++cp & 0x3F; if (*cp >> 6 != 2) break; ++l; toolong  = (u < 0x10); GR_FALLTHROUGH;
+                // no break
+            case 3:     u <<= 6; u |= *++cp & 0x3F; if (*cp >> 6 != 2) break; ++l; toolong |= (u < 0x20); GR_FALLTHROUGH;
+                // no break
+            case 2:     u <<= 6; u |= *++cp & 0x3F; if (*cp >> 6 != 2) break; ++l; toolong |= (u < 0x80); GR_FALLTHROUGH;
+                // no break
             case 1:     break;
             case 0:     l = -1; return 0xFFFD;
         }
 
         if (l != seq_sz || toolong)
         {
             l = -l;
             return 0xFFFD;
--- a/gfx/graphite2/src/inc/json.h
+++ b/gfx/graphite2/src/inc/json.h
@@ -27,17 +27,17 @@ of the License or (at your option) any l
 // JSON pretty printer for graphite font debug output logging.
 // Created on: 15 Dec 2011
 //     Author: Tim Eves
 
 #pragma once
 
 #include "inc/Main.h"
 #include <cassert>
-#include <stdio.h>
+#include <cstdio>
 #include "inc/List.h"
 
 namespace graphite2 {
 
 class json
 {
     // Prevent copying
     json(const json &);
--- a/gfx/graphite2/src/json.cpp
+++ b/gfx/graphite2/src/json.cpp
@@ -24,17 +24,17 @@ Mozilla Public License (http://mozilla.o
 License, as published by the Free Software Foundation, either version 2
 of the License or (at your option) any later version.
 */
 // JSON debug logging
 // Author: Tim Eves
 
 #if !defined GRAPHITE2_NTRACING
 
-#include <stdio.h>
+#include <cstdio>
 #include <limits>
 #include "inc/json.h"
 
 using namespace graphite2;
 
 namespace
 {
     enum
--- a/gfx/layers/apz/src/InputBlockState.cpp
+++ b/gfx/layers/apz/src/InputBlockState.cpp
@@ -98,19 +98,17 @@ CancelableBlockState::CancelableBlockSta
 bool
 CancelableBlockState::SetContentResponse(bool aPreventDefault)
 {
   if (mContentResponded) {
     return false;
   }
   TBS_LOG("%p got content response %d with timer expired %d\n",
     this, aPreventDefault, mContentResponseTimerExpired);
-  if (!mContentResponseTimerExpired) {
-    mPreventDefault = aPreventDefault;
-  }
+  mPreventDefault = aPreventDefault;
   mContentResponded = true;
   return true;
 }
 
 bool
 CancelableBlockState::TimeoutContentResponse()
 {
   if (mContentResponseTimerExpired) {
@@ -620,16 +618,17 @@ PanGestureBlockState::SetNeedsToWaitForC
 }
 
 TouchBlockState::TouchBlockState(const RefPtr<AsyncPanZoomController>& aTargetApzc,
                                  bool aTargetConfirmed, TouchCounter& aCounter)
   : CancelableBlockState(aTargetApzc, aTargetConfirmed)
   , mAllowedTouchBehaviorSet(false)
   , mDuringFastFling(false)
   , mSingleTapOccurred(false)
+  , mInSlop(false)
   , mTouchCounter(aCounter)
 {
   TBS_LOG("Creating %p\n", this);
 }
 
 bool
 TouchBlockState::SetAllowedTouchBehaviors(const nsTArray<TouchBehaviorFlags>& aBehaviors)
 {
@@ -826,10 +825,38 @@ TouchBlockState::TouchActionAllowsPannin
     // Default to allowed
     return true;
   }
   TouchBehaviorFlags flags = mAllowedTouchBehaviors[0];
   return (flags & AllowedTouchBehavior::HORIZONTAL_PAN)
       && (flags & AllowedTouchBehavior::VERTICAL_PAN);
 }
 
+bool
+TouchBlockState::UpdateSlopState(const MultiTouchInput& aInput)
+{
+  if (aInput.mType == MultiTouchInput::MULTITOUCH_START) {
+    // this is by definition the first event in this block. If it's the first
+    // touch, then we enter a slop state.
+    mInSlop = (aInput.mTouches.Length() == 1);
+    if (mInSlop) {
+      mSlopOrigin = aInput.mTouches[0].mScreenPoint;
+      TBS_LOG("%p entering slop with origin %s\n", this, Stringify(mSlopOrigin).c_str());
+    }
+    return false;
+  }
+  if (mInSlop) {
+    bool stayInSlop = (aInput.mType == MultiTouchInput::MULTITOUCH_MOVE) &&
+        (aInput.mTouches.Length() == 1) &&
+        ((aInput.mTouches[0].mScreenPoint - mSlopOrigin).Length() <
+            AsyncPanZoomController::GetTouchStartTolerance());
+    if (!stayInSlop) {
+      // we're out of the slop zone, and will stay out for the remainder of
+      // this block
+      TBS_LOG("%p exiting slop\n", this);
+      mInSlop = false;
+    }
+  }
+  return mInSlop;
+}
+
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/apz/src/InputBlockState.h
+++ b/gfx/layers/apz/src/InputBlockState.h
@@ -425,28 +425,40 @@ public:
   /**
    * @return false iff touch-action is enabled and the allowed touch behaviors for
    *         the first touch point do not allow panning in the specified direction(s).
    */
   bool TouchActionAllowsPanningX() const;
   bool TouchActionAllowsPanningY() const;
   bool TouchActionAllowsPanningXY() const;
 
+  /**
+   * Notifies the input block of an incoming touch event so that the block can
+   * update its internal slop state. "Slop" refers to the area around the
+   * initial touchstart where we drop touchmove events so that content doesn't
+   * see them.
+   * @return true iff the provided event is a touchmove in the slop area and
+   *         so should not be sent to content.
+   */
+  bool UpdateSlopState(const MultiTouchInput& aInput);
+
   bool HasEvents() const override;
   void DropEvents() override;
   void HandleEvents() override;
   void DispatchEvent(const InputData& aEvent) const override;
   bool MustStayActive() override;
   const char* Type() override;
 
 private:
   nsTArray<TouchBehaviorFlags> mAllowedTouchBehaviors;
   bool mAllowedTouchBehaviorSet;
   bool mDuringFastFling;
   bool mSingleTapOccurred;
+  bool mInSlop;
+  ScreenIntPoint mSlopOrigin;
   nsTArray<MultiTouchInput> mEvents;
   // A reference to the InputQueue's touch counter
   TouchCounter& mTouchCounter;
 };
 
 } // namespace layers
 } // namespace mozilla
 
--- a/gfx/layers/apz/src/InputQueue.cpp
+++ b/gfx/layers/apz/src/InputQueue.cpp
@@ -155,17 +155,22 @@ InputQueue::ReceiveTouchInput(const RefP
 
   // XXX calling ArePointerEventsConsumable on |target| may be wrong here if
   // the target isn't confirmed and the real target turns out to be something
   // else. For now assume this is rare enough that it's not an issue.
   if (block->IsDuringFastFling()) {
     INPQ_LOG("dropping event due to block %p being in fast motion\n", block);
     result = nsEventStatus_eConsumeNoDefault;
   } else if (target && target->ArePointerEventsConsumable(block, aEvent.AsMultiTouchInput().mTouches.Length())) {
-    result = nsEventStatus_eConsumeDoDefault;
+    if (block->UpdateSlopState(aEvent.AsMultiTouchInput())) {
+      INPQ_LOG("dropping event due to block %p being in slop\n", block);
+      result = nsEventStatus_eConsumeNoDefault;
+    } else {
+      result = nsEventStatus_eConsumeDoDefault;
+    }
   }
   if (!MaybeHandleCurrentBlock(block, aEvent)) {
     block->AddEvent(aEvent.AsMultiTouchInput());
   }
   return result;
 }
 
 nsEventStatus
--- a/gfx/layers/apz/test/mochitest/test_bug1151667.html
+++ b/gfx/layers/apz/test/mochitest/test_bug1151667.html
@@ -1,18 +1,20 @@
 <!DOCTYPE HTML>
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=1151667
 -->
 <head>
   <title>Test for Bug 1151667</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
   <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+  <script type="application/javascript" src="apz_test_utils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <style>
   #subframe {
     margin-top: 100px;
     height: 500px;
     width: 500px;
     overflow: scroll;
   }
@@ -50,14 +52,18 @@ function startTest() {
 function continueTest() {
   var subframe = document.getElementById('subframe');
   is(subframe.scrollTop > 0, true, "We should have scrolled the subframe down");
   is(document.documentElement.scrollTop, 0, "We should not have scrolled the page");
   SimpleTest.finish();
 }
 
 SimpleTest.waitForExplicitFinish();
-SimpleTest.waitForFocus(startTest, window);
+SimpleTest.waitForFocus(function() {
+  waitForAllPaints(function() {
+    flushApzRepaints(startTest);
+  })
+}, window);
 
 </script>
 </pre>
 </body>
 </html>
--- a/gfx/layers/apz/test/mochitest/test_layerization.html
+++ b/gfx/layers/apz/test/mochitest/test_layerization.html
@@ -1,17 +1,18 @@
 <!DOCTYPE HTML>
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=1173580
 -->
 <head>
   <title>Test for layerization</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
   <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
   <script type="application/javascript" src="apz_test_utils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <link rel="stylesheet" type="text/css" href="helper_subframe_style.css"/>
   <style>
   #container {
     display: flex;
     overflow: scroll;
@@ -141,17 +142,19 @@ function startTest() {
   // This test requires APZ - if it's not enabled, skip it.
   var apzEnabled = SpecialPowers.getDOMWindowUtils(window).asyncPanZoomEnabled;
   if (!apzEnabled) {
     ok(true, "APZ not enabled, skipping test");
     SimpleTest.finish();
     return;
   }
 
-  driveTest();
+  waitForAllPaints(function() {
+    flushApzRepaints(driveTest);
+  })
 }
 
 SimpleTest.waitForExplicitFinish();
 SimpleTest.testInChaosMode();
 
 // Disable smooth scrolling, because it results in long-running scroll
 // animations that can result in a 'scroll' event triggered by an earlier
 // wheel event as corresponding to a later wheel event.
--- a/gfx/layers/apz/test/mochitest/test_wheel_transactions.html
+++ b/gfx/layers/apz/test/mochitest/test_wheel_transactions.html
@@ -79,17 +79,19 @@ function* runTest() {
   yield SpecialPowers.pushPrefEnv({"set": [["mousewheel.transaction.timeout", timeout]]}, driveTest);
   SimpleTest.requestFlakyTimeout("we are testing code that measures actual elapsed time between two events");
 
   // Scroll up a bit more. It's still |outer| scrolling because
   // |inner| is still scrolled all the way to the top.
   yield scrollWheelOver(outer, 10);
 
   // Wait for the transaction timeout to elapse.
-  yield window.setTimeout(driveTest, timeout);
+  // timeout * 5 is used to make it less likely that the timeout is less than
+  // the system timestamp resolution
+  yield window.setTimeout(driveTest, timeout * 5);
 
   // Now scroll down. The transaction having timed out, the event
   // should pick up a new target, and that should be |inner|.
   yield scrollWheelOver(outer, -10);
   ok(inner.scrollTop > 0, "'inner' should have been scrolled");
 
   // Finally, test scroll handoff after a timeout.
 
@@ -98,17 +100,19 @@ function* runTest() {
   while (inner.scrollTop < inner.scrollTopMax) {
     yield scrollWheelOver(outer, -10);
     // Avoid a failure getting us into an infinite loop.
     ok(inner.scrollTop > prevScrollTop, "scrolling down should increase scrollTop");
     prevScrollTop = inner.scrollTop;
   }
 
   // Wait for the transaction timeout to elapse.
-  yield window.setTimeout(driveTest, timeout);
+  // timeout * 5 is used to make it less likely that the timeout is less than
+  // the system timestamp resolution
+  yield window.setTimeout(driveTest, timeout * 5);
 
   // Continued downward scrolling should scroll |outer| to the bottom.
   prevScrollTop = outer.scrollTop;
   while (outer.scrollTop < outer.scrollTopMax) {
     yield scrollWheelOver(outer, -10);
     // Avoid a failure getting us into an infinite loop.
     ok(outer.scrollTop > prevScrollTop, "scrolling down should increase scrollTop");
     prevScrollTop = outer.scrollTop;
--- a/gfx/layers/apz/util/APZCCallbackHelper.cpp
+++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp
@@ -550,17 +550,18 @@ APZCCallbackHelper::FireSingleTapEvent(c
   DispatchSynthesizedMouseEvent(eMouseDown, time, aPoint, aModifiers, aWidget);
   DispatchSynthesizedMouseEvent(eMouseUp, time, aPoint, aModifiers, aWidget);
 }
 
 static nsIScrollableFrame*
 GetScrollableAncestorFrame(nsIFrame* aTarget)
 {
   uint32_t flags = nsLayoutUtils::SCROLLABLE_ALWAYS_MATCH_ROOT
-                 | nsLayoutUtils::SCROLLABLE_ONLY_ASYNC_SCROLLABLE;
+                 | nsLayoutUtils::SCROLLABLE_ONLY_ASYNC_SCROLLABLE
+                 | nsLayoutUtils::SCROLLABLE_FIXEDPOS_FINDS_ROOT;
   return nsLayoutUtils::GetNearestScrollableFrame(aTarget, flags);
 }
 
 static dom::Element*
 GetDisplayportElementFor(nsIScrollableFrame* aScrollableFrame)
 {
   if (!aScrollableFrame) {
     return nullptr;
--- a/gfx/src/nsITheme.h
+++ b/gfx/src/nsITheme.h
@@ -10,17 +10,16 @@
 #define nsITheme_h_
 
 #include "nsISupports.h"
 #include "nsCOMPtr.h"
 #include "nsColor.h"
 #include "Units.h"
 
 struct nsRect;
-struct nsIntMargin;
 class nsPresContext;
 class nsRenderingContext;
 class nsDeviceContext;
 class nsIFrame;
 class nsIAtom;
 class nsIWidget;
 
 // IID for the nsITheme interface
--- a/gfx/src/nsMargin.h
+++ b/gfx/src/nsMargin.h
@@ -4,30 +4,23 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef NSMARGIN_H
 #define NSMARGIN_H
 
 #include "nsCoord.h"
 #include "nsPoint.h"
 #include "mozilla/gfx/BaseMargin.h"
+#include "mozilla/gfx/Rect.h"
 
 struct nsMargin : public mozilla::gfx::BaseMargin<nscoord, nsMargin> {
   typedef mozilla::gfx::BaseMargin<nscoord, nsMargin> Super;
 
   // Constructors
   nsMargin() : Super() {}
   nsMargin(const nsMargin& aMargin) : Super(aMargin) {}
   nsMargin(nscoord aTop, nscoord aRight, nscoord aBottom, nscoord aLeft)
     : Super(aTop, aRight, aBottom, aLeft) {}
 };
 
-struct nsIntMargin : public mozilla::gfx::BaseMargin<int32_t, nsIntMargin> {
-  typedef mozilla::gfx::BaseMargin<int32_t, nsIntMargin> Super;
-
-  // Constructors
-  nsIntMargin() : Super() {}
-  nsIntMargin(const nsIntMargin& aMargin) : Super(aMargin) {}
-  nsIntMargin(int32_t aTop, int32_t aRight, int32_t aBottom, int32_t aLeft)
-    : Super(aTop, aRight, aBottom, aLeft) {}
-};
+typedef mozilla::gfx::IntMargin nsIntMargin;
 
 #endif /* NSMARGIN_H */
--- a/gfx/src/nsRect.h
+++ b/gfx/src/nsRect.h
@@ -10,22 +10,20 @@
 #include <stdio.h>                      // for FILE
 #include <stdint.h>                     // for int32_t, int64_t
 #include <algorithm>                    // for min/max
 #include "mozilla/Likely.h"             // for MOZ_UNLIKELY
 #include "mozilla/gfx/Rect.h"
 #include "nsCoord.h"                    // for nscoord, etc
 #include "nsISupportsImpl.h"            // for MOZ_COUNT_CTOR, etc
 #include "nsPoint.h"                    // for nsIntPoint, nsPoint
+#include "nsMargin.h"                   // for nsIntMargin, nsMargin
 #include "nsSize.h"                     // for IntSize, nsSize
 #include "nscore.h"                     // for NS_BUILD_REFCNT_LOGGING
 
-struct nsMargin;
-struct nsIntMargin;
-
 typedef mozilla::gfx::IntRect nsIntRect;
 
 struct nsRect :
   public mozilla::gfx::BaseRect<nscoord, nsRect, nsPoint, nsSize, nsMargin> {
   typedef mozilla::gfx::BaseRect<nscoord, nsRect, nsPoint, nsSize, nsMargin> Super;
 
   static void VERIFY_COORD(nscoord aValue) { ::VERIFY_COORD(aValue); }
 
--- a/gfx/thebes/gfx2DGlue.h
+++ b/gfx/thebes/gfx2DGlue.h
@@ -46,21 +46,16 @@ inline gfxMatrix ThebesMatrix(const Matr
                    aMatrix._22, aMatrix._31, aMatrix._32);
 }
 
 inline Point ToPoint(const gfxPoint &aPoint)
 {
   return Point(Float(aPoint.x), Float(aPoint.y));
 }
 
-inline IntMargin ToIntMargin(const nsIntMargin& aMargin)
-{
-  return IntMargin(aMargin.top, aMargin.right, aMargin.bottom, aMargin.left);
-}
-
 inline Size ToSize(const gfxSize &aSize)
 {
   return Size(Float(aSize.width), Float(aSize.height));
 }
 
 inline gfxPoint ThebesPoint(const Point &aPoint)
 {
   return gfxPoint(aPoint.x, aPoint.y);
--- a/js/public/UbiNode.h
+++ b/js/public/UbiNode.h
@@ -634,16 +634,22 @@ class Base {
     // of the out parameter. True is returned on success, false is returned on
     // OOM.
     virtual bool jsObjectConstructorName(JSContext* cx,
                                          UniquePtr<char16_t[], JS::FreePolicy>& outName) const {
         outName.reset(nullptr);
         return true;
     }
 
+    // Methods for CoarseType::Script referents
+
+    // Return the script's source's filename if available. If unavailable,
+    // return nullptr.
+    virtual const char* scriptFilename() const { return nullptr; }
+
   private:
     Base(const Base& rhs) = delete;
     Base& operator=(const Base& rhs) = delete;
 };
 
 // A traits template with a specialization for each referent type that
 // ubi::Node supports. The specialization must be the concrete subclass of
 // Base that represents a pointer to the referent type. It must also
@@ -774,16 +780,18 @@ class Node {
     JS::Zone* zone()                const { return base()->zone(); }
     JSCompartment* compartment()    const { return base()->compartment(); }
     const char* jsObjectClassName() const { return base()->jsObjectClassName(); }
     bool jsObjectConstructorName(JSContext* cx,
                                  UniquePtr<char16_t[], JS::FreePolicy>& outName) const {
         return base()->jsObjectConstructorName(cx, outName);
     }
 
+    const char* scriptFilename() const { return base()->scriptFilename(); }
+
     using Size = Base::Size;
     Size size(mozilla::MallocSizeOf mallocSizeof) const {
         return base()->size(mallocSizeof);
     }
 
     UniquePtr<EdgeRange> edges(JSRuntime* rt, bool wantNames = true) const {
         return base()->edges(rt, wantNames);
     }
@@ -1047,16 +1055,17 @@ struct Concrete<JS::Symbol> : TracerConc
     static void construct(void* storage, JS::Symbol* ptr) {
         new (storage) Concrete(ptr);
     }
 };
 
 template<> struct Concrete<JSScript> : TracerConcreteWithCompartment<JSScript> {
     CoarseType coarseType() const final { return CoarseType::Script; }
     Size size(mozilla::MallocSizeOf mallocSizeOf) const override;
+    const char* scriptFilename() const final;
 
   protected:
     explicit Concrete(JSScript *ptr) : TracerConcreteWithCompartment<JSScript>(ptr) { }
 
   public:
     static void construct(void *storage, JSScript *ptr) { new (storage) Concrete(ptr); }
 };
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-takeCensus-11.js
@@ -0,0 +1,44 @@
+// Check byte counts produced by takeCensus.
+
+const g = newGlobal();
+g.eval("setLazyParsingDisabled(true)");
+
+const dbg = new Debugger(g);
+
+g.evaluate("function one() {}", { fileName: "one.js" });
+g.evaluate(`function two1() {}
+            function two2() {}`,
+           { fileName: "two.js" });
+g.evaluate(`function three1() {}
+            function three2() {}
+            function three3() {}`,
+           { fileName: "three.js" });
+
+const report = dbg.memory.takeCensus({
+  breakdown: {
+    by: "coarseType",
+    scripts: {
+      by: "filename",
+      then: { by: "count", count: true, bytes: false },
+      noFilename: {
+        by: "internalType",
+        then: { by: "count", count: true, bytes: false }
+      }
+    },
+
+    // Not really interested in these, but they're here for completeness.
+    objects: { by: "count", count: true, byte: false },
+    strings: { by: "count", count: true, byte: false },
+    other:   { by: "count", count: true, byte: false },
+  }
+});
+
+print(JSON.stringify(report, null, 4));
+
+assertEq(report.scripts["one.js"].count, 1);
+assertEq(report.scripts["two.js"].count, 2);
+assertEq(report.scripts["three.js"].count, 3);
+
+const noFilename = report.scripts.noFilename;
+assertEq(!!noFilename, true);
+assertEq(typeof noFilename, "object");
--- a/js/src/jit/IonCode.h
+++ b/js/src/jit/IonCode.h
@@ -777,16 +777,17 @@ IsMarked(const jit::VMFunction*)
 
 // JS::ubi::Nodes can point to js::jit::JitCode instances; they're js::gc::Cell
 // instances with no associated compartment.
 namespace JS {
 namespace ubi {
 template<>
 struct Concrete<js::jit::JitCode> : TracerConcrete<js::jit::JitCode> {
     CoarseType coarseType() const final { return CoarseType::Script; }
+
     Size size(mozilla::MallocSizeOf mallocSizeOf) const override {
         Size size = js::gc::Arena::thingSize(get().asTenured().getAllocKind());
         size += get().bufferSize();
         size += get().headerSize();
         return size;
     }
 
   protected:
--- a/js/src/jit/arm/Assembler-arm.h
+++ b/js/src/jit/arm/Assembler-arm.h
@@ -642,19 +642,23 @@ class Imm8 : public Operand2
 {
   public:
     explicit Imm8(uint32_t imm)
       : Operand2(EncodeImm(imm))
     { }
 
   public:
     static datastore::Imm8mData EncodeImm(uint32_t imm) {
+        // RotateLeft below may not be called with a shift of zero.
+        if (imm <= 0xFF)
+            return datastore::Imm8mData(imm, 0);
+
         // An encodable integer has a maximum of 8 contiguous set bits,
         // with an optional wrapped left rotation to even bit positions.
-        for (int rot = 0; rot < 16; rot++) {
+        for (int rot = 1; rot < 16; rot++) {
             uint32_t rotimm = mozilla::RotateLeft(imm, rot*2);
             if (rotimm <= 0xFF)
                 return datastore::Imm8mData(rotimm, rot);
         }
         return datastore::Imm8mData();
     }
 
     // Pair template?
--- a/js/src/jit/mips64/Assembler-mips64.cpp
+++ b/js/src/jit/mips64/Assembler-mips64.cpp
@@ -276,28 +276,31 @@ Assembler::bind(InstImm* inst, uintptr_t
         addLongJump(BufferOffset(branch));
         Assembler::WriteLoad64Instructions(inst, ScratchRegister, target);
         inst[4] = InstReg(op_special, ScratchRegister, zero, ra, ff_jalr).encode();
         // There is 1 nop after this.
         return;
     }
 
     if (BOffImm16::IsInRange(offset)) {
-        bool conditional = (inst[0].encode() != inst_bgezal.encode() &&
-                            inst[0].encode() != inst_beq.encode());
-
         inst[0].setBOffImm16(BOffImm16(offset));
         inst[1].makeNop();
 
+        // Don't skip trailing nops can imporve performance
+        // on Loongson3 platform.
+#ifndef _MIPS_ARCH_LOONGSON3A
+        bool conditional = (inst[0].encode() != inst_bgezal.encode() &&
+                            inst[0].encode() != inst_beq.encode());
+
         // Skip the trailing nops in conditional branches.
-        // FIXME: On Loongson3 platform, the branch degrade performance.
-        if (0 && conditional) {
+        if (conditional) {
             inst[2] = InstImm(op_regimm, zero, rt_bgez, BOffImm16(5 * sizeof(uint32_t))).encode();
             // There are 4 nops after this
         }
+#endif
         return;
     }
 
     if (inst[0].encode() == inst_beq.encode()) {
         // Handle long unconditional jump.
         addLongJump(BufferOffset(branch));
         Assembler::WriteLoad64Instructions(inst, ScratchRegister, target);
         inst[4] = InstReg(op_special, ScratchRegister, zero, zero, ff_jr).encode();
--- a/js/src/jsapi-tests/testUbiNode.cpp
+++ b/js/src/jsapi-tests/testUbiNode.cpp
@@ -341,8 +341,42 @@ BEGIN_TEST(test_ubiPostOrder)
     CHECK(visited[4] == 'c');
     CHECK(visited[5] == 'b');
     CHECK(visited[6] == 'a');
     CHECK(visited[7] == 'r');
 
     return true;
 }
 END_TEST(test_ubiPostOrder)
+
+BEGIN_TEST(test_JS_ubi_Node_scriptFilename)
+{
+    JS::RootedValue val(cx);
+    CHECK(evaluate("(function one() {                      \n"  // 1
+                   "  return (function two() {             \n"  // 2
+                   "    return (function three() {         \n"  // 3
+                   "      return function four() {};       \n"  // 4
+                   "    }());                              \n"  // 5
+                   "  }());                                \n"  // 6
+                   "}());                                  \n", // 7
+                   "my-cool-filename.js",
+                   1,
+                   &val));
+
+    CHECK(val.isObject());
+    JS::RootedObject obj(cx, &val.toObject());
+
+    CHECK(obj->is<JSFunction>());
+    JS::RootedFunction func(cx, &obj->as<JSFunction>());
+
+    JS::RootedScript script(cx, func->getOrCreateScript(cx));
+    CHECK(script);
+    CHECK(script->filename());
+
+    JS::ubi::Node node(script);
+    const char* filename = node.scriptFilename();
+    CHECK(filename);
+    CHECK(strcmp(filename, script->filename()) == 0);
+    CHECK(strcmp(filename, "my-cool-filename.js") == 0);
+
+    return true;
+}
+END_TEST(test_JS_ubi_Node_scriptFilename)
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -6165,32 +6165,16 @@ GCRuntime::budgetIncrementalGC(SliceBudg
     }
 
     if (reset)
         resetIncrementalGC("zone change");
 }
 
 namespace {
 
-class AutoDisableStoreBuffer
-{
-    StoreBuffer& sb;
-    bool prior;
-
-  public:
-    explicit AutoDisableStoreBuffer(GCRuntime* gc) : sb(gc->storeBuffer) {
-        prior = sb.isEnabled();
-        sb.disable();
-    }
-    ~AutoDisableStoreBuffer() {
-        if (prior)
-            sb.enable();
-    }
-};
-
 class AutoScheduleZonesForGC
 {
     JSRuntime* rt_;
 
   public:
     explicit AutoScheduleZonesForGC(JSRuntime* rt) : rt_(rt) {
         for (ZonesIter zone(rt_, WithAtoms); !zone.done(); zone.next()) {
             if (rt->gc.gcMode() == JSGC_MODE_GLOBAL)
@@ -6228,22 +6212,16 @@ class AutoScheduleZonesForGC
  */
 MOZ_NEVER_INLINE bool
 GCRuntime::gcCycle(bool nonincrementalByAPI, SliceBudget& budget, JS::gcreason::Reason reason)
 {
     AutoNotifyGCActivity notify(*this);
 
     evictNursery(reason);
 
-    /*
-     * Marking can trigger many incidental post barriers, some of them for
-     * objects which are not going to be live after the GC.
-     */
-    AutoDisableStoreBuffer adsb(this);
-
     AutoTraceSession session(rt, JS::HeapState::MajorCollecting);
 
     majorGCTriggerReason = JS::gcreason::NO_REASON;
     interFrameGC = true;
 
     number++;
     if (!isIncrementalGCInProgress())
         incMajorGcNumber();
@@ -6501,17 +6479,16 @@ GCRuntime::abortGC()
     MOZ_ASSERT(!rt->mainThread.suppressGC);
 
     AutoStopVerifyingBarriers av(rt, false);
 
     gcstats::AutoGCSlice agc(stats, scanZonesBeforeGC(), invocationKind,
                              SliceBudget::unlimited(), JS::gcreason::ABORT_GC);
 
     evictNursery(JS::gcreason::ABORT_GC);
-    AutoDisableStoreBuffer adsb(this);
     AutoTraceSession session(rt, JS::HeapState::MajorCollecting);
 
     number++;
     resetIncrementalGC("abort");
 }
 
 void
 GCRuntime::notifyDidPaint()
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -4401,15 +4401,35 @@ JS::ubi::Concrete<JSScript>::size(mozill
     size += baselineStubsSize;
 
     size += jit::SizeOfIonData(&get(), mallocSizeOf);
 
     MOZ_ASSERT(size > 0);
     return size;
 }
 
+const char*
+JS::ubi::Concrete<JSScript>::scriptFilename() const
+{
+    return get().filename();
+}
+
 JS::ubi::Node::Size
 JS::ubi::Concrete<js::LazyScript>::size(mozilla::MallocSizeOf mallocSizeOf) const
 {
     Size size = js::gc::Arena::thingSize(get().asTenured().getAllocKind());
     size += get().sizeOfExcludingThis(mallocSizeOf);
     return size;
 }
+
+const char*
+JS::ubi::Concrete<js::LazyScript>::scriptFilename() const
+{
+    auto sourceObject = get().sourceObject();
+    if (!sourceObject)
+        return nullptr;
+
+    auto source = sourceObject->source();
+    if (!source)
+        return nullptr;
+
+    return source->filename();
+}
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -2482,16 +2482,17 @@ CloneGlobalScript(JSContext* cx, Handle<
 // JS::ubi::Nodes can point to js::LazyScripts; they're js::gc::Cell instances
 // with no associated compartment.
 namespace JS {
 namespace ubi {
 template<>
 struct Concrete<js::LazyScript> : TracerConcrete<js::LazyScript> {