Merge m-c to b2g-inbound a=merge
authorWes Kocher <wkocher@mozilla.com>
Thu, 15 Jan 2015 17:57:43 -0800
changeset 224184 95004d80cbf9bc18f08c357d0b3696917ba77856
parent 224183 1efa20405cf63bb852aebc395f41e9f3375ad837 (current diff)
parent 224120 cac6192956abb79c9434e89c0107bdc76b8b087e (diff)
child 224185 6377fa2506ab4fbcd0ae0541f6b5cfb4cc037e9f
push id54150
push usercbook@mozilla.com
push dateFri, 16 Jan 2015 14:14:56 +0000
treeherdermozilla-inbound@ac6623427298 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone38.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to b2g-inbound a=merge
testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_mimeheader_03.html
testing/web-platform/tests/workers/semantics/interface-objects/002.html
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -87,17 +87,17 @@ pref("network.cookie.cookieBehavior", 0)
 pref("network.http.spdy.enabled.http2draft", false);
 pref("network.http.spdy.push-allowance", 32768);
 
 // See bug 545869 for details on why these are set the way they are
 pref("network.buffer.cache.count", 24);
 pref("network.buffer.cache.size",  16384);
 
 // predictive actions
-pref("network.predictor.enable", false); // disabled on b2g
+pref("network.predictor.enabled", false); // disabled on b2g
 pref("network.predictor.max-db-size", 2097152); // bytes
 pref("network.predictor.preserve", 50); // percentage of predictor data to keep when cleaning up
 
 /* session history */
 pref("browser.sessionhistory.max_total_viewers", 1);
 pref("browser.sessionhistory.max_entries", 50);
 pref("browser.sessionhistory.contentViewerTimeout", 360);
 
@@ -1063,16 +1063,19 @@ pref("identity.fxaccounts.enabled", true
 // Mobile Identity API.
 pref("services.mobileid.server.uri", "https://msisdn.services.mozilla.com");
 
 // Enable mapped array buffer.
 #ifndef XP_WIN
 pref("dom.mapped_arraybuffer.enabled", true);
 #endif
 
+// BroadcastChannel API
+pref("dom.broadcastChannel.enabled", true);
+
 // UDPSocket API
 pref("dom.udpsocket.enabled", true);
 
 // Enable TV Manager API
 pref("dom.tv.enabled", true);
 
 pref("dom.mozSettings.SettingsDB.debug.enabled", true);
 pref("dom.mozSettings.SettingsManager.debug.enabled", true);
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2557,26 +2557,26 @@ let gMenuButtonUpdateBadge = {
  * or from about:newtab or from remote error pages that invoke
  * us via async messaging.
  */
 let BrowserOnClick = {
   init: function () {
     let mm = window.messageManager;
     mm.addMessageListener("Browser:CertExceptionError", this);
     mm.addMessageListener("Browser:SiteBlockedError", this);
-    mm.addMessageListener("Browser:NetworkError", this);
+    mm.addMessageListener("Browser:EnableOnlineMode", this);
     mm.addMessageListener("Browser:SendSSLErrorReport", this);
     mm.addMessageListener("Browser:SetSSLErrorReportAuto", this);
   },
 
   uninit: function () {
     let mm = window.messageManager;
     mm.removeMessageListener("Browser:CertExceptionError", this);
     mm.removeMessageListener("Browser:SiteBlockedError", this);
-    mm.removeMessageListener("Browser:NetworkError", this);
+    mm.removeMessageListener("Browser:EnableOnlineMode", this);
     mm.removeMessageListener("Browser:SendSSLErrorReport", this);
     mm.removeMessageListener("Browser:SetSSLErrorReportAuto", this);
   },
 
   handleEvent: function (event) {
     if (!event.isTrusted || // Don't trust synthetic events
         event.button == 2) {
       return;
@@ -2600,19 +2600,22 @@ let BrowserOnClick = {
         this.onAboutCertError(msg.target, msg.data.elementId,
                               msg.data.isTopFrame, msg.data.location,
                               msg.data.sslStatusAsString);
       break;
       case "Browser:SiteBlockedError":
         this.onAboutBlocked(msg.data.elementId, msg.data.isMalware,
                             msg.data.isTopFrame, msg.data.location);
       break;
-      case "Browser:NetworkError":
-        // Reset network state, the error page will refresh on its own.
-        Services.io.offline = false;
+      case "Browser:EnableOnlineMode":
+        if (Services.io.offline) {
+          // Reset network state and refresh the page.
+          Services.io.offline = false;
+          msg.target.reload();
+        }
       break;
       case "Browser:SendSSLErrorReport":
         this.onSSLErrorReport(msg.target, msg.data.elementId,
                               msg.data.documentURI,
                               msg.data.location,
                               msg.data.securityInfo);
       break;
       case "Browser:SetSSLErrorReportAuto":
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -563,17 +563,18 @@ let ClickEventHandler = {
     // Handle click events from about pages
     if (ownerDoc.documentURI.startsWith("about:certerror")) {
       this.onAboutCertError(originalTarget, ownerDoc);
       return;
     } else if (ownerDoc.documentURI.startsWith("about:blocked")) {
       this.onAboutBlocked(originalTarget, ownerDoc);
       return;
     } else if (ownerDoc.documentURI.startsWith("about:neterror")) {
-      this.onAboutNetError(originalTarget, ownerDoc);
+      this.onAboutNetError(event, ownerDoc.documentURI);
+      return;
     }
 
     let [href, node] = this._hrefAndLinkNodeForClickEvent(event);
 
     let json = { button: event.button, shiftKey: event.shiftKey,
                  ctrlKey: event.ctrlKey, metaKey: event.metaKey,
                  altKey: event.altKey, href: null, title: null,
                  bookmark: false };
@@ -630,22 +631,28 @@ let ClickEventHandler = {
     sendAsyncMessage("Browser:SiteBlockedError", {
       location: ownerDoc.location.href,
       isMalware: /e=malwareBlocked/.test(ownerDoc.documentURI),
       elementId: targetElement.getAttribute("id"),
       isTopFrame: (ownerDoc.defaultView.parent === ownerDoc.defaultView)
     });
   },
 
-  onAboutNetError: function (targetElement, ownerDoc) {
-    let elmId = targetElement.getAttribute("id");
-    if (elmId != "errorTryAgain" || !/e=netOffline/.test(ownerDoc.documentURI)) {
+  onAboutNetError: function (event, documentURI) {
+    let elmId = event.originalTarget.getAttribute("id");
+    if (elmId != "errorTryAgain" || !/e=netOffline/.test(documentURI)) {
       return;
     }
-    sendSyncMessage("Browser:NetworkError", {});
+    // browser front end will handle clearing offline mode and refreshing
+    // the page *if* we're in offline mode now. Otherwise let the error page
+    // handle the click.
+    if (Services.io.offline) {
+      event.preventDefault();
+      sendAsyncMessage("Browser:EnableOnlineMode", {});
+    }
   },
 
   /**
    * Extracts linkNode and href for the current click target.
    *
    * @param event
    *        The click event.
    * @return [href, linkNode].
--- a/browser/base/content/test/general/browser_bug435325.js
+++ b/browser/base/content/test/general/browser_bug435325.js
@@ -51,22 +51,22 @@ function checkPage() {
   // Now press the "Try Again" button
   ok(gBrowser.contentDocument.getElementById("errorTryAgain"),
     "The error page has got a #errorTryAgain element");
 
   // Re-enable the proxy so example.com is resolved to localhost, rather than
   // the actual example.com.
   Services.prefs.setIntPref("network.proxy.type", proxyPrefValue);
 
+  Services.obs.addObserver(function observer(aSubject, aTopic) {
+    ok(!Services.io.offline, "After clicking the Try Again button, we're back " +
+                             "online.");
+    finish();
+  }, "network:offline-status-changed", false);
   gBrowser.contentDocument.getElementById("errorTryAgain").click();
-
-  ok(!Services.io.offline, "After clicking the Try Again button, we're back " +
-                           "online.");
-
-  finish();
 }
 
 registerCleanupFunction(function() {
   Services.prefs.setBoolPref("browser.cache.disk.enable", true);
   Services.prefs.setBoolPref("browser.cache.memory.enable", true);
   Services.io.offline = false;
   gBrowser.removeCurrentTab();
 });
--- a/browser/base/content/test/newtab/browser_newtab_enhanced.js
+++ b/browser/base/content/test/newtab/browser_newtab_enhanced.js
@@ -32,28 +32,28 @@ function runTests() {
       title: siteNode.querySelector(".newtab-title").textContent,
     };
   }
 
   // Make the page have a directory link followed by a history link
   yield setLinks("-1");
 
   // Test with enhanced = false
-  NewTabUtils.allPages.enhanced = false;
   yield addNewTabPageTab();
+  yield customizeNewTabPage("classic");
   let {type, enhanced, title} = getData(0);
   is(type, "organic", "directory link is organic");
   isnot(enhanced, "", "directory link has enhanced image");
   is(title, "title");
 
   is(getData(1), null, "history link pushed out by directory link");
 
   // Test with enhanced = true
-  NewTabUtils.allPages.enhanced = true;
   yield addNewTabPageTab();
+  yield customizeNewTabPage("enhanced");
   ({type, enhanced, title} = getData(0));
   is(type, "organic", "directory link is still organic");
   isnot(enhanced, "", "directory link still has enhanced image");
   is(title, "title");
 
   is(getData(1), null, "history link still pushed out by directory link");
 
   // Test with a pinned link
@@ -62,17 +62,20 @@ function runTests() {
   ({type, enhanced, title} = getData(0));
   is(type, "enhanced", "pinned history link is enhanced");
   isnot(enhanced, "", "pinned history link has enhanced image");
   is(title, "title");
 
   is(getData(1), null, "directory link pushed out by pinned history link");
 
   // Test pinned link with enhanced = false
-  NewTabUtils.allPages.enhanced = false;
   yield addNewTabPageTab();
+  yield customizeNewTabPage("classic");
   ({type, enhanced, title} = getData(0));
   isnot(type, "enhanced", "history link is not enhanced");
   is(enhanced, "", "history link has no enhanced image");
   is(title, "site#-1");
 
   is(getData(1), null, "directory link still pushed out by pinned history link");
+
+  ok(getContentDocument().getElementById("newtab-intro-what"),
+     "'What is this page?' link exists");
 }
--- a/browser/base/content/test/newtab/head.js
+++ b/browser/base/content/test/newtab/head.js
@@ -679,8 +679,39 @@ function whenSearchInitDone() {
   getContentWindow().addEventListener(eventName, function onEvent(event) {
     if (event.detail.type == "State") {
       getContentWindow().removeEventListener(eventName, onEvent);
       deferred.resolve();
     }
   });
   return deferred.promise;
 }
+
+/**
+ * Changes the newtab customization option and waits for the panel to open and close
+ *
+ * @param {string} aTheme
+ *        Can be any of("blank"|"classic"|"enhanced")
+ */
+function customizeNewTabPage(aTheme) {
+  let document = getContentDocument();
+  let panel = document.getElementById("newtab-customize-panel");
+  let customizeButton = document.getElementById("newtab-customize-button");
+
+  // Attache onShown the listener on panel
+  panel.addEventListener("popupshown", function onShown() {
+    panel.removeEventListener("popupshown", onShown);
+
+    // Get the element for the specific option and click on it,
+    // then trigger an escape to close the panel
+    document.getElementById("newtab-customize-" + aTheme).click();
+    executeSoon(() => { panel.hidePopup(); });
+  });
+
+  // Attache the listener for panel closing, this will resolve the promise
+  panel.addEventListener("popuphidden", function onHidden() {
+    panel.removeEventListener("popuphidden", onHidden);
+    executeSoon(TestRunner.next);
+  });
+
+  // Click on the customize button to display the panel
+  customizeButton.click();
+}
--- a/browser/devtools/animationinspector/animation-panel.js
+++ b/browser/devtools/animationinspector/animation-panel.js
@@ -188,47 +188,52 @@ PlayerWidget.prototype = {
     let titleEl = createNode({
       parent: this.el,
       attributes: {
         "class": "animation-title"
       }
     });
     let titleHTML = "";
 
-    // Name
+    // Name.
     if (state.name) {
-      // Css animations have names
+      // Css animations have names.
       titleHTML += L10N.getStr("player.animationNameLabel");
       titleHTML += "<strong>" + state.name + "</strong>";
     } else {
-      // Css transitions don't
+      // Css transitions don't.
       titleHTML += L10N.getStr("player.transitionNameLabel");
     }
 
-    // Duration and iteration count
+    // Duration, delay and iteration count.
     titleHTML += "<span class='meta-data'>";
     titleHTML += L10N.getStr("player.animationDurationLabel");
     titleHTML += "<strong>" + L10N.getFormatStr("player.timeLabel",
       this.getFormattedTime(state.duration)) + "</strong>";
+    if (state.delay) {
+      titleHTML += L10N.getStr("player.animationDelayLabel");
+      titleHTML += "<strong>" + L10N.getFormatStr("player.timeLabel",
+        this.getFormattedTime(state.delay)) + "</strong>";
+    }
     titleHTML += L10N.getStr("player.animationIterationCountLabel");
     let count = state.iterationCount || L10N.getStr("player.infiniteIterationCount");
     titleHTML += "<strong>" + count + "</strong>";
     titleHTML += "</span>"
 
     titleEl.innerHTML = titleHTML;
 
-    // Timeline widget
+    // Timeline widget.
     let timelineEl = createNode({
       parent: this.el,
       attributes: {
         "class": "timeline"
       }
     });
 
-    // Playback control buttons container
+    // Playback control buttons container.
     let playbackControlsEl = createNode({
       parent: timelineEl,
       attributes: {
         "class": "playback-controls"
       }
     });
 
     // Control buttons (when currentTime becomes settable, rewind and
@@ -236,73 +241,71 @@ PlayerWidget.prototype = {
     this.playPauseBtnEl = createNode({
       parent: playbackControlsEl,
       nodeType: "button",
       attributes: {
         "class": "toggle devtools-button"
       }
     });
 
-    // Sliders container
+    // Sliders container.
     let slidersContainerEl = createNode({
       parent: timelineEl,
       attributes: {
         "class": "sliders-container",
       }
     });
 
-    let max = state.duration; // Infinite iterations
+    let max = state.duration; // Infinite iterations.
     if (state.iterationCount) {
-      // Finite iterations
+      // Finite iterations.
       max = state.iterationCount * state.duration;
     }
 
     // For now, keyframes aren't exposed by the actor. So the only range <input>
     // displayed in the container is the currentTime. When keyframes are
     // available, one input per keyframe can be added here.
     this.currentTimeEl = createNode({
       nodeType: "input",
       parent: slidersContainerEl,
       attributes: {
         "type": "range",
         "class": "current-time",
         "min": "0",
         "max": max,
         "step": "10",
+        "value": "0",
         // The currentTime isn't settable yet, so disable the timeline slider
         "disabled": "true"
       }
     });
 
     // Time display
     this.timeDisplayEl = createNode({
       parent: timelineEl,
       attributes: {
         "class": "time-display"
       }
     });
     this.timeDisplayEl.textContent = L10N.getFormatStr("player.timeLabel",
-      this.getFormattedTime());
+      this.getFormattedTime(0));
 
     this.containerEl.appendChild(this.el);
   },
 
   /**
    * Format time as a string.
    * @param {Number} time Defaults to the player's currentTime.
    * @return {String} The formatted time, e.g. "10.55"
    */
-  getFormattedTime: function(time=this.player.state.currentTime) {
-    let str = time/1000 + "";
-    str = str.split(".");
-    if (str.length === 1) {
-      return str[0] + ".00";
-    } else {
-      return str[0] + "." + str[1].substring(0, 2);
-    }
+  getFormattedTime: function(time) {
+    return (time/1000).toLocaleString(undefined, {
+      minimumFractionDigits: 2,
+      maximumFractionDigits: 2
+    });
   },
 
   /**
    * Executed when the playPause button is clicked.
    * Note that tests may want to call this callback directly rather than
    * simulating a click on the button since it returns the promise returned by
    * play and paused.
    * @return {Promise}
@@ -385,16 +388,22 @@ PlayerWidget.prototype = {
   },
 
   /**
    * Display the time in the timeDisplayEl and in the currentTimeEl slider.
    */
   displayTime: function(time) {
     let state = this.player.state;
 
+    // If the animation is delayed, don't start displaying the time until the
+    // delay has passed.
+    if (state.delay) {
+      time = Math.max(0, time - state.delay);
+    }
+
     this.timeDisplayEl.textContent = L10N.getFormatStr("player.timeLabel",
       this.getFormattedTime(time));
     if (!state.iterationCount && time !== state.duration) {
       this.currentTimeEl.value = time % state.duration;
     } else {
       this.currentTimeEl.value = time;
     }
   },
--- a/browser/devtools/animationinspector/test/browser.ini
+++ b/browser/devtools/animationinspector/test/browser.ini
@@ -6,13 +6,15 @@ support-files =
   head.js
 
 [browser_animation_empty_on_invalid_nodes.js]
 [browser_animation_panel_exists.js]
 [browser_animation_participate_in_inspector_update.js]
 [browser_animation_play_pause_button.js]
 [browser_animation_playerFronts_are_refreshed.js]
 [browser_animation_playerWidgets_destroy.js]
+[browser_animation_playerWidgets_meta_data.js]
 [browser_animation_refresh_when_active.js]
 [browser_animation_same_nb_of_playerWidgets_and_playerFronts.js]
 [browser_animation_shows_player_on_valid_node.js]
 [browser_animation_timeline_animates.js]
-[browser_animation_ui_updates_when_animation_changes.js]
+[browser_animation_timeline_waits_for_delay.js]
+[browser_animation_ui_updates_when_animation_changes.js]
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/devtools/animationinspector/test/browser_animation_playerWidgets_meta_data.js
@@ -0,0 +1,49 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that player widgets show the right player meta-data (name, duration,
+// iteration count, delay).
+
+add_task(function*() {
+  yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
+  let {inspector, panel} = yield openAnimationInspector();
+
+  info("Select the simple animated node");
+  yield selectNode(".animated", inspector);
+
+  let titleEl = panel.playerWidgets[0].el.querySelector(".animation-title");
+  ok(titleEl,
+    "The player widget has a title element, where meta-data should be displayed");
+
+  let nameEl = titleEl.querySelector("strong");
+  ok(nameEl, "The first <strong> tag was retrieved, it should contain the name");
+  is(nameEl.textContent, "simple-animation", "The animation name is correct");
+
+  let metaDataEl = titleEl.querySelector(".meta-data");
+  ok(metaDataEl, "The meta-data element exists");
+
+  let metaDataEls = metaDataEl.querySelectorAll("strong");
+  is(metaDataEls.length, 2, "2 meta-data elements were found");
+  is(metaDataEls[0].textContent, "2.00s",
+    "The first meta-data is the duration, and is correct");
+
+  info("Select the node with the delayed animation");
+  yield selectNode(".delayed", inspector);
+
+  titleEl = panel.playerWidgets[0].el.querySelector(".animation-title");
+  nameEl = titleEl.querySelector("strong");
+  is(nameEl.textContent, "simple-animation", "The animation name is correct");
+
+  metaDataEls = titleEl.querySelectorAll(".meta-data strong");
+  is(metaDataEls.length, 3,
+    "3 meta-data elements were found for the delayed animation");
+  is(metaDataEls[0].textContent, "3.00s",
+    "The first meta-data is the duration, and is correct");
+  is(metaDataEls[1].textContent, "60.00s",
+    "The second meta-data is the delay, and is correct");
+  is(metaDataEls[2].textContent, "10",
+    "The third meta-data is the iteration count, and is correct");
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/animationinspector/test/browser_animation_timeline_waits_for_delay.js
@@ -0,0 +1,24 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the currentTime timeline doesn't move if the animation is currently
+// waiting for an animation-delay.
+
+add_task(function*() {
+  yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
+  let {inspector, panel} = yield openAnimationInspector();
+
+  info("Select the delayed animation node");
+  yield selectNode(".delayed", inspector);
+
+  let widget = panel.playerWidgets[0];
+
+  let timeline = widget.currentTimeEl;
+  is(timeline.value, 0, "The timeline is at 0 since the animation hasn't started");
+
+  let timeLabel = widget.timeDisplayEl;
+  is(timeLabel.textContent, "0.00s", "The current time is 0");
+});
--- a/browser/devtools/animationinspector/test/doc_simple_animation.html
+++ b/browser/devtools/animationinspector/test/doc_simple_animation.html
@@ -1,42 +1,50 @@
 <!DOCTYPE html>
 <html lang="en">
 <head>
   <meta charset="UTF-8">
   <style>
     .ball {
-      width: 100px;
-      height: 100px;
+      width: 80px;
+      height: 80px;
       border-radius: 50%;
       background: #f06;
 
       position: absolute;
     }
 
     .still {
-      top: 50px;
-      left: 50px;
+      top: 0;
+      left: 10px;
     }
 
     .animated {
-      top: 200px;
-      left: 200px;
+      top: 100px;
+      left: 10px;
 
       animation: simple-animation 2s infinite alternate;
     }
 
     .multi {
-      top: 100px;
-      left: 400px;
+      top: 200px;
+      left: 10px;
 
       animation: simple-animation 2s infinite alternate,
                  other-animation 5s infinite alternate;
     }
 
+    .delayed {
+      top: 300px;
+      left: 10px;
+      background: rebeccapurple;
+
+      animation: simple-animation 3s 60s 10;
+    }
+
     @keyframes simple-animation {
       100% {
         transform: translateX(300px);
       }
     }
 
     @keyframes other-animation {
       100% {
@@ -45,10 +53,11 @@
     }
   </style>
 </head>
 <body>
   <!-- Comment node -->
   <div class="ball still"></div>
   <div class="ball animated"></div>
   <div class="ball multi"></div>
+  <div class="ball delayed"></div>
 </body>
 </html>
--- a/browser/devtools/canvasdebugger/test/browser.ini
+++ b/browser/devtools/canvasdebugger/test/browser.ini
@@ -25,16 +25,17 @@ support-files =
 [browser_canvas-frontend-call-stack-01.js]
 [browser_canvas-frontend-call-stack-02.js]
 [browser_canvas-frontend-call-stack-03.js]
 [browser_canvas-frontend-clear.js]
 [browser_canvas-frontend-img-screenshots.js]
 [browser_canvas-frontend-img-thumbnails-01.js]
 [browser_canvas-frontend-img-thumbnails-02.js]
 [browser_canvas-frontend-open.js]
+skip-if = e10s # bug 1102301 - leaks while running as a standalone directory in e10s mode
 [browser_canvas-frontend-record-01.js]
 [browser_canvas-frontend-record-02.js]
 [browser_canvas-frontend-record-03.js]
 [browser_canvas-frontend-reload-01.js]
 [browser_canvas-frontend-reload-02.js]
 [browser_canvas-frontend-slider-01.js]
 [browser_canvas-frontend-slider-02.js]
 [browser_canvas-frontend-snapshot-select.js]
--- a/browser/devtools/debugger/test/browser.ini
+++ b/browser/devtools/debugger/test/browser.ini
@@ -113,17 +113,17 @@ skip-if = e10s # TODO
 skip-if = e10s && debug
 [browser_dbg_addon-console.js]
 skip-if = e10s && debug || os == 'win' # bug 1005274
 [browser_dbg_auto-pretty-print-01.js]
 skip-if = e10s && debug
 [browser_dbg_auto-pretty-print-02.js]
 skip-if = e10s && debug
 [browser_dbg_bfcache.js]
-skip-if = e10s # TODO
+skip-if = e10s || true # bug 1113935
 [browser_dbg_blackboxing-01.js]
 skip-if = e10s && debug
 [browser_dbg_blackboxing-02.js]
 skip-if = e10s && debug
 [browser_dbg_blackboxing-03.js]
 skip-if = e10s && debug
 [browser_dbg_blackboxing-04.js]
 skip-if = e10s && debug
--- a/browser/devtools/debugger/test/browser_dbg_bfcache.js
+++ b/browser/devtools/debugger/test/browser_dbg_bfcache.js
@@ -7,69 +7,66 @@
  */
 
 const TAB_URL_1 = EXAMPLE_URL + "doc_script-switching-01.html";
 const TAB_URL_2 = EXAMPLE_URL + "doc_recursion-stack.html";
 
 let gTab, gDebuggee, gPanel, gDebugger;
 let gSources;
 
-function test() {
-  initDebugger(TAB_URL_1).then(([aTab, aDebuggee, aPanel]) => {
-    gTab = aTab;
-    gDebuggee = aDebuggee;
-    gPanel = aPanel;
-    gDebugger = gPanel.panelWin;
-    gSources = gDebugger.DebuggerView.Sources;
+const test = Task.async(function* () {
+  info("Starting browser_dbg_bfcache.js's `test`.");
+
+  ([gTab, gDebuggee, gPanel]) = yield initDebugger(TAB_URL_1);
+  gDebugger = gPanel.panelWin;
+  gSources = gDebugger.DebuggerView.Sources;
 
-    testFirstPage()
-      .then(testLocationChange)
-      .then(testBack)
-      .then(testForward)
-      .then(() => closeDebuggerAndFinish(gPanel))
-      .then(null, aError => {
-        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
-      });
-  });
-}
+  yield testFirstPage();
+  yield testLocationChange();
+  yield testBack();
+  yield testForward();
+  return closeDebuggerAndFinish(gPanel);
+});
 
 function testFirstPage() {
   info("Testing first page.");
 
   // Spin the event loop before causing the debuggee to pause, to allow
   // this function to return first.
   executeSoon(() => gDebuggee.firstCall());
 
-  return waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1).then(() => {
-    validateFirstPage();
-  });
+  return waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
+    .then(validateFirstPage);
 }
 
 function testLocationChange() {
   info("Navigating to a different page.");
 
-  return navigateActiveTabTo(gPanel, TAB_URL_2, gDebugger.EVENTS.SOURCES_ADDED).then(() => {
-    validateSecondPage();
-  });
+  return navigateActiveTabTo(gPanel,
+                             TAB_URL_2,
+                             gDebugger.EVENTS.SOURCES_ADDED)
+    .then(validateSecondPage);
 }
 
 function testBack() {
   info("Going back.");
 
-  return navigateActiveTabInHistory(gPanel, "back", gDebugger.EVENTS.SOURCES_ADDED).then(() => {
-    validateFirstPage();
-  });
+  return navigateActiveTabInHistory(gPanel,
+                                    "back",
+                                    gDebugger.EVENTS.SOURCES_ADDED)
+    .then(validateFirstPage);
 }
 
 function testForward() {
   info("Going forward.");
 
-  return navigateActiveTabInHistory(gPanel, "forward", gDebugger.EVENTS.SOURCES_ADDED).then(() => {
-    validateSecondPage();
-  });
+  return navigateActiveTabInHistory(gPanel,
+                                    "forward",
+                                    gDebugger.EVENTS.SOURCES_ADDED)
+    .then(validateSecondPage);
 }
 
 function validateFirstPage() {
   is(gSources.itemCount, 2,
     "Found the expected number of sources.");
   ok(gSources.getItemForAttachment(e => e.label == "code_script-switching-01.js"),
     "Found the first source label.");
   ok(gSources.getItemForAttachment(e => e.label == "code_script-switching-02.js"),
--- a/browser/devtools/framework/sidebar.js
+++ b/browser/devtools/framework/sidebar.js
@@ -1,289 +1,542 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=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/. */
 
 const {Cu} = require("chrome");
 
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 
 var {Promise: promise} = require("resource://gre/modules/Promise.jsm");
 var EventEmitter = require("devtools/toolkit/event-emitter");
 var Telemetry = require("devtools/shared/telemetry");
 
 const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 /**
  * ToolSidebar provides methods to register tabs in the sidebar.
  * It's assumed that the sidebar contains a xul:tabbox.
+ * Typically, you'll want the tabbox parameter to be a XUL tabbox like this:
+ *
+ * <tabbox id="inspector-sidebar" handleCtrlTab="false" class="devtools-sidebar-tabs">
+ *   <tabs/>
+ *   <tabpanels flex="1"/>
+ * </tabbox>
+ *
+ * The ToolSidebar API has a method to add new tabs, so the tabs and tabpanels
+ * nodes can be empty. But they can also already contain items before the
+ * ToolSidebar is created.
+ *
+ * Tabs added through the addTab method are only identified by an ID and a URL
+ * which is used as the href of an iframe node that is inserted in the newly
+ * created tabpanel.
+ * Tabs already present before the ToolSidebar is created may contain anything.
+ * However, these tabs must have ID attributes if it is required for the various
+ * methods that accept an ID as argument to work here.
  *
  * @param {Node} tabbox
  *  <tabbox> node;
  * @param {ToolPanel} panel
  *  Related ToolPanel instance;
  * @param {String} uid
  *  Unique ID
- * @param {Boolean} showTabstripe
- *  Show the tabs.
+ * @param {Object} options
+ *  - hideTabstripe: Should the tabs be hidden. Defaults to false
+ *  - showAllTabsMenu: Should a drop-down menu be displayed in case tabs
+ *    become hidden. Defaults to false.
+ *  - disableTelemetry: By default, switching tabs on and off in the sidebar
+ *    will record tool usage in telemetry, pass this option to true to avoid it.
+ *
+ * Events raised:
+ * - new-tab-registered : After a tab has been added via addTab. The tab ID
+ *   is passed with the event. This however, is raised before the tab iframe
+ *   is fully loaded.
+ * - <tabid>-ready : After the tab iframe has been loaded
+ * - <tabid>-selected : After tab <tabid> was selected
+ * - select : Same as above, but for any tab, the ID is passed with the event
+ * - <tabid>-unselected : After tab <tabid> is unselected
  */
-function ToolSidebar(tabbox, panel, uid, showTabstripe=true)
-{
+function ToolSidebar(tabbox, panel, uid, options={}) {
   EventEmitter.decorate(this);
 
   this._tabbox = tabbox;
   this._uid = uid;
   this._panelDoc = this._tabbox.ownerDocument;
   this._toolPanel = panel;
+  this._options = options;
+
+  this._onTabBoxOverflow = this._onTabBoxOverflow.bind(this);
+  this._onTabBoxUnderflow = this._onTabBoxUnderflow.bind(this);
 
   try {
     this._width = Services.prefs.getIntPref("devtools.toolsidebar-width." + this._uid);
   } catch(e) {}
 
-  this._telemetry = new Telemetry();
+  if (!options.disableTelemetry) {
+    this._telemetry = new Telemetry();
+  }
 
   this._tabbox.tabpanels.addEventListener("select", this, true);
 
   this._tabs = new Map();
 
-  if (!showTabstripe) {
+  // Check for existing tabs in the DOM and add them.
+  this.addExistingTabs();
+
+  if (this._options.hideTabstripe) {
     this._tabbox.setAttribute("hidetabs", "true");
   }
 
+  if (this._options.showAllTabsMenu) {
+    this.addAllTabsMenu();
+  }
+
   this._toolPanel.emit("sidebar-created", this);
 }
 
 exports.ToolSidebar = ToolSidebar;
 
 ToolSidebar.prototype = {
+  TAB_ID_PREFIX: "sidebar-tab-",
+
+  TABPANEL_ID_PREFIX: "sidebar-panel-",
+
+  /**
+   * Add a "…" button at the end of the tabstripe that toggles a dropdown menu
+   * containing the list of all tabs if any become hidden due to lack of room.
+   *
+   * If the ToolSidebar was created with the "showAllTabsMenu" option set to
+   * true, this is already done automatically. If not, you may call this
+   * function at any time to add the menu.
+   */
+  addAllTabsMenu: function() {
+    if (this._allTabsBtn) {
+      return;
+    }
+
+    let tabs = this._tabbox.tabs;
+
+    // Create a toolbar and insert it first in the tabbox
+    let allTabsToolbar = this._panelDoc.createElementNS(XULNS, "toolbar");
+    this._tabbox.insertBefore(allTabsToolbar, tabs);
+
+    // Move the tabs inside and make them flex
+    allTabsToolbar.appendChild(tabs);
+    tabs.setAttribute("flex", "1");
+
+    // Create the dropdown menu next to the tabs
+    this._allTabsBtn = this._panelDoc.createElementNS(XULNS, "toolbarbutton");
+    this._allTabsBtn.setAttribute("class", "devtools-sidebar-alltabs");
+    this._allTabsBtn.setAttribute("type", "menu");
+    this._allTabsBtn.setAttribute("label", l10n("sidebar.showAllTabs.label"));
+    this._allTabsBtn.setAttribute("tooltiptext", l10n("sidebar.showAllTabs.tooltip"));
+    this._allTabsBtn.setAttribute("hidden", "true");
+    allTabsToolbar.appendChild(this._allTabsBtn);
+
+    let menuPopup = this._panelDoc.createElementNS(XULNS, "menupopup");
+    this._allTabsBtn.appendChild(menuPopup);
+
+    // Listening to tabs overflow event to toggle the alltabs button
+    tabs.addEventListener("overflow", this._onTabBoxOverflow, false);
+    tabs.addEventListener("underflow", this._onTabBoxUnderflow, false);
+
+    // Add menuitems to the alltabs menu if there are already tabs in the
+    // sidebar
+    for (let [id, tab] of this._tabs) {
+      this._addItemToAllTabsMenu(id, tab, tab.hasAttribute("selected"));
+    }
+  },
+
+  removeAllTabsMenu: function() {
+    if (!this._allTabsBtn) {
+      return;
+    }
+
+    let tabs = this._tabbox.tabs;
+
+    tabs.removeEventListener("overflow", this._onTabBoxOverflow, false);
+    tabs.removeEventListener("underflow", this._onTabBoxUnderflow, false);
+
+    // Moving back the tabs as a first child of the tabbox
+    this._tabbox.insertBefore(tabs, this._tabbox.tabpanels);
+    this._tabbox.querySelector("toolbar").remove();
+
+    this._allTabsBtn = null;
+  },
+
+  _onTabBoxOverflow: function() {
+    this._allTabsBtn.removeAttribute("hidden");
+  },
+
+  _onTabBoxUnderflow: function() {
+    this._allTabsBtn.setAttribute("hidden", "true");
+  },
+
+  /**
+   * Add an item in the allTabs menu for a given tab.
+   */
+  _addItemToAllTabsMenu: function(id, tab, selected=false) {
+    if (!this._allTabsBtn) {
+      return;
+    }
+
+    let item = this._panelDoc.createElementNS(XULNS, "menuitem");
+    item.setAttribute("id", "sidebar-alltabs-item-" + id);
+    item.setAttribute("label", tab.getAttribute("label"));
+    item.setAttribute("type", "checkbox");
+    if (selected) {
+      item.setAttribute("checked", true);
+    }
+    // The auto-checking of menuitems in this menu doesn't work, so let's do
+    // it manually
+    item.setAttribute("autocheck", false);
+
+    this._allTabsBtn.querySelector("menupopup").appendChild(item);
+
+    item.addEventListener("click", () => {
+      this._tabbox.selectedTab = tab;
+    }, false);
+
+    tab.allTabsMenuItem = item;
+
+    return item;
+  },
+
   /**
    * Register a tab. A tab is a document.
    * The document must have a title, which will be used as the name of the tab.
    *
    * @param {string} tab uniq id
    * @param {string} url
    */
-  addTab: function ToolSidebar_addTab(id, url, selected=false) {
+  addTab: function(id, url, selected=false) {
     let iframe = this._panelDoc.createElementNS(XULNS, "iframe");
     iframe.className = "iframe-" + id;
     iframe.setAttribute("flex", "1");
     iframe.setAttribute("src", url);
     iframe.tooltip = "aHTMLTooltip";
 
-    let tab = this._tabbox.tabs.appendItem();
+    // Creating the tab and adding it to the tabbox
+    let tab = this._panelDoc.createElementNS(XULNS, "tab");
+    this._tabbox.tabs.appendChild(tab);
     tab.setAttribute("label", ""); // Avoid showing "undefined" while the tab is loading
-    tab.setAttribute("id", "sidebar-tab-" + id);
+    tab.setAttribute("id", this.TAB_ID_PREFIX + id);
+
+    // Add the tab to the allTabs menu if exists
+    let allTabsItem = this._addItemToAllTabsMenu(id, tab, selected);
 
     let onIFrameLoaded = (event) => {
       let doc = event.target;
       let win = doc.defaultView;
       tab.setAttribute("label", doc.title);
 
+      if (allTabsItem) {
+        allTabsItem.setAttribute("label", doc.title);
+      }
+
       iframe.removeEventListener("load", onIFrameLoaded, true);
       if ("setPanel" in win) {
         win.setPanel(this._toolPanel, iframe);
       }
       this.emit(id + "-ready");
     };
 
     iframe.addEventListener("load", onIFrameLoaded, true);
 
     let tabpanel = this._panelDoc.createElementNS(XULNS, "tabpanel");
-    tabpanel.setAttribute("id", "sidebar-panel-" + id);
+    tabpanel.setAttribute("id", this.TABPANEL_ID_PREFIX + id);
     tabpanel.appendChild(iframe);
     this._tabbox.tabpanels.appendChild(tabpanel);
 
     this._tooltip = this._panelDoc.createElementNS(XULNS, "tooltip");
     this._tooltip.id = "aHTMLTooltip";
     tabpanel.appendChild(this._tooltip);
     this._tooltip.page = true;
 
-    tab.linkedPanel = "sidebar-panel-" + id;
+    tab.linkedPanel = this.TABPANEL_ID_PREFIX + id;
 
     // We store the index of this tab.
     this._tabs.set(id, tab);
 
     if (selected) {
       // For some reason I don't understand, if we call this.select in this
       // event loop (after inserting the tab), the tab will never get the
       // the "selected" attribute set to true.
       this._panelDoc.defaultView.setTimeout(() => {
         this.select(id);
       }, 10);
     }
 
     this.emit("new-tab-registered", id);
   },
 
+  untitledTabsIndex: 0,
+
+  /**
+   * Search for existing tabs in the markup that aren't know yet and add them.
+   */
+  addExistingTabs: function() {
+    let knownTabs = [...this._tabs.values()];
+
+    for (let tab of this._tabbox.tabs.querySelectorAll("tab")) {
+      if (knownTabs.indexOf(tab) !== -1) {
+        continue;
+      }
+
+      // Find an ID for this unknown tab
+      let id = tab.getAttribute("id") || "untitled-tab-" + (this.untitledTabsIndex++);
+
+      // Register the tab
+      this._tabs.set(id, tab);
+      this.emit("new-tab-registered", id);
+    }
+  },
+
   /**
    * Remove an existing tab.
+   * @param {String} tabId The ID of the tab that was used to register it, or
+   * the tab id attribute value if the tab existed before the sidebar got created.
+   * @param {String} tabPanelId Optional. If provided, this ID will be used
+   * instead of the tabId to retrieve and remove the corresponding <tabpanel>
    */
-  removeTab: Task.async(function*(id) {
-    let tab = this._tabbox.tabs.querySelector("tab#sidebar-tab-" + id);
+  removeTab: Task.async(function*(tabId, tabPanelId) {
+    // Remove the tab if it can be found
+    let tab = this.getTab(tabId);
     if (!tab) {
       return;
     }
 
-    let win = this.getWindowForTab(id);
-    if ("destroy" in win) {
+    let win = this.getWindowForTab(tabId);
+    if (win && ("destroy" in win)) {
       yield win.destroy();
     }
 
     tab.remove();
 
-    let panel = this.getTab(id);
+    // Also remove the tabpanel
+    let panel = this.getTabPanel(tabPanelId || tabId);
     if (panel) {
       panel.remove();
     }
 
-    this._tabs.delete(id);
+    this._tabs.delete(tabId);
+    this.emit("tab-unregistered", tabId);
+  }),
 
-    this.emit("tab-unregistered", id);
-  }),
+  /**
+   * Show or hide a specific tab
+   */
+  toggleTab: function(id, isVisible) {
+    let tab = this.getTab(id);
+    if (!tab) {
+      return;
+    }
+    tab.hidden = !isVisible;
+    if (this._allTabsBtn) {
+      this._allTabsBtn.querySelector("#sidebar-alltabs-item-" + id).hidden = !isVisible;
+    }
+  },
 
   /**
    * Select a specific tab.
    */
-  select: function ToolSidebar_select(id) {
-    let tab = this._tabs.get(id);
+  select: function(id) {
+    let tab = this.getTab(id);
     if (tab) {
       this._tabbox.selectedTab = tab;
     }
   },
 
   /**
    * Return the id of the selected tab.
    */
-  getCurrentTabID: function ToolSidebar_getCurrentTabID() {
+  getCurrentTabID: function() {
     let currentID = null;
     for (let [id, tab] of this._tabs) {
       if (this._tabbox.tabs.selectedItem == tab) {
         currentID = id;
         break;
       }
     }
     return currentID;
   },
 
   /**
-   * Returns the requested tab based on the id.
-   *
-   * @param String id
-   *        unique id of the requested tab.
+   * Returns the requested tab panel based on the id.
+   * @param {String} id
+   * @return {DOMNode}
    */
-  getTab: function ToolSidebar_getTab(id) {
-    return this._tabbox.tabpanels.querySelector("#sidebar-panel-" + id);
+  getTabPanel: function(id) {
+    // Search with and without the ID prefix as there might have been existing
+    // tabpanels by the time the sidebar got created
+    return this._tabbox.tabpanels.querySelector("#" + this.TABPANEL_ID_PREFIX + id + ", #" + id);
+  },
+
+  /**
+   * Return the tab based on the provided id, if one was registered with this id.
+   * @param {String} id
+   * @return {DOMNode}
+   */
+  getTab: function(id) {
+    return this._tabs.get(id);
   },
 
   /**
    * Event handler.
    */
-  handleEvent: function ToolSidebar_eventHandler(event) {
-    if (event.type == "select") {
-      if (this._currentTool == this.getCurrentTabID()) {
-        // Tool hasn't changed.
-        return;
+  handleEvent: function(event) {
+    if (event.type !== "select" || this._destroyed) {
+      return;
+    }
+
+    if (this._currentTool == this.getCurrentTabID()) {
+      // Tool hasn't changed.
+      return;
+    }
+
+    let previousTool = this._currentTool;
+    this._currentTool = this.getCurrentTabID();
+    if (previousTool) {
+      if (this._telemetry) {
+        this._telemetry.toolClosed(previousTool);
       }
+      this.emit(previousTool + "-unselected");
+    }
 
-      let previousTool = this._currentTool;
-      this._currentTool = this.getCurrentTabID();
-      if (previousTool) {
-        this._telemetry.toolClosed(previousTool);
-        this.emit(previousTool + "-unselected");
+    if (this._telemetry) {
+      this._telemetry.toolOpened(this._currentTool);
+    }
+
+    this.emit(this._currentTool + "-selected");
+    this.emit("select", this._currentTool);
+
+    // Handlers for "select"/"...-selected"/"...-unselected" events might have
+    // destroyed the sidebar in the meantime.
+    if (this._destroyed) {
+      return;
+    }
+
+    // Handle menuitem selection if the allTabsMenu is there by unchecking all
+    // items except the selected one.
+    let tab = this._tabbox.selectedTab;
+    if (tab.allTabsMenuItem) {
+      for (let otherItem of this._allTabsBtn.querySelectorAll("menuitem")) {
+        otherItem.removeAttribute("checked");
       }
-
-      this._telemetry.toolOpened(this._currentTool);
-      this.emit(this._currentTool + "-selected");
-      this.emit("select", this._currentTool);
+      tab.allTabsMenuItem.setAttribute("checked", true);
     }
   },
 
   /**
    * Toggle sidebar's visibility state.
    */
-  toggle: function ToolSidebar_toggle() {
+  toggle: function() {
     if (this._tabbox.hasAttribute("hidden")) {
       this.show();
     } else {
       this.hide();
     }
   },
 
   /**
    * Show the sidebar.
    */
-  show: function ToolSidebar_show() {
+  show: function() {
     if (this._width) {
       this._tabbox.width = this._width;
     }
     this._tabbox.removeAttribute("hidden");
 
     this.emit("show");
   },
 
   /**
    * Show the sidebar.
    */
-  hide: function ToolSidebar_hide() {
+  hide: function() {
     Services.prefs.setIntPref("devtools.toolsidebar-width." + this._uid, this._tabbox.width);
     this._tabbox.setAttribute("hidden", "true");
 
     this.emit("hide");
   },
 
   /**
    * Return the window containing the tab content.
    */
-  getWindowForTab: function ToolSidebar_getWindowForTab(id) {
+  getWindowForTab: function(id) {
     if (!this._tabs.has(id)) {
       return null;
     }
 
-    let panel = this._panelDoc.getElementById(this._tabs.get(id).linkedPanel);
+    // Get the tabpanel and make sure it contains an iframe
+    let panel = this.getTabPanel(id);
+    if (!panel || !panel.firstChild || !panel.firstChild.contentWindow) {
+      return;
+    }
     return panel.firstChild.contentWindow;
   },
 
   /**
    * Clean-up.
    */
   destroy: Task.async(function*() {
     if (this._destroyed) {
-      return promise.resolve(null);
+      return;
     }
     this._destroyed = true;
 
     Services.prefs.setIntPref("devtools.toolsidebar-width." + this._uid, this._tabbox.width);
 
+    if (this._allTabsBtn) {
+      this.removeAllTabsMenu();
+    }
+
     this._tabbox.tabpanels.removeEventListener("select", this, true);
 
     // Note that we check for the existence of this._tabbox.tabpanels at each
     // step as the container window may have been closed by the time one of the
     // panel's destroy promise resolves.
     while (this._tabbox.tabpanels && this._tabbox.tabpanels.hasChildNodes()) {
       let panel = this._tabbox.tabpanels.firstChild;
       let win = panel.firstChild.contentWindow;
-      if ("destroy" in win) {
+      if (win && ("destroy" in win)) {
         yield win.destroy();
       }
       panel.remove();
     }
 
     while (this._tabbox.tabs && this._tabbox.tabs.hasChildNodes()) {
       this._tabbox.tabs.removeChild(this._tabbox.tabs.firstChild);
     }
 
-    if (this._currentTool) {
+    if (this._currentTool && this._telemetry) {
       this._telemetry.toolClosed(this._currentTool);
     }
 
     this._toolPanel.emit("sidebar-destroyed", this);
 
     this._tabs = null;
     this._tabbox = null;
     this._panelDoc = null;
     this._toolPanel = null;
+  })
+}
 
-    return promise.resolve(null);
-  }),
-}
+XPCOMUtils.defineLazyGetter(this, "l10n", function() {
+  let bundle = Services.strings.createBundle("chrome://browser/locale/devtools/toolbox.properties");
+  let l10n = function(aName, ...aArgs) {
+    try {
+      if (aArgs.length == 0) {
+        return bundle.GetStringFromName(aName);
+      } else {
+        return bundle.formatStringFromName(aName, aArgs, aArgs.length);
+      }
+    } catch (ex) {
+      Services.console.logStringMessage("Error reading '" + aName + "'");
+    }
+  };
+  return l10n;
+});
--- a/browser/devtools/framework/test/browser.ini
+++ b/browser/devtools/framework/test/browser.ini
@@ -1,14 +1,15 @@
 [DEFAULT]
 subsuite = devtools
 support-files =
   browser_toolbox_options_disable_js.html
   browser_toolbox_options_disable_js_iframe.html
   browser_toolbox_options_disable_cache.sjs
+  browser_toolbox_sidebar_tool.xul
   head.js
   helper_disable_cache.js
   doc_theme.css
 
 [browser_devtools_api.js]
 skip-if = e10s # Bug 1090340
 [browser_devtools_api_destroy.js]
 skip-if = e10s # Bug 1070837 - devtools/framework/toolbox.js |doc| getter not e10s friendly
@@ -34,16 +35,18 @@ skip-if = e10s # Bug 1030318
 skip-if = e10s # Bug 1070837 - devtools/framework/toolbox.js |doc| getter not e10s friendly
 # [browser_toolbox_raise.js] # Bug 962258
 # skip-if = os == "win"
 [browser_toolbox_ready.js]
 [browser_toolbox_select_event.js]
 skip-if = e10s # Bug 1069044 - destroyInspector may hang during shutdown
 [browser_toolbox_sidebar.js]
 [browser_toolbox_sidebar_events.js]
+[browser_toolbox_sidebar_existing_tabs.js]
+[browser_toolbox_sidebar_overflow_menu.js]
 [browser_toolbox_tabsswitch_shortcuts.js]
 [browser_toolbox_tool_ready.js]
 [browser_toolbox_tool_remote_reopen.js]
 [browser_toolbox_window_reload_target.js]
 [browser_toolbox_window_shortcuts.js]
 skip-if = os == "mac" && os_version == "10.8" || os == "win" && os_version == "5.1" # Bug 851129 - Re-enable browser_toolbox_window_shortcuts.js test after leaks are fixed
 [browser_toolbox_window_title_changes.js]
 [browser_toolbox_zoom.js]
--- a/browser/devtools/framework/test/browser_toolbox_options_disable_js.js
+++ b/browser/devtools/framework/test/browser_toolbox_options_disable_js.js
@@ -1,15 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that disabling JavaScript for a tab works as it should.
 
-const TEST_URI = "http://example.com/browser/browser/devtools/framework/" +
-                 "test/browser_toolbox_options_disable_js.html";
+const TEST_URI = URL_ROOT + "browser_toolbox_options_disable_js.html";
 
 let doc;
 let toolbox;
 
 function test() {
   gBrowser.selectedTab = gBrowser.addTab();
   let target = TargetFactory.forTab(gBrowser.selectedTab);
 
--- a/browser/devtools/framework/test/browser_toolbox_sidebar.js
+++ b/browser/devtools/framework/test/browser_toolbox_sidebar.js
@@ -106,17 +106,17 @@ function test() {
     let panels = panel.sidebar._tabbox.querySelectorAll("tabpanel");
     let label = 1;
     for (let tab of tabs) {
       is(tab.getAttribute("label"), label++, "Tab has the right title");
     }
 
     is(label, 4, "Found the right amount of tabs.");
     is(panel.sidebar._tabbox.selectedPanel, panels[0], "First tab is selected");
-    ok(panel.sidebar.getCurrentTabID(), "tab1", "getCurrentTabID() is correct");
+    is(panel.sidebar.getCurrentTabID(), "tab1", "getCurrentTabID() is correct");
 
     panel.sidebar.once("tab1-unselected", function() {
       ok(true, "received 'unselected' event");
       panel.sidebar.once("tab2-selected", function() {
         ok(true, "received 'selected' event");
         panel.sidebar.hide();
         is(panel.sidebar._tabbox.getAttribute("hidden"), "true", "Sidebar hidden");
         is(panel.sidebar.getWindowForTab("tab1").location.href, tab1URL, "Window is accessible");
@@ -149,16 +149,17 @@ function test() {
   function testWidth(panel) {
     let tabbox = panel.panelDoc.getElementById("sidebar");
     tabbox.width = 420;
     panel.sidebar.destroy().then(function() {
       tabbox.width = 0;
       panel.sidebar = new ToolSidebar(tabbox, panel, "testbug865688", true);
       panel.sidebar.show();
       is(panel.panelDoc.getElementById("sidebar").width, 420, "Width restored")
+
       finishUp(panel);
     });
   }
 
   function finishUp(panel) {
     panel.sidebar.destroy();
     gDevTools.unregisterTool(toolDefinition.id);
 
new file mode 100644
--- /dev/null
+++ b/browser/devtools/framework/test/browser_toolbox_sidebar_existing_tabs.js
@@ -0,0 +1,76 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that the sidebar widget auto-registers existing tabs.
+
+const Cu = Components.utils;
+const {ToolSidebar} = devtools.require("devtools/framework/sidebar");
+
+const testToolURL = "data:text/xml;charset=utf8,<?xml version='1.0'?>" +
+                "<?xml-stylesheet href='chrome://browser/skin/devtools/common.css' type='text/css'?>" +
+                "<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'>" +
+                "<hbox flex='1'><description flex='1'>test tool</description>" +
+                "<splitter class='devtools-side-splitter'/>" +
+                "<tabbox flex='1' id='sidebar' class='devtools-sidebar-tabs'>" +
+                "<tabs><tab id='tab1' label='tab 1'></tab><tab id='tab2' label='tab 2'></tab></tabs>" +
+                "<tabpanels flex='1'><tabpanel id='tabpanel1'>tab 1</tabpanel><tabpanel id='tabpanel2'>tab 2</tabpanel></tabpanels>" +
+                "</tabbox></hbox></window>";
+
+const testToolDefinition = {
+  id: "testTool",
+  url: testToolURL,
+  label: "Test Tool",
+  isTargetSupported: () => true,
+  build: (iframeWindow, toolbox) => {
+    return promise.resolve({
+      target: toolbox.target,
+      toolbox: toolbox,
+      isReady: true,
+      destroy: () => {},
+      panelDoc: iframeWindow.document,
+    });
+  }
+};
+
+add_task(function*() {
+  let tab = yield addTab("about:blank");
+
+  let target = TargetFactory.forTab(tab);
+
+  gDevTools.registerTool(testToolDefinition);
+  let toolbox = yield gDevTools.showToolbox(target, testToolDefinition.id);
+
+  let toolPanel = toolbox.getPanel(testToolDefinition.id);
+  let tabbox = toolPanel.panelDoc.getElementById("sidebar");
+
+  info("Creating the sidebar widget");
+  let sidebar = new ToolSidebar(tabbox, toolPanel, "bug1101569");
+  
+  info("Checking that existing tabs have been registered");
+  ok(sidebar.getTab("tab1"), "Existing tab 1 was found");
+  ok(sidebar.getTab("tab2"), "Existing tab 2 was found");
+  ok(sidebar.getTabPanel("tabpanel1"), "Existing tabpanel 1 was found");
+  ok(sidebar.getTabPanel("tabpanel2"), "Existing tabpanel 2 was found");
+
+  info("Checking that the sidebar API works with existing tabs");
+
+  sidebar.select("tab2");
+  is(tabbox.selectedTab, tabbox.querySelector("#tab2"),
+    "Existing tabs can be selected");
+
+  sidebar.select("tab1");
+  is(tabbox.selectedTab, tabbox.querySelector("#tab1"),
+    "Existing tabs can be selected");
+
+  is(sidebar.getCurrentTabID(), "tab1", "getCurrentTabID returns the expected id");
+
+  info("Removing a tab");
+  sidebar.removeTab("tab2", "tabpanel2");
+  ok(!sidebar.getTab("tab2"), "Tab 2 was removed correctly");
+  ok(!sidebar.getTabPanel("tabpanel2"), "Tabpanel 2 was removed correctly");
+
+  sidebar.destroy();
+  gDevTools.unregisterTool(testToolDefinition.id);
+  gBrowser.removeCurrentTab();
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/framework/test/browser_toolbox_sidebar_overflow_menu.js
@@ -0,0 +1,76 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that the sidebar widget correctly displays the "all tabs..." button
+// when the tabs overflow.
+
+const {ToolSidebar} = devtools.require("devtools/framework/sidebar");
+
+const testToolDefinition = {
+  id: "testTool",
+  url: CHROME_URL_ROOT + "browser_toolbox_sidebar_tool.xul",
+  label: "Test Tool",
+  isTargetSupported: () => true,
+  build: (iframeWindow, toolbox) => {
+    return {
+      target: toolbox.target,
+      toolbox: toolbox,
+      isReady: true,
+      destroy: () => {},
+      panelDoc: iframeWindow.document,
+    };
+  }
+};
+
+add_task(function*() {
+  let tab = yield addTab("about:blank");
+  let target = TargetFactory.forTab(tab);
+
+  gDevTools.registerTool(testToolDefinition);
+  let toolbox = yield gDevTools.showToolbox(target, testToolDefinition.id);
+
+  let toolPanel = toolbox.getPanel(testToolDefinition.id);
+  let tabbox = toolPanel.panelDoc.getElementById("sidebar");
+
+  info("Creating the sidebar widget");
+  let sidebar = new ToolSidebar(tabbox, toolPanel, "bug1101569", {
+    showAllTabsMenu: true
+  });
+
+  let allTabsMenu = toolPanel.panelDoc.querySelector(".devtools-sidebar-alltabs");
+  ok(allTabsMenu, "The all-tabs menu is available");
+  is(allTabsMenu.getAttribute("hidden"), "true", "The menu is hidden for now");
+
+  info("Adding 10 tabs to the sidebar widget");
+  for (let nb = 0; nb < 10; nb ++) {
+    let url = `data:text/html;charset=utf8,<title>tab ${nb}</title><p>Test tab ${nb}</p>`;
+    sidebar.addTab("tab" + nb, url, nb === 0);
+  }
+
+  info("Fake an overflow event so that the all-tabs menu is visible");
+  sidebar._onTabBoxOverflow();
+  ok(!allTabsMenu.hasAttribute("hidden"), "The all-tabs menu is now shown");
+
+  info("Select each tab, one by one");
+  for (let nb = 0; nb < 10; nb ++) {
+    let id = "tab" + nb;
+
+    info("Found tab item nb " + nb);
+    let item = allTabsMenu.querySelector("#sidebar-alltabs-item-" + id);
+
+    info("Click on the tab");
+    EventUtils.sendMouseEvent({type: "click"}, item, toolPanel.panelDoc.defaultView);
+
+    is(tabbox.selectedTab.id, "sidebar-tab-" + id,
+      "The selected tab is now nb " + nb);
+  }
+
+  info("Fake an underflow event so that the all-tabs menu gets hidden");
+  sidebar._onTabBoxUnderflow();
+  is(allTabsMenu.getAttribute("hidden"), "true", "The all-tabs menu is hidden");
+
+  yield sidebar.destroy();
+  gDevTools.unregisterTool(testToolDefinition.id);
+  gBrowser.removeCurrentTab();
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/framework/test/browser_toolbox_sidebar_tool.xul
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/devtools/widgets.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/devtools/common.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/devtools/widgets.css" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript;version=1.8" src="chrome://browser/content/devtools/theme-switching.js"/>
+  <box flex="1" class="devtools-responsive-container theme-body">
+    <vbox flex="1" class="devtools-main-content" id="content">test</vbox>
+    <splitter class="devtools-side-splitter"/>
+    <tabbox flex="1" id="sidebar" class="devtools-sidebar-tabs">
+      <tabs/>
+      <tabpanels flex="1"/>
+    </tabbox>
+  </box>
+</window>
--- a/browser/devtools/framework/test/head.js
+++ b/browser/devtools/framework/test/head.js
@@ -3,16 +3,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 let TargetFactory = gDevTools.TargetFactory;
 
 const { console } = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
 const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
 const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 
+const URL_ROOT = "http://example.com/browser/browser/devtools/framework/test/";
+const CHROME_URL_ROOT = "chrome://mochitests/content/browser/browser/devtools/framework/test/";
+
 let TargetFactory = devtools.TargetFactory;
 
 // All test are asynchronous
 waitForExplicitFinish();
 
 // Uncomment this pref to dump all devtools emitted events to the console.
 // Services.prefs.setBoolPref("devtools.dump.emit", true);
 
--- a/browser/devtools/framework/test/helper_disable_cache.js
+++ b/browser/devtools/framework/test/helper_disable_cache.js
@@ -1,16 +1,15 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 // Common code shared by browser_toolbox_options_disable_cache-*.js
-const TEST_URI = "http://mochi.test:8888/browser/browser/devtools/framework/" +
-                 "test/browser_toolbox_options_disable_cache.sjs";
+const TEST_URI = URL_ROOT + "browser_toolbox_options_disable_cache.sjs";
 let tabs = [
 {
   title: "Tab 0",
   desc: "Toggles cache on.",
   startToolbox: true
 },
 {
   title: "Tab 1",
--- a/browser/devtools/inspector/inspector-panel.js
+++ b/browser/devtools/inspector/inspector-panel.js
@@ -342,18 +342,16 @@ InspectorPanel.prototype = {
                         "layoutview" == defaultTab);
 
     if (this.target.form.animationsActor) {
       this.sidebar.addTab("animationinspector",
                           "chrome://browser/content/devtools/animationinspector/animation-inspector.xhtml",
                           "animationinspector" == defaultTab);
     }
 
-    let ruleViewTab = this.sidebar.getTab("ruleview");
-
     this.sidebar.show();
   },
 
   /**
    * Reset the inspector on new root mutation.
    */
   onNewRoot: function InspectorPanel_onNewRoot() {
     this._defaultNode = null;
--- a/browser/devtools/markupview/markup-view.js
+++ b/browser/devtools/markupview/markup-view.js
@@ -2124,17 +2124,17 @@ function ElementEditor(aContainer, aNode
       element: this.tag,
       trigger: "dblclick",
       stopOnReturn: true,
       done: this.onTagEdit.bind(this),
     });
   }
 
   // Make the new attribute space editable.
-  editableField({
+  this.newAttr.editMode = editableField({
     element: this.newAttr,
     trigger: "dblclick",
     stopOnReturn: true,
     contentType: InplaceEditor.CONTENT_TYPES.CSS_MIXED,
     popup: this.markup.popup,
     done: (aVal, aCommit) => {
       if (!aCommit) {
         return;
@@ -2230,17 +2230,17 @@ ElementEditor.prototype = {
     }
 
     // Wrap with ' since there are no single quotes in the attribute value.
     if (hasDoubleQuote && !hasSingleQuote) {
         initial = aAttr.name + "='" + editValueDisplayed + "'";
     }
 
     // Make the attribute editable.
-    editableField({
+    attr.editMode = editableField({
       element: inner,
       trigger: "dblclick",
       stopOnReturn: true,
       selectAll: false,
       initial: initial,
       contentType: InplaceEditor.CONTENT_TYPES.CSS_MIXED,
       popup: this.markup.popup,
       start: (aEditor, aEvent) => {
@@ -2252,28 +2252,29 @@ ElementEditor.prototype = {
           let length = editValueDisplayed.length;
           let editorLength = aEditor.input.value.length;
           let start = editorLength - (length + 1);
           aEditor.input.setSelectionRange(start, start + length);
         } else {
           aEditor.input.select();
         }
       },
-      done: (aVal, aCommit) => {
+      done: (aVal, aCommit, direction) => {
         if (!aCommit || aVal === initial) {
           return;
         }
 
         let doMods = this._startModifyingAttributes();
         let undoMods = this._startModifyingAttributes();
 
         // Remove the attribute stored in this editor and re-add any attributes
         // parsed out of the input element. Restore original attribute if
         // parsing fails.
         try {
+          this.refocusOnEdit(aAttr.name, attr, direction);
           this._saveAttribute(aAttr.name, undoMods);
           doMods.removeAttribute(aAttr.name);
           this._applyAttributes(aVal, attr, doMods, undoMods);
           this.container.undo.do(() => {
             doMods.apply();
           }, () => {
             undoMods.apply();
           });
@@ -2343,16 +2344,107 @@ ElementEditor.prototype = {
       let oldValue = node.getAttribute(aName);
       aUndoMods.setAttribute(aName, oldValue);
     } else {
       aUndoMods.removeAttribute(aName);
     }
   },
 
   /**
+   * Listen to mutations, and when the attribute list is regenerated
+   * try to focus on the attribute after the one that's being edited now.
+   * If the attribute order changes, go to the beginning of the attribute list.
+   */
+  refocusOnEdit: function(attrName, attrNode, direction) {
+    // Only allow one refocus on attribute change at a time, so when there's
+    // more than 1 request in parallel, the last one wins.
+    if (this._editedAttributeObserver) {
+      this.markup._inspector.off("markupmutation", this._editedAttributeObserver);
+      this._editedAttributeObserver = null;
+    }
+
+    let container = this.markup.getContainer(this.node);
+
+    let activeAttrs = [...this.attrList.childNodes].filter(el => el.style.display != "none");
+    let attributeIndex = activeAttrs.indexOf(attrNode);
+
+    let onMutations = this._editedAttributeObserver = (e, mutations) => {
+      let isDeletedAttribute = false;
+      let isNewAttribute = false;
+      for (let mutation of mutations) {
+        let inContainer = this.markup.getContainer(mutation.target) === container;
+        if (!inContainer) {
+          continue;
+        }
+
+        let isOriginalAttribute = mutation.attributeName === attrName;
+
+        isDeletedAttribute = isDeletedAttribute || isOriginalAttribute && mutation.newValue === null;
+        isNewAttribute = isNewAttribute || mutation.attributeName !== attrName;
+      }
+      let isModifiedOrder = isDeletedAttribute && isNewAttribute;
+      this._editedAttributeObserver = null;
+
+      // "Deleted" attributes are merely hidden, so filter them out.
+      let visibleAttrs = [...this.attrList.childNodes].filter(el => el.style.display != "none");
+      let activeEditor;
+      if (visibleAttrs.length > 0) {
+        if (!direction) {
+          // No direction was given; stay on current attribute.
+          activeEditor = visibleAttrs[attributeIndex];
+        } else if (isModifiedOrder) {
+          // The attribute was renamed, reordering the existing attributes.
+          // So let's go to the beginning of the attribute list for consistency.
+          activeEditor = visibleAttrs[0];
+        } else {
+          let newAttributeIndex;
+          if (isDeletedAttribute) {
+            newAttributeIndex = attributeIndex;
+          } else {
+            if (direction == Ci.nsIFocusManager.MOVEFOCUS_FORWARD) {
+              newAttributeIndex = attributeIndex + 1;
+            } else if (direction == Ci.nsIFocusManager.MOVEFOCUS_BACKWARD) {
+              newAttributeIndex = attributeIndex - 1;
+            }
+          }
+
+          // The number of attributes changed (deleted), or we moved through the array
+          // so check we're still within bounds.
+          if (newAttributeIndex >= 0 && newAttributeIndex <= visibleAttrs.length - 1) {
+            activeEditor = visibleAttrs[newAttributeIndex];
+          }
+        }
+      }
+
+      // Either we have no attributes left,
+      // or we just edited the last attribute and want to move on.
+      if (!activeEditor) {
+        activeEditor = this.newAttr;
+      }
+
+      // Refocus was triggered by tab or shift-tab.
+      // Continue in edit mode.
+      if (direction) {
+        activeEditor.editMode();
+      } else {
+        // Refocus was triggered by enter.
+        // Exit edit mode (but restore focus).
+        let editable = activeEditor === this.newAttr ? activeEditor : activeEditor.querySelector(".editable");
+        editable.focus();
+      }
+
+      this.markup.emit("refocusedonedit");
+    };
+
+    // Start listening for mutations until we find an attributes change
+    // that modifies this attribute.
+    this.markup._inspector.once("markupmutation", onMutations);
+  },
+
+  /**
    * Called when the tag name editor has is done editing.
    */
   onTagEdit: function(newTagName, isCommit) {
     if (!isCommit || newTagName.toLowerCase() === this.node.tagName.toLowerCase() ||
         !("editTagName" in this.markup.walker)) {
       return;
     }
 
--- a/browser/devtools/markupview/test/browser.ini
+++ b/browser/devtools/markupview/test/browser.ini
@@ -58,16 +58,17 @@ skip-if = e10s # Bug 1040751 - CodeMirro
 [browser_markupview_events_jquery_1.11.1.js]
 skip-if = e10s # Bug 1040751 - CodeMirror editor.destroy() isn't e10s compatible
 [browser_markupview_events_jquery_2.1.1.js]
 skip-if = e10s # Bug 1040751 - CodeMirror editor.destroy() isn't e10s compatible
 [browser_markupview_html_edit_01.js]
 [browser_markupview_html_edit_02.js]
 [browser_markupview_html_edit_03.js]
 [browser_markupview_image_tooltip.js]
+[browser_markupview_keybindings_01.js]
 [browser_markupview_mutation_01.js]
 [browser_markupview_mutation_02.js]
 [browser_markupview_navigation.js]
 [browser_markupview_node_not_displayed_01.js]
 [browser_markupview_node_not_displayed_02.js]
 [browser_markupview_pagesize_01.js]
 [browser_markupview_pagesize_02.js]
 skip-if = e10s # Bug 1036409 - The last selected node isn't reselected
@@ -78,12 +79,13 @@ skip-if = e10s # Bug 1036409 - The last 
 [browser_markupview_tag_edit_04.js]
 [browser_markupview_tag_edit_05.js]
 [browser_markupview_tag_edit_06.js]
 [browser_markupview_tag_edit_07.js]
 [browser_markupview_tag_edit_08.js]
 [browser_markupview_tag_edit_09.js]
 [browser_markupview_tag_edit_10.js]
 [browser_markupview_tag_edit_11.js]
+[browser_markupview_tag_edit_12.js]
 [browser_markupview_textcontent_edit_01.js]
 [browser_markupview_toggle_01.js]
 [browser_markupview_toggle_02.js]
 [browser_markupview_toggle_03.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/markupview/test/browser_markupview_keybindings_01.js
@@ -0,0 +1,47 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests tabbing through attributes on a node
+
+const TEST_URL = "data:text/html;charset=utf8,<div a b c d e id='test'></div>";
+
+add_task(function*() {
+  let {inspector} = yield addTab(TEST_URL).then(openInspector);
+
+  info("Focusing the tag editor of the test element");
+  let {editor} = yield getContainerForSelector("div", inspector);
+  editor.tag.focus();
+
+  info("Pressing tab and expecting to focus the ID attribute, always first");
+  EventUtils.sendKey("tab", inspector.panelWin);
+  checkFocusedAttribute("id");
+
+  info("Hit enter to turn the attribute to edit mode");
+  EventUtils.sendKey("return", inspector.panelWin);
+  checkFocusedAttribute("id", true);
+
+  // Check the order of the other attributes in the DOM to the check they appear
+  // correctly in the markup-view
+  let attributes = [...getNode("div").attributes].filter(attr => attr.name !== "id");
+
+  info("Tabbing forward through attributes in edit mode");
+  for (let {name} of attributes) {
+    collapseSelectionAndTab(inspector);
+    checkFocusedAttribute(name, true);
+  }
+
+  info("Tabbing backward through attributes in edit mode");
+
+  // Just reverse the attributes other than id and remove the first one since
+  // it's already focused now.
+  let reverseAttributes = attributes.reverse();
+  reverseAttributes.shift();
+
+  for (let {name} of reverseAttributes) {
+    collapseSelectionAndShiftTab(inspector);
+    checkFocusedAttribute(name, true);
+  }
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/markupview/test/browser_markupview_tag_edit_12.js
@@ -0,0 +1,115 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that focus position is correct when tabbing through and editing
+// attributes.
+
+const TEST_URL = "data:text/html;charset=utf8,<div id='attr' c='3' b='2' a='1'></div><div id='delattr' last='1' tobeinvalid='2'></div>";
+
+add_task(function*() {
+  let {inspector} = yield addTab(TEST_URL).then(openInspector);
+
+  yield testAttributeEditing(inspector);
+  yield testAttributeDeletion(inspector);
+});
+
+function* testAttributeEditing(inspector) {
+  info("Testing focus position after attribute editing");
+
+  // Modifying attributes reorders them in the internal representation to move
+  // the modified attribute to the end. breadcrumbs.js will update attributes
+  // to match original order if you selectNode before modifying attributes.
+  // So, hacky workaround for consistency with manual testing.
+  // Should be removed after Bug 1093593.
+  yield selectNode("#attr", inspector);
+
+  info("Setting the first non-id attribute in edit mode");
+  yield activateFirstAttribute("#attr", inspector); // focuses id
+  collapseSelectionAndTab(inspector); // focuses the first attr after id
+
+  // Detect the attributes order from the DOM, instead of assuming an order in
+  // the test, because the NamedNodeMap returned by element.attributes doesn't
+  // guaranty any specific order.
+  // Filter out the id attribute as the markup-view places it first anyway.
+  let attrs = getNodeAttributesOtherThanId("#attr");
+
+  info("Editing this attribute, keeping the same name, and tabbing to the next");
+  yield editAttributeAndTab(attrs[0].name + '="99"', inspector);
+  checkFocusedAttribute(attrs[1].name, true);
+
+  info("Editing the new focused attribute, keeping the name, and tabbing to the previous");
+  yield editAttributeAndTab(attrs[1].name + '="99"', inspector, true);
+  checkFocusedAttribute(attrs[0].name, true);
+
+  info("Editing attribute name, changes attribute order");
+  yield editAttributeAndTab("d='4'", inspector);
+  checkFocusedAttribute("id", true);
+
+  // Escape of the currently focused field for the next test
+  EventUtils.sendKey("escape", inspector.panelWin);
+}
+
+function* testAttributeDeletion(inspector) {
+  info("Testing focus position after attribute deletion");
+
+  // Modifying attributes reorders them in the internal representation to move
+  // the modified attribute to the end. breadcrumbs.js will update attributes
+  // to match original order if you selectNode before modifying attributes.
+  // So, hacky workaround for consistency with manual testing.
+  // Should be removed after Bug 1093593.
+  yield selectNode("#delattr", inspector);
+
+  info("Setting the first non-id attribute in edit mode");
+  yield activateFirstAttribute("#delattr", inspector); // focuses id
+  collapseSelectionAndTab(inspector); // focuses the first attr after id
+
+  // Detect the attributes order from the DOM, instead of assuming an order in
+  // the test, because the NamedNodeMap returned by element.attributes doesn't
+  // guaranty any specific order.
+  // Filter out the id attribute as the markup-view places it first anyway.
+  let attrs = getNodeAttributesOtherThanId("#delattr");
+
+  info("Entering an invalid attribute to delete the attribute");
+  yield editAttributeAndTab('"', inspector);
+  checkFocusedAttribute(attrs[1].name, true);
+
+  info("Deleting the last attribute");
+  yield editAttributeAndTab(" ", inspector);
+
+  // Check we're on the newattr element
+  let focusedAttr = Services.focus.focusedElement;
+  ok(focusedAttr.classList.contains("styleinspector-propertyeditor"), "in newattr");
+  is(focusedAttr.tagName, "input", "newattr is active");
+}
+
+function* editAttributeAndTab(newValue, inspector, goPrevious) {
+  var onEditMutation = inspector.markup.once("refocusedonedit");
+  inspector.markup.doc.activeElement.value = newValue;
+  if (goPrevious) {
+    EventUtils.synthesizeKey("VK_TAB", { shiftKey: true },
+      inspector.panelWin);
+  } else {
+    EventUtils.sendKey("tab", inspector.panelWin);
+  }
+  yield onEditMutation;
+}
+
+/**
+ * Given a markup container, focus and turn in edit mode its first attribute
+ * field.
+ */
+function* activateFirstAttribute(container, inspector) {
+  let {editor} = yield getContainerForSelector(container, inspector);
+  editor.tag.focus();
+
+  // Go to "id" attribute and trigger edit mode.
+  EventUtils.sendKey("tab", inspector.panelWin);
+  EventUtils.sendKey("return", inspector.panelWin);
+}
+
+function getNodeAttributesOtherThanId(selector) {
+  return [...getNode(selector).attributes].filter(attr => attr.name !== "id");
+}
--- a/browser/devtools/markupview/test/head.js
+++ b/browser/devtools/markupview/test/head.js
@@ -521,8 +521,43 @@ let reopenMenu = Task.async(function*(me
  * Wait for all current promises to be resolved. See this as executeSoon that
  * can be used with yield.
  */
 function promiseNextTick() {
   let deferred = promise.defer();
   executeSoon(deferred.resolve);
   return deferred.promise;
 }
+
+/**
+ * Collapses the current text selection in an input field and tabs to the next
+ * field.
+ */
+function collapseSelectionAndTab(inspector) {
+  EventUtils.sendKey("tab", inspector.panelWin); // collapse selection and move caret to end
+  EventUtils.sendKey("tab", inspector.panelWin); // next element
+}
+
+/**
+ * Collapses the current text selection in an input field and tabs to the
+ * previous field.
+ */
+function collapseSelectionAndShiftTab(inspector) {
+  EventUtils.synthesizeKey("VK_TAB", { shiftKey: true },
+    inspector.panelWin); // collapse selection and move caret to end
+  EventUtils.synthesizeKey("VK_TAB", { shiftKey: true },
+    inspector.panelWin); // previous element
+}
+
+/**
+ * Check that the current focused element is an attribute element in the markup
+ * view.
+ * @param {String} attrName The attribute name expected to be found
+ * @param {Boolean} editMode Whether or not the attribute should be in edit mode
+ */
+function checkFocusedAttribute(attrName, editMode) {
+  let focusedAttr = Services.focus.focusedElement;
+  is(focusedAttr ? focusedAttr.parentNode.dataset.attr : undefined,
+    attrName, attrName + " attribute editor is currently focused.");
+  is(focusedAttr ? focusedAttr.tagName : undefined,
+    editMode ? "input": "span",
+    editMode ? attrName + " is in edit mode" : attrName + " is not in edit mode");
+}
--- a/browser/devtools/shared/inplace-editor.js
+++ b/browser/devtools/shared/inplace-editor.js
@@ -6,17 +6,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/.
  *
  * Basic use:
  * let spanToEdit = document.getElementById("somespan");
  *
  * editableField({
  *   element: spanToEdit,
- *   done: function(value, commit) {
+ *   done: function(value, commit, direction) {
  *     if (commit) {
  *       spanToEdit.textContent = value;
  *     }
  *   },
  *   trigger: "dblclick"
  * });
  *
  * See editableField() for more options.
@@ -57,19 +57,21 @@ Cu.import("resource://gre/modules/devtoo
  *       won't be created if canEdit returns false.
  *    {function} start:
  *       Will be called when the inplace editor is initialized.
  *    {function} change:
  *       Will be called when the text input changes.  Will be called
  *       with the current value of the text input.
  *    {function} done:
  *       Called when input is committed or blurred.  Called with
- *       current value and a boolean telling the caller whether to
- *       commit the change.  This function is called before the editor
- *       has been torn down.
+ *       current value, a boolean telling the caller whether to
+ *       commit the change, and the direction of the next element to be
+ *       selected. Direction may be one of nsIFocusManager.MOVEFOCUS_FORWARD,
+ *       nsIFocusManager.MOVEFOCUS_BACKWARD, or null (no movement).
+ *       This function is called before the editor has been torn down.
  *    {function} destroy:
  *       Called when the editor is destroyed and has been torn down.
  *    {string} advanceChars:
  *       If any characters in advanceChars are typed, focus will advance
  *       to the next element.
  *    {boolean} stopOnReturn:
  *       If true, the return key will not advance the editor to the next
  *       focusable element.
@@ -98,16 +100,17 @@ exports.editableField = editableField;
  *
  * @param {object} aOptions
  *    The options for this editor, including:
  *    {Element} element: The DOM element.
  *    {string} trigger: The DOM event that should trigger editing,
  *      defaults to "click"
  * @param {function} aCallback
  *        Called when the editor is activated.
+ * @return {function} function which calls aCallback
  */
 function editableItem(aOptions, aCallback)
 {
   let trigger = aOptions.trigger || "click"
   let element = aOptions.element;
   element.addEventListener(trigger, function(evt) {
     if (evt.target.nodeName !== "a") {
       let win = this.ownerDocument.defaultView;
@@ -143,16 +146,23 @@ function editableItem(aOptions, aCallbac
       element.addEventListener("mouseup", cleanup, false);
       element.addEventListener("mouseout", cleanup, false);
     }
   }, false);
 
   // Mark the element editable field for tab
   // navigation while editing.
   element._editable = true;
+
+  // Save the trigger type so we can dispatch this later
+  element._trigger = trigger;
+
+  return function turnOnEditMode() {
+    aCallback(element);
+  }
 }
 
 exports.editableItem = this.editableItem;
 
 /*
  * Various API consumers (especially tests) sometimes want to grab the
  * inplaceEditor expando off span elements. However, when each global has its
  * own compartment, those expandos live on Xray wrappers that are only visible
@@ -764,27 +774,27 @@ InplaceEditor.prototype = {
     this._updateSize();
     // This emit is mainly for the purpose of making the test flow simpler.
     this.emit("after-suggest");
   },
 
   /**
    * Call the client's done handler and clear out.
    */
-  _apply: function InplaceEditor_apply(aEvent)
+  _apply: function InplaceEditor_apply(aEvent, direction)
   {
     if (this._applied) {
       return;
     }
 
     this._applied = true;
 
     if (this.done) {
       let val = this.input.value.trim();
-      return this.done(this.cancelled ? this.initial : val, !this.cancelled);
+      return this.done(this.cancelled ? this.initial : val, !this.cancelled, direction);
     }
 
     return null;
   },
 
   /**
    * Handle loss of focus by calling done if it hasn't been called yet.
    */
@@ -940,32 +950,34 @@ InplaceEditor.prototype = {
         }
         else if (this.popup && this.popup.isOpen) {
           aEvent.preventDefault();
           this._cycleCSSSuggestion(aEvent.shiftKey, true);
           return;
         }
       }
 
-      this._apply();
+      this._apply(aEvent, direction);
 
       // Close the popup if open
       if (this.popup && this.popup.isOpen) {
         this.popup.hidePopup();
       }
 
       if (direction !== null && focusManager.focusedElement === input) {
         // If the focused element wasn't changed by the done callback,
         // move the focus as requested.
         let next = moveFocus(this.doc.defaultView, direction);
 
         // If the next node to be focused has been tagged as an editable
-        // node, send it a click event to trigger
+        // node, trigger editing using the configured event
         if (next && next.ownerDocument === this.doc && next._editable) {
-          next.click();
+          let e = this.doc.createEvent('Event');
+          e.initEvent(next._trigger, true, true);
+          next.dispatchEvent(e);
         }
       }
 
       this._clear();
     } else if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE) {
       // Cancel and blur ourselves.
       // Now we don't want to suggest anything as we are moving out.
       this._preventSuggestions = true;
--- a/browser/devtools/styleeditor/StyleSheetEditor.jsm
+++ b/browser/devtools/styleeditor/StyleSheetEditor.jsm
@@ -436,17 +436,17 @@ StyleSheetEditor.prototype = {
     }
     this.focus();
   },
 
   /**
    * Toggled the disabled state of the underlying stylesheet.
    */
   toggleDisabled: function() {
-    this.styleSheet.toggleDisabled();
+    this.styleSheet.toggleDisabled().then(null, Cu.reportError);
   },
 
   /**
    * Queue a throttled task to update the live style sheet.
    *
    * @param boolean immediate
    *        Optional. If true the update is performed immediately.
    */
@@ -478,17 +478,18 @@ StyleSheetEditor.prototype = {
                              // when it is enabled back. @see enableStylesheet
 
     if (this.sourceEditor) {
       this._state.text = this.sourceEditor.getText();
     }
 
     let transitionsEnabled = Services.prefs.getBoolPref(TRANSITION_PREF);
 
-    this.styleSheet.update(this._state.text, transitionsEnabled);
+    this.styleSheet.update(this._state.text, transitionsEnabled)
+                   .then(null, Cu.reportError);
   },
 
   /**
    * Handle mousemove events, calling _highlightSelectorAt after a delay only
    * and reseting the delay everytime.
    */
   _onMouseMove: function(e) {
     this.highlighter.hide();
--- a/browser/devtools/timeline/test/browser_timeline_overview-update.js
+++ b/browser/devtools/timeline/test/browser_timeline_overview-update.js
@@ -49,23 +49,26 @@ add_task(function*() {
   is(TimelineView.memoryOverview.selectionEnabled, false,
     "The selection should still not be enabled for the memory overview (4).");
   is(TimelineView.memoryOverview.hasSelection(), false,
     "The memory overview should not have a selection while recording.");
 
   yield TimelineController.toggleRecording();
   ok(true, "Recording has ended.");
 
-  is(TimelineController.getMarkers().length, 0,
-    "There are no markers available.");
+  // TODO: Re-enable this assertion as part of bug 1120830
+  // is(TimelineController.getMarkers().length, 0,
+  //  "There are no markers available.");
   isnot(TimelineController.getMemory().length, 0,
     "There are some memory measurements available.");
 
   is(TimelineView.markersOverview.selectionEnabled, true,
     "The selection should now be enabled for the markers overview.");
-  is(TimelineView.markersOverview.hasSelection(), false,
-    "The markers overview should not have a selection after recording.");
+  // TODO: Re-enable this assertion as part of bug 1120830
+  // is(TimelineView.markersOverview.hasSelection(), false,
+  //  "The markers overview should not have a selection after recording.");
 
   is(TimelineView.memoryOverview.selectionEnabled, true,
     "The selection should now be enabled for the memory overview.");
-  is(TimelineView.memoryOverview.hasSelection(), false,
-    "The memory overview should not have a selection after recording.");
+  // TODO: Re-enable this assertion as part of bug 1120830
+  // is(TimelineView.memoryOverview.hasSelection(), false,
+  //  "The memory overview should not have a selection after recording.");
 });
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -3550,18 +3550,18 @@ JSTerm.prototype = {
   {
     let deferred = promise.defer();
 
     let onTabReady = () => {
       let window = this.sidebar.getWindowForTab("variablesview");
       deferred.resolve(window);
     };
 
-    let tab = this.sidebar.getTab("variablesview");
-    if (tab) {
+    let tabPanel = this.sidebar.getTabPanel("variablesview");
+    if (tabPanel) {
       if (this.sidebar.getCurrentTabID() == "variablesview") {
         onTabReady();
       }
       else {
         this.sidebar.once("variablesview-selected", onTabReady);
         this.sidebar.select("variablesview");
       }
     }
--- a/browser/installer/windows/nsis/installer.nsi
+++ b/browser/installer/windows/nsis/installer.nsi
@@ -584,19 +584,16 @@ Section "-Application" APP_IDX
         ; know the correct full path.
         ${LogMsg} "Added Quick Launch Shortcut: ${BrandFullName}.lnk"
         GetFunctionAddress $0 AddQuickLaunchShortcut
         UAC::ExecCodeSegment $0
       ${EndIf}
     ${EndUnless}
   ${EndIf}
 
-  ; Add the Firewall entries during install
-  Call AddFirewallEntries
-
 !ifdef MOZ_MAINTENANCE_SERVICE
   ${If} $TmpVal == "HKLM"
     ; Add the registry keys for allowed certificates.
     ${AddMaintCertKeys}
   ${EndIf}
 !endif
 SectionEnd
 
@@ -622,16 +619,19 @@ Section "-InstallEndCleanup"
     ${EndIf}
     ; Adds a pinned Task Bar shortcut (see MigrateTaskBarShortcut for details).
     ${MigrateTaskBarShortcut}
   ${EndUnless}
 
   ${GetShortcutsLogPath} $0
   WriteIniStr "$0" "TASKBAR" "Migrated" "true"
 
+  ; Add the Firewall entries during install
+  Call AddFirewallEntries
+
   ; Refresh desktop icons
   System::Call "shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_DWORDFLUSH}, i 0, i 0)"
 
   ${InstallEndCleanupCommon}
 
   ${If} $PreventRebootRequired == "true"
     SetRebootFlag false
   ${EndIf}
--- a/browser/installer/windows/nsis/shared.nsh
+++ b/browser/installer/windows/nsis/shared.nsh
@@ -933,17 +933,17 @@ FunctionEnd
     ReadRegStr $2 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$1" "ProfileImagePath"
     System::Call "advapi32::RegLoadKey(i ${HKEY_USERS}, t 'User-$0', t '$2\ntuser.dat')"
     System::Call "advapi32::RegLoadKey(i ${HKEY_USERS}, t 'User-$0_Classes', t '$2\AppData\Local\Microsoft\Windows\UsrClass.dat')"
     IntOp $0 $0 + 1
   ${Loop}
 !macroend
 !define MountRegistryIntoHKU "!insertmacro MountRegistryIntoHKU"
 !define un.MountRegistryIntoHKU "!insertmacro MountRegistryIntoHKU"
-;
+
 ; Unmounts all user ntuser.dat files into the registry as a subkey of HKU
 !macro UnmountRegistryIntoHKU
   ; $0 is used as an index for HKEY_USERS enumeration
   StrCpy $0 0
   ${Do}
     EnumRegKey $1 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" $0
     ${If} $1 == ""
       ${Break}
@@ -1551,16 +1551,82 @@ FunctionEnd
   Push "sandboxbroker.dll"
   Push "xpcom.dll"
   Push "crashreporter.exe"
   Push "updater.exe"
   Push "${FileMainEXE}"
 !macroend
 !define PushFilesToCheck "!insertmacro PushFilesToCheck"
 
+
+; Pushes the string "true" to the top of the stack if the Firewall service is
+; running and pushes the string "false" to the top of the stack if it isn't.
+!define SC_MANAGER_ALL_ACCESS 0x3F
+!define SERVICE_QUERY_CONFIG 0x0001
+!define SERVICE_QUERY_STATUS 0x0004
+!define SERVICE_RUNNING 0x4
+
+!macro IsFirewallSvcRunning
+  Push $R9
+  Push $R8
+  Push $R7
+  Push $R6
+  Push "false"
+
+  System::Call 'advapi32::OpenSCManagerW(n, n, i ${SC_MANAGER_ALL_ACCESS}) i.R6'
+  ${If} $R6 != 0
+    ; MpsSvc is the Firewall service on Windows Vista and above.
+    ; When opening the service with SERVICE_QUERY_CONFIG the return value will
+    ; be 0 if the service is not installed.
+    System::Call 'advapi32::OpenServiceW(i R6, t "MpsSvc", i ${SERVICE_QUERY_CONFIG}) i.R7'
+    ${If} $R7 != 0
+      System::Call 'advapi32::CloseServiceHandle(i R7) n'
+      ; Open the service with SERVICE_QUERY_CONFIG so its status can be queried.
+      System::Call 'advapi32::OpenServiceW(i R6, t "MpsSvc", i ${SERVICE_QUERY_STATUS}) i.R7'
+    ${Else}
+      ; SharedAccess is the Firewall service on Windows XP.
+      ; When opening the service with SERVICE_QUERY_CONFIG the return value will
+      ; be 0 if the service is not installed.
+      System::Call 'advapi32::OpenServiceW(i R6, t "SharedAccess", i ${SERVICE_QUERY_CONFIG}) i.R7'
+      ${If} $R7 != 0
+        System::Call 'advapi32::CloseServiceHandle(i R7) n'
+        ; Open the service with SERVICE_QUERY_CONFIG so its status can be
+        ; queried.
+        System::Call 'advapi32::OpenServiceW(i R6, t "SharedAccess", i ${SERVICE_QUERY_STATUS}) i.R7'
+      ${EndIf}
+    ${EndIf}
+    ; Did the calls to OpenServiceW succeed?
+    ${If} $R7 != 0
+      System::Call '*(i,i,i,i,i,i,i) i.R9'
+      ; Query the current status of the service.
+      System::Call 'advapi32::QueryServiceStatus(i R7, i $R9) i'
+      System::Call '*$R9(i, i.R8)'
+      System::Free $R9
+      System::Call 'advapi32::CloseServiceHandle(i R7) n'
+      IntFmt $R8 "0x%X" $R8
+      ${If} $R8 == ${SERVICE_RUNNING}
+        Pop $R9
+        Push "true"
+      ${EndIf}
+    ${EndIf}
+    System::Call 'advapi32::CloseServiceHandle(i R6) n'
+  ${EndIf}
+
+  Exch 1
+  Pop $R6
+  Exch 1
+  Pop $R7
+  Exch 1
+  Pop $R8
+  Exch 1
+  Pop $R9
+!macroend
+!define IsFirewallSvcRunning "!insertmacro IsFirewallSvcRunning"
+!define un.IsFirewallSvcRunning "!insertmacro IsFirewallSvcRunning"
+
 ; Sets this installation as the default browser by setting the registry keys
 ; under HKEY_CURRENT_USER via registry calls and using the AppAssocReg NSIS
 ; plugin for Vista and above. This is a function instead of a macro so it is
 ; easily called from an elevated instance of the binary. Since this can be
 ; called by an elevated instance logging is not performed in this function.
 Function SetAsDefaultAppUserHKCU
   ; Only set as the user's StartMenuInternet browser if the StartMenuInternet
   ; registry keys are for this install.
@@ -1614,18 +1680,23 @@ FunctionEnd
 ; Helper for updating the shortcut application model IDs.
 Function FixShortcutAppModelIDs
   ${If} ${AtLeastWin7}
   ${AndIf} "$AppUserModelID" != ""
     ${UpdateShortcutAppModelIDs} "$INSTDIR\${FileMainEXE}" "$AppUserModelID" $0
   ${EndIf}
 FunctionEnd
 
+; Helper for adding Firewall exceptions during install and after app update.
 Function AddFirewallEntries
-	liteFirewallW::AddRule "$INSTDIR\${FileMainEXE}" "${BrandShortName} ($INSTDIR)"
+  ${IsFirewallSvcRunning}
+  Pop $0
+  ${If} "$0" == "true"
+    liteFirewallW::AddRule "$INSTDIR\${FileMainEXE}" "${BrandShortName} ($INSTDIR)"
+  ${EndIf}
 FunctionEnd
 
 ; The !ifdef NO_LOG prevents warnings when compiling the installer.nsi due to
 ; this function only being used by the uninstaller.nsi.
 !ifdef NO_LOG
 
 Function SetAsDefaultAppUser
   ; On Win8, we want to avoid having a UAC prompt since we'll already have
--- a/browser/installer/windows/nsis/uninstaller.nsi
+++ b/browser/installer/windows/nsis/uninstaller.nsi
@@ -459,18 +459,16 @@ Section "Uninstall"
         FileOpen $0 "$INSTDIR\${FileMainEXE}.moz-delete" w
         FileWrite $0 "Will be deleted on restart"
         Delete /REBOOTOK "$INSTDIR\${FileMainEXE}.moz-delete"
         FileClose $0
       ${EndUnless}
     ${EndIf}
   ${EndIf}
 
-	liteFirewallW::RemoveRule "$INSTDIR\${FileMainEXE}" "${BrandShortName} ($INSTDIR)"
-
   ; Refresh desktop icons otherwise the start menu internet item won't be
   ; removed and other ugly things will happen like recreation of the app's
   ; clients registry key by the OS under some conditions.
   System::Call "shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i 0, i 0, i 0)"
 
 !ifdef MOZ_MAINTENANCE_SERVICE
   ; Get the path the allowed cert is at and remove it
   ; Keep this block of code last since it modfies the reg view
@@ -484,16 +482,21 @@ Section "Uninstall"
     DeleteRegKey HKLM "$MaintCertKey"
     ${If} ${RunningX64}
       SetRegView lastused
     ${EndIf}
   ${EndIf}
   Call un.UninstallServiceIfNotUsed
 !endif
 
+  ${un.IsFirewallSvcRunning}
+  Pop $0
+  ${If} "$0" == "true"
+    liteFirewallW::RemoveRule "$INSTDIR\${FileMainEXE}" "${BrandShortName} ($INSTDIR)"
+  ${EndIf}
 SectionEnd
 
 ################################################################################
 # Language
 
 !insertmacro MOZ_MUI_LANGUAGE 'baseLocale'
 !verbose push
 !verbose 3
--- a/browser/locales/en-US/chrome/browser/devtools/animationinspector.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/animationinspector.properties
@@ -21,16 +21,21 @@ player.animationNameLabel=Animation:
 # transition
 player.transitionNameLabel=Transition
 
 # LOCALIZATION NOTE (player.animationDurationLabel):
 # This string is displayed in each animation player widget. It is the label
 # displayed before the animation duration.
 player.animationDurationLabel=Duration:
 
+# LOCALIZATION NOTE (player.animationDelayLabel):
+# This string is displayed in each animation player widget. It is the label
+# displayed before the animation delay.
+player.animationDelayLabel=Delay:
+
 # LOCALIZATION NOTE (player.animationIterationCountLabel):
 # This string is displayed in each animation player widget. It is the label
 # displayed before the number of times the animation is set to repeat.
 player.animationIterationCountLabel=Repeats:
 
 # LOCALIZATION NOTE (player.infiniteIterationCount):
 # In case the animation repeats infinitely, this string is displayed next to the
 # player.animationIterationCountLabel string, instead of a number.
--- a/browser/locales/en-US/chrome/browser/devtools/toolbox.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/toolbox.properties
@@ -66,16 +66,26 @@ scratchpad.keycode=VK_F4
 # Used for toggling the browser console from the detached toolbox window
 # Needs to match browserConsoleCmd.commandkey from browser.dtd
 browserConsoleCmd.commandkey=j
 
 # LOCALIZATION NOTE (pickButton.tooltip)
 # This is the tooltip of the pick button in the toolbox toolbar
 pickButton.tooltip=Pick an element from the page
 
+# LOCALIZATION NOTE (sidebar.showAllTabs.label)
+# This is the label shown in the show all tabs button in the tabbed side
+# bar, when there's no enough space to show all tabs at once
+sidebar.showAllTabs.label=…
+
+# LOCALIZATION NOTE (sidebar.showAllTabs.tooltip)
+# This is the tooltip shown when hover over the '…' button in the tabbed side
+# bar, when there's no enough space to show all tabs at once
+sidebar.showAllTabs.tooltip=All tabs
+
 # LOCALIZATION NOTE (options.darkTheme.label)
 # Used as a label for dark theme
 options.darkTheme.label=Dark theme
 
 # LOCALIZATION NOTE (options.lightTheme.label)
 # Used as a label for light theme
 options.lightTheme.label=Light theme
 
--- a/browser/modules/WindowsPreviewPerTab.jsm
+++ b/browser/modules/WindowsPreviewPerTab.jsm
@@ -137,16 +137,17 @@ function snapRectAtScale(r, scale) {
  */
 function PreviewController(win, tab) {
   this.win = win;
   this.tab = tab;
   this.linkedBrowser = tab.linkedBrowser;
   this.preview = this.win.createTabPreview(this);
 
   this.linkedBrowser.addEventListener("MozAfterPaint", this, false);
+  this.linkedBrowser.addEventListener("resize", this, false);
   this.tab.addEventListener("TabAttrModified", this, false);
 
   XPCOMUtils.defineLazyGetter(this, "canvasPreview", function () {
     let canvas = this.win.win.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
     canvas.mozOpaque = true;
     return canvas;
   });
 
@@ -166,16 +167,17 @@ function PreviewController(win, tab) {
   });
 }
 
 PreviewController.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsITaskbarPreviewController,
                                          Ci.nsIDOMEventListener]),
   destroy: function () {
     this.tab.removeEventListener("TabAttrModified", this, false);
+    this.linkedBrowser.removeEventListener("resize", this, false);
     this.linkedBrowser.removeEventListener("MozAfterPaint", this, false);
 
     // Break cycles, otherwise we end up leaking the window with everything
     // attached to it.
     delete this.win;
     delete this.preview;
     delete this.dirtyRegion;
   },
@@ -196,48 +198,61 @@ PreviewController.prototype = {
       rects.push(r);
     }
     return rects;
   },
 
   // Resizes the canvasPreview to 0x0, essentially freeing its memory.
   // updateCanvasPreview() will detect the size mismatch as a resize event
   // the next time it is called.
-  resetCanvasPreview: function () {
-    this.canvasPreview.width = 0;
-    this.canvasPreview.height = 0;
+  resetCanvasPreview: function () this.resizeCanvasPreview(0, 0),
+
+  resizeCanvasPreview: function (width, height) {
+    this.canvasPreview.width = width;
+    this.canvasPreview.height = height;
+  },
+
+  get wasResizedSinceLastPreview () {
+    let bx = this.linkedBrowser.boxObject;
+    return bx.width != this.canvasPreview.width ||
+           bx.height != this.canvasPreview.height;
   },
 
   get zoom() {
     // Note that winutils.fullZoom accounts for "quantization" of the zoom factor
     // from nsIContentViewer due to conversion through appUnits.
     // We do -not- want screenPixelsPerCSSPixel here, because that would -also-
     // incorporate any scaling that is applied due to hi-dpi resolution options.
     return this.winutils.fullZoom;
   },
 
   // Updates the controller's canvas with the parts of the <browser> that need
   // to be redrawn.
   updateCanvasPreview: function () {
     let win = this.linkedBrowser.contentWindow;
     let bx = this.linkedBrowser.boxObject;
+    // If we resized then we need to flush layout so that the previews are up
+    // to date. Layout flushing for resizes is deferred for background tabs so
+    // we may need to force it. (bug 526620)
+    let flushLayout = this.wasResizedSinceLastPreview;
     // Check for resize
-    if (bx.width != this.canvasPreview.width ||
-        bx.height != this.canvasPreview.height) {
+    if (flushLayout) {
       // Invalidate the entire area and repaint
       this.onTabPaint({left:0, top:0, right:win.innerWidth, bottom:win.innerHeight});
-      this.canvasPreview.width = bx.width;
-      this.canvasPreview.height = bx.height;
+      this.resizeCanvasPreview(bx.width, bx.height);
     }
 
     // Draw dirty regions
     let ctx = this.canvasPreview.getContext("2d");
     let scale = this.zoom;
 
     let flags = this.canvasPreviewFlags;
+    if (flushLayout)
+      flags &= ~Ci.nsIDOMCanvasRenderingContext2D.DRAWWINDOW_DO_NOT_FLUSH;
+
     // The dirty region may include parts that are offscreen so we clip to the
     // canvas area.
     this.dirtyRegion.intersectRect(0, 0, win.innerWidth, win.innerHeight);
     this.dirtyRects.forEach(function (r) {
       // We need to snap the rectangle to be pixel aligned in the destination
       // coordinate space. Otherwise natively themed widgets might not draw.
       snapRectAtScale(r, scale);
       let x = r.x;
@@ -367,16 +382,29 @@ PreviewController.prototype = {
         }
         let preview = this.preview;
         if (preview.visible)
           preview.invalidate();
         break;
       case "TabAttrModified":
         this.updateTitleAndTooltip();
         break;
+      case "resize":
+        // We need to invalidate our window's other tabs' previews since layout
+        // due to resizing is delayed for background tabs. Note that this
+        // resize may not be the first after the main window has been resized -
+        // the user may be switching to our tab which forces the resize.
+        this.win.previews.forEach(function (p) {
+          let controller = p.controller.wrappedJSObject;
+          if (controller.wasResizedSinceLastPreview) {
+            controller.resetCanvasPreview();
+            p.invalidate();
+          }
+        });
+        break;
     }
   }
 };
 
 XPCOMUtils.defineLazyGetter(PreviewController.prototype, "canvasPreviewFlags",
   function () { let canvasInterface = Ci.nsIDOMCanvasRenderingContext2D;
                 return canvasInterface.DRAWWINDOW_DRAW_VIEW
                      | canvasInterface.DRAWWINDOW_DRAW_CARET
--- a/browser/themes/shared/devedition.inc.css
+++ b/browser/themes/shared/devedition.inc.css
@@ -2,17 +2,18 @@
 % 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/.
 
 /* devedition.css is loaded in browser.xul after browser.css when it is
    preffed on.  The bulk of the styling is here in the shared file, but
    there are overrides for each platform in their devedition.css files. */
 
 :root {
-  --space-above-tabbar: 1px;
+  --tab-toolbar-navbar-overlap: 0px;
+  --space-above-tabbar: 0px;
   --toolbarbutton-text-shadow: none;
   --panel-ui-button-background-size: 1px calc(100% - 1px);
   --panel-ui-button-background-position: 1px 0px;
 }
 
 :root[devtoolstheme="dark"] {
   /* Chrome */
   --chrome-background-color: #1C2126;
@@ -27,19 +28,19 @@
 
   /* Tabs */
   --tabs-toolbar-color: #F5F7FA;
   --tab-background-color: #1C2126;
   --tab-hover-background-color: #07090a;
   --tab-separator-color: #474C50;
   --tab-selection-color: #f5f7fa;
   --tab-selection-background-color: #1a4666;
-  --tab-selection-box-shadow: 0 2px 0 #d7f1ff inset,
-                                     0 8px 3px -5px #2b82bf inset,
-                                     0 -1px 0 rgba(0,0,0,.2) inset;
+  --tab-selection-box-shadow: 0 2px 0 #D7F1FF inset,
+                              0 -2px 0 rgba(0,0,0,.05) inset,
+                              0 -1px 0 rgba(0,0,0,.3) inset;
   --pinned-tab-glow: radial-gradient(22px at center calc(100% - 2px), rgba(76,158,217,0.9) 13%, rgba(0,0,0,0.4) 16%, transparent 70%);
 
   /* Toolbar buttons */
   --toolbarbutton-hover-background: rgba(25,33, 38,.6) linear-gradient(rgba(25,33,38,.6), rgba(25,33,38,.6)) padding-box;
   --toolbarbutton-hover-boxshadow: none;
   --toolbarbutton-hover-bordercolor: rgba(25,33,38,.6);
   --toolbarbutton-active-background: rgba(25,33,38,1) linear-gradient(rgba(25,33,38,1), rgba(25,33,38,1)) border-box;
   --toolbarbutton-active-boxshadow: none;
@@ -87,19 +88,19 @@
   --chrome-selection-color: #f5f7fa;
   --chrome-selection-background-color: #4c9ed9;
 
   --tab-background-color: #E3E4E6;
   --tab-hover-background-color: #D7D8DA;
   --tab-separator-color: #C6C6C7;
   --tab-selection-color: #f5f7fa;
   --tab-selection-background-color: #4c9ed9;
-  --tab-selection-box-shadow: 0 2px 0 #d7f1ff inset,
-                              0 8px 3px -5px #319BDB inset,
-                              0 -1px 0 #2A7CB1 inset;
+  --tab-selection-box-shadow: 0 2px 0 #9FDFFF inset,
+                              0 -2px 0 rgba(0,0,0,.05) inset,
+                              0 -1px 0 rgba(0,0,0,.2) inset;
   --pinned-tab-glow: radial-gradient(22px at center calc(100% - 2px), rgba(76,158,217,0.9) 13%, transparent 16%);
 
 
   /* Toolbar buttons */
   --toolbarbutton-hover-background: #D7D7D8;
   --toolbarbutton-hover-boxshadow: none;
   --toolbarbutton-hover-bordercolor: rgba(0,0,0,0.1);
   --toolbarbutton-active-background: rgba(76,158,217,.5) linear-gradient(rgba(76,158,217,.5), rgba(76,158,217,.5)) border-box
@@ -172,17 +173,17 @@
 
 #navigator-toolbox::after {
   background: var(--chrome-navigator-toolbox-separator-color);
 }
 
 #navigator-toolbox > toolbar:not(#TabsToolbar):not(#toolbar-menubar),
 .browserContainer > findbar,
 #browser-bottombox {
-  background: var(--chrome-secondary-background-color) !important;
+  background-color: var(--chrome-secondary-background-color) !important;
   color: var(--chrome-color);
 }
 
 #navigator-toolbox .toolbarbutton-1,
 .browserContainer > findbar .findbar-button,
 #PlacesToolbar toolbarbutton.bookmark-item {
   color: var(--chrome-color);
   text-shadow: var(--toolbarbutton-text-shadow);
@@ -222,19 +223,20 @@ window:not([chromehidden~="toolbar"]) #u
   border-radius: 0;
   border: none;
   background: transparent;
 }
 
 /* Nav bar specific stuff */
 #nav-bar {
   margin-top: 0 !important;
-  border: none !important;
+  border-top: none !important;
+  border-bottom: none !important;
   border-radius: 0 !important;
-  box-shadow: 0 1px var(--chrome-nav-bar-separator-color) inset !important;
+  box-shadow: 0 -1px var(--chrome-nav-bar-separator-color) !important;
   background-image: none !important;
 }
 
 /* No extra vertical padding for nav bar */
 #nav-bar-customization-target,
 #nav-bar {
   padding-top: 0;
   padding-bottom: 0;
@@ -283,17 +285,16 @@ searchbar:not([oneoffui]) .search-go-but
 .tabbrowser-tab {
   /* We normally rely on other tab elements for pointer events, but this
      theme hides those so we need it set here instead */
   pointer-events: auto;
   background-color: var(--tab-background-color);
 }
 
 .tabbrowser-tab[pinned][titlechanged]:not([selected="true"]) > .tab-stack > .tab-content {
-  /* The -2px in `calc` is the height of `tabToolbarNavbarOverlap` plus a 1px offset from the center */
   background-image: var(--pinned-tab-glow);
   background-position: center;
   background-size: 100%;
 }
 
 .tabbrowser-arrowscrollbox > .scrollbutton-down:not([disabled]):hover,
 .tabbrowser-arrowscrollbox > .scrollbutton-up:not([disabled]):hover,
 .tabbrowser-tab:hover {
@@ -306,21 +307,14 @@ searchbar:not([oneoffui]) .search-go-but
   box-shadow: var(--tab-selection-box-shadow);
 }
 
 /* Don't need space for the tab curves (66px - 30px) */
 .tabs-newtab-button {
   width: 36px;
 }
 
-/* Override @tabToolbarNavbarOverlap@ */
-#TabsToolbar .toolbarbutton-1,
-.tabbrowser-arrowscrollbox > .scrollbutton-up,
-.tabbrowser-arrowscrollbox > .scrollbutton-down {
-  margin-bottom: 0;
-}
-
 #TabsToolbar > #new-tab-button:hover,
 .tabs-newtab-button:hover {
   /* Important needed because !important is used in browser.css */
   background-color: var(--tab-hover-background-color) !important;
   background-image: none;
 }
--- a/browser/themes/shared/devtools/dark-theme.css
+++ b/browser/themes/shared/devtools/dark-theme.css
@@ -186,17 +186,18 @@
 }
 
 .variable-or-property .token-domnode {
   font-weight: bold;
 }
 
 .theme-toolbar,
 .devtools-toolbar,
-.devtools-sidebar-tabs > tabs,
+.devtools-sidebar-tabs tabs,
+.devtools-sidebar-alltabs,
 .CodeMirror-dialog { /* General toolbar styling */
   color: var(--theme-body-color-alt);
   background-color: var(--theme-toolbar-background);
   border-color: hsla(210,8%,5%,.6);
 }
 
 .theme-fg-contrast { /* To be used for text on theme-bg-contrast */
   color: black;
--- a/browser/themes/shared/devtools/light-theme.css
+++ b/browser/themes/shared/devtools/light-theme.css
@@ -189,17 +189,18 @@
 }
 
 .theme-fg-contrast { /* To be used for text on theme-bg-contrast */
   color: black;
 }
 
 .theme-toolbar,
 .devtools-toolbar,
-.devtools-sidebar-tabs > tabs,
+.devtools-sidebar-tabs tabs,
+.devtools-sidebar-alltabs,
 .CodeMirror-dialog { /* General toolbar styling */
   color: var(--theme-body-color-alt);
   background-color: var(--theme-toolbar-background);
   border-color: var(--theme-splitter-color);
 }
 
 .ruleview-colorswatch,
 .computedview-colorswatch,
--- a/browser/themes/shared/devtools/toolbars.inc.css
+++ b/browser/themes/shared/devtools/toolbars.inc.css
@@ -6,17 +6,18 @@
 %filter substitution
 %define smallSeparatorDark linear-gradient(transparent 15%, #5a6169 15%, #5a6169 85%, transparent 85%)
 %define smallSeparatorLight linear-gradient(transparent 15%, #aaa 15%, #aaa 85%, transparent 85%)
 %define solidSeparatorDark linear-gradient(#2d5b7d, #2d5b7d)
 %define solidSeparatorLight linear-gradient(#aaa, #aaa)
 
 /* Toolbars */
 .devtools-toolbar,
-.devtools-sidebar-tabs > tabs {
+.devtools-sidebar-tabs tabs,
+.devtools-sidebar-alltabs {
   -moz-appearance: none;
   padding: 0;
   border-width: 0;
   border-bottom-width: 1px;
   border-style: solid;
   height: 24px;
   line-height: 24px;
   box-sizing: border-box;
@@ -411,29 +412,40 @@
   border: 0;
 }
 
 .theme-light .devtools-sidebar-tabs > tabpanels {
   background: var(--theme-sidebar-background);
   color: var(--theme-body-color);
 }
 
-.devtools-sidebar-tabs > tabs {
+.devtools-sidebar-tabs tabs {
   position: static;
   font: inherit;
   margin-bottom: 0;
   overflow: hidden;
 }
 
-.devtools-sidebar-tabs > tabs > .tabs-right,
-.devtools-sidebar-tabs > tabs > .tabs-left {
+.devtools-sidebar-alltabs {
+  margin: 0;
+  border-width: 0 0 1px 0;
+  -moz-border-start-width: 1px;
+  border-style: solid;
+}
+
+.devtools-sidebar-alltabs dropmarker {
   display: none;
 }
 
-.devtools-sidebar-tabs > tabs > tab {
+.devtools-sidebar-tabs tabs > .tabs-right,
+.devtools-sidebar-tabs tabs > .tabs-left {
+  display: none;
+}
+
+.devtools-sidebar-tabs tabs > tab {
   -moz-appearance: none;
   /* We want to match the height of a toolbar with a toolbarbutton
    * First, we need to replicated the padding of toolbar (4px),
    * then we need to take the border of the buttons into account (1px).
    */
   padding: 0 3px;
   margin: 0;
   min-width: 78px;
@@ -444,80 +456,80 @@
   border-width: 0;
   -moz-border-start-width: 1px;
   border-style: solid;
   border-radius: 0;
   position: static;
   text-shadow: none;
 }
 
-.devtools-sidebar-tabs > tabs > tab:first-child {
+.devtools-sidebar-tabs tabs > tab:first-child {
   -moz-border-start-width: 0;
 }
 
-.theme-dark .devtools-sidebar-tabs > tabs > tab {
+.theme-dark .devtools-sidebar-tabs tabs > tab {
   border-image: @smallSeparatorDark@ 1 1;
 }
 
-.theme-dark .devtools-sidebar-tabs > tabs > tab:hover {
+.theme-dark .devtools-sidebar-tabs tabs > tab:hover {
   background: hsla(206,37%,4%,.2);
   border-image: @smallSeparatorDark@ 1 1;
 }
 
-.theme-dark .devtools-sidebar-tabs > tabs > tab:hover:active {
+.theme-dark .devtools-sidebar-tabs tabs > tab:hover:active {
   background: hsla(206,37%,4%,.4);
   border-image: @smallSeparatorDark@ 1 1;
 }
 
-.theme-dark .devtools-sidebar-tabs > tabs > tab[selected] + tab {
+.theme-dark .devtools-sidebar-tabs tabs > tab[selected] + tab {
   border-image: @solidSeparatorDark@ 1 1;
 }
 
-.theme-dark .devtools-sidebar-tabs > tabs > tab[selected] + tab:hover {
+.theme-dark .devtools-sidebar-tabs tabs > tab[selected] + tab:hover {
   background: hsla(206,37%,4%,.2);
   border-image: @solidSeparatorDark@ 1 1;
 }
 
-.theme-dark .devtools-sidebar-tabs > tabs > tab[selected] + tab:hover:active {
+.theme-dark .devtools-sidebar-tabs tabs > tab[selected] + tab:hover:active {
   background: hsla(206,37%,4%,.4);
   border-image: @solidSeparatorDark@ 1 1;
 }
 
-.theme-dark .devtools-sidebar-tabs > tabs > tab[selected],
-.theme-dark .devtools-sidebar-tabs > tabs > tab[selected]:hover:active {
+.theme-dark .devtools-sidebar-tabs tabs > tab[selected],
+.theme-dark .devtools-sidebar-tabs tabs > tab[selected]:hover:active {
   color: var(--theme-selection-color);
   background: #1d4f73;
   border-image: @solidSeparatorDark@ 1 1;
 }
 
-.theme-light .devtools-sidebar-tabs > tabs > tab {
+.theme-light .devtools-sidebar-tabs tabs > tab {
   border-image: @smallSeparatorLight@ 1 1;
 }
 
-.theme-light .devtools-sidebar-tabs > tabs > tab:hover {
+.theme-light .devtools-sidebar-tabs tabs > tab:hover {
   background: #ddd;
   border-image: @smallSeparatorLight@ 1 1;
 }
 
-.theme-light .devtools-sidebar-tabs > tabs > tab:hover:active {
+.theme-light .devtools-sidebar-tabs tabs > tab:hover:active {
   background: #ddd;
   border-image: @smallSeparatorLight@ 1 1;
 }
 
-.theme-light .devtools-sidebar-tabs > tabs > tab[selected] + tab {
+.theme-light .devtools-sidebar-tabs tabs > tab[selected] + tab {
   border-image: @solidSeparatorLight@;
 }
 
-.theme-light .devtools-sidebar-tabs > tabs > tab[selected] + tab:hover {
+.theme-light .devtools-sidebar-tabs tabs > tab[selected] + tab:hover {
   background: #ddd;
   border-image: @solidSeparatorLight@;
 }
 
-.theme-light .devtools-sidebar-tabs > tabs > tab[selected],
-.theme-light .devtools-sidebar-tabs > tabs > tab[selected]:hover:active {
+.theme-light .devtools-sidebar-tabs tabs > tab[selected],
+.theme-light .devtools-sidebar-tabs tabs > tab[selected]:hover:active {
   color: var(--theme-selection-color);
   background: #4c9ed9;
   border-image: @solidSeparatorLight@;
 }
 
 /* Toolbox - moved from toolbox.css.
  * Rules that apply to the global toolbox like command buttons,
  * devtools tabs, docking buttons, etc. */
@@ -729,17 +741,17 @@
 }
 
 .devtools-tab {
   -moz-appearance: none;
   -moz-binding: url("chrome://global/content/bindings/general.xml#control-item");
   -moz-box-align: center;
   min-width: 32px;
   min-height: 24px;
-  max-width: 127px;
+  max-width: 110px;
   margin: 0;
   padding: 0;
   border-style: solid;
   border-width: 0;
   -moz-border-start-width: 1px;
   -moz-box-align: center;
 }
 
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -10169,16 +10169,19 @@ nsDocShell::InternalLoad(nsIURI * aURI,
     }
 
     nsAutoString srcdoc;
     if (aFlags & INTERNAL_LOAD_FLAGS_IS_SRCDOC)
       srcdoc = aSrcdoc;
     else
       srcdoc = NullString();
 
+    mozilla::net::PredictorLearn(aURI, nullptr,
+                                 nsINetworkPredictor::LEARN_LOAD_TOPLEVEL,
+                                 this);
     mozilla::net::PredictorPredict(aURI, nullptr,
                                    nsINetworkPredictor::PREDICT_LOAD,
                                    this, nullptr);
 
     nsCOMPtr<nsIRequest> req;
     rv = DoURILoad(aURI, aReferrer,
                    !(aFlags & INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER),
                    aReferrerPolicy,
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -317,31 +317,32 @@ public:
 
   nsPIDOMWindow* GetParentObject() const
   {
     return GetWindow();
   }
 
   virtual JSObject* WrapObject(JSContext* cx) MOZ_OVERRIDE;
 
+  // GetWindowFromGlobal returns the inner window for this global, if
+  // any, else null.
+  static already_AddRefed<nsPIDOMWindow> GetWindowFromGlobal(JSObject* aGlobal);
+
 #ifdef MOZ_EME
   already_AddRefed<Promise>
   RequestMediaKeySystemAccess(const nsAString& aKeySystem,
                               const Optional<Sequence<MediaKeySystemOptions>>& aOptions,
                               ErrorResult& aRv);
 #endif
 
 private:
   virtual ~Navigator();
 
   bool CheckPermission(const char* type);
   static bool CheckPermission(nsPIDOMWindow* aWindow, const char* aType);
-  // GetWindowFromGlobal returns the inner window for this global, if
-  // any, else null.
-  static already_AddRefed<nsPIDOMWindow> GetWindowFromGlobal(JSObject* aGlobal);
 
   nsRefPtr<nsMimeTypeArray> mMimeTypes;
   nsRefPtr<nsPluginArray> mPlugins;
   nsRefPtr<Geolocation> mGeolocation;
   nsRefPtr<DesktopNotificationCenter> mNotification;
   nsRefPtr<battery::BatteryManager> mBatteryManager;
 #ifdef MOZ_B2G_FM
   nsRefPtr<FMRadio> mFMRadio;
new file mode 100644
--- /dev/null
+++ b/dom/broadcastchannel/BroadcastChannel.cpp
@@ -0,0 +1,760 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "BroadcastChannel.h"
+#include "BroadcastChannelChild.h"
+#include "mozilla/dom/BroadcastChannelBinding.h"
+#include "mozilla/dom/Navigator.h"
+#include "mozilla/dom/StructuredCloneUtils.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/Preferences.h"
+#include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
+
+#include "nsIAppsService.h"
+#include "nsIDocument.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsServiceManagerUtils.h"
+#include "nsISupportsPrimitives.h"
+
+#ifdef XP_WIN
+#undef PostMessage
+#endif
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+using namespace workers;
+
+class BroadcastChannelMessage MOZ_FINAL
+{
+public:
+  NS_INLINE_DECL_REFCOUNTING(BroadcastChannelMessage)
+
+  JSAutoStructuredCloneBuffer mBuffer;
+  StructuredCloneClosure mClosure;
+
+  BroadcastChannelMessage()
+  { }
+
+private:
+  ~BroadcastChannelMessage()
+  { }
+};
+
+namespace {
+
+void
+GetOrigin(nsIPrincipal* aPrincipal, nsAString& aOrigin, ErrorResult& aRv)
+{
+  MOZ_ASSERT(aPrincipal);
+
+  uint16_t appStatus = aPrincipal->GetAppStatus();
+
+  if (appStatus == nsIPrincipal::APP_STATUS_NOT_INSTALLED) {
+    nsAutoString tmp;
+    aRv = nsContentUtils::GetUTFOrigin(aPrincipal, tmp);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return;
+    }
+
+    aOrigin = tmp;
+    return;
+  }
+
+  uint32_t appId = aPrincipal->GetAppId();
+
+  // If we are in "app code", use manifest URL as unique origin since
+  // multiple apps can share the same origin but not same broadcast messages.
+  nsresult rv;
+  nsCOMPtr<nsIAppsService> appsService =
+    do_GetService("@mozilla.org/AppsService;1", &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(rv);
+    return;
+  }
+
+  appsService->GetManifestURLByLocalId(appId, aOrigin);
+}
+
+nsIPrincipal*
+GetPrincipalFromWorkerPrivate(WorkerPrivate* aWorkerPrivate)
+{
+  nsIPrincipal* principal = aWorkerPrivate->GetPrincipal();
+  if (principal) {
+    return principal;
+  }
+
+  // Walk up to our containing page
+  WorkerPrivate* wp = aWorkerPrivate;
+  while (wp->GetParent()) {
+    wp = wp->GetParent();
+  }
+
+  return wp->GetPrincipal();
+}
+
+class InitializeRunnable MOZ_FINAL : public WorkerMainThreadRunnable
+{
+public:
+  InitializeRunnable(WorkerPrivate* aWorkerPrivate, nsAString& aOrigin,
+                     PrincipalInfo& aPrincipalInfo, ErrorResult& aRv)
+    : WorkerMainThreadRunnable(aWorkerPrivate)
+    , mWorkerPrivate(GetCurrentThreadWorkerPrivate())
+    , mOrigin(aOrigin)
+    , mPrincipalInfo(aPrincipalInfo)
+    , mRv(aRv)
+  {
+    MOZ_ASSERT(mWorkerPrivate);
+  }
+
+  bool MainThreadRun() MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    nsIPrincipal* principal = GetPrincipalFromWorkerPrivate(mWorkerPrivate);
+    if (!principal) {
+      mRv.Throw(NS_ERROR_FAILURE);
+      return true;
+    }
+
+    bool isNullPrincipal;
+    mRv = principal->GetIsNullPrincipal(&isNullPrincipal);
+    if (NS_WARN_IF(mRv.Failed())) {
+      return true;
+    }
+
+    if (NS_WARN_IF(isNullPrincipal)) {
+      mRv.Throw(NS_ERROR_FAILURE);
+      return true;
+    }
+
+    mRv = PrincipalToPrincipalInfo(principal, &mPrincipalInfo);
+    if (NS_WARN_IF(mRv.Failed())) {
+      return true;
+    }
+
+    GetOrigin(principal, mOrigin, mRv);
+    if (NS_WARN_IF(mRv.Failed())) {
+      return true;
+    }
+
+    // Walk up to our containing page
+    WorkerPrivate* wp = mWorkerPrivate;
+    while (wp->GetParent()) {
+      wp = wp->GetParent();
+    }
+
+    // Window doesn't exist for some kind of workers (eg: SharedWorkers)
+    nsPIDOMWindow* window = wp->GetWindow();
+    if (!window) {
+      return true;
+    }
+
+    nsIDocument* doc = window->GetExtantDoc();
+    // No bfcache when BroadcastChannel is used.
+    if (doc) {
+      doc->DisallowBFCaching();
+    }
+
+    return true;
+  }
+
+private:
+  WorkerPrivate* mWorkerPrivate;
+  nsAString& mOrigin;
+  PrincipalInfo& mPrincipalInfo;
+  ErrorResult& mRv;
+};
+
+class PostMessageRunnable MOZ_FINAL : public nsICancelableRunnable
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  PostMessageRunnable(BroadcastChannelChild* aActor,
+                      BroadcastChannelMessage* aData)
+    : mActor(aActor)
+    , mData(aData)
+  {
+    MOZ_ASSERT(mActor);
+  }
+
+  NS_IMETHODIMP Run()
+  {
+    MOZ_ASSERT(mActor);
+    if (mActor->IsActorDestroyed()) {
+      return NS_OK;
+    }
+
+    ClonedMessageData message;
+
+    SerializedStructuredCloneBuffer& buffer = message.data();
+    buffer.data = mData->mBuffer.data();
+    buffer.dataLength = mData->mBuffer.nbytes();
+
+    PBackgroundChild* backgroundManager = mActor->Manager();
+    MOZ_ASSERT(backgroundManager);
+
+    const nsTArray<nsRefPtr<File>>& blobs = mData->mClosure.mBlobs;
+
+    if (!blobs.IsEmpty()) {
+      message.blobsChild().SetCapacity(blobs.Length());
+
+      for (uint32_t i = 0, len = blobs.Length(); i < len; ++i) {
+        PBlobChild* blobChild =
+          BackgroundChild::GetOrCreateActorForBlob(backgroundManager, blobs[i]);
+        MOZ_ASSERT(blobChild);
+
+        message.blobsChild().AppendElement(blobChild);
+      }
+    }
+
+    mActor->SendPostMessage(message);
+    return NS_OK;
+  }
+
+  NS_IMETHODIMP Cancel()
+  {
+    mActor = nullptr;
+    return NS_OK;
+  }
+
+private:
+  ~PostMessageRunnable() {}
+
+  nsRefPtr<BroadcastChannelChild> mActor;
+  nsRefPtr<BroadcastChannelMessage> mData;
+};
+
+NS_IMPL_ISUPPORTS(PostMessageRunnable, nsICancelableRunnable, nsIRunnable)
+
+class CloseRunnable MOZ_FINAL : public nsICancelableRunnable
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  explicit CloseRunnable(BroadcastChannel* aBC)
+    : mBC(aBC)
+  {
+    MOZ_ASSERT(mBC);
+  }
+
+  NS_IMETHODIMP Run()
+  {
+    mBC->Shutdown();
+    return NS_OK;
+  }
+
+  NS_IMETHODIMP Cancel()
+  {
+    mBC = nullptr;
+    return NS_OK;
+  }
+
+private:
+  ~CloseRunnable() {}
+
+  nsRefPtr<BroadcastChannel> mBC;
+};
+
+NS_IMPL_ISUPPORTS(CloseRunnable, nsICancelableRunnable, nsIRunnable)
+
+class TeardownRunnable MOZ_FINAL : public nsICancelableRunnable
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  explicit TeardownRunnable(BroadcastChannelChild* aActor)
+    : mActor(aActor)
+  {
+    MOZ_ASSERT(mActor);
+  }
+
+  NS_IMETHODIMP Run()
+  {
+    MOZ_ASSERT(mActor);
+    if (!mActor->IsActorDestroyed()) {
+      mActor->SendClose();
+    }
+    return NS_OK;
+  }
+
+  NS_IMETHODIMP Cancel()
+  {
+    mActor = nullptr;
+    return NS_OK;
+  }
+
+private:
+  ~TeardownRunnable() {}
+
+  nsRefPtr<BroadcastChannelChild> mActor;
+};
+
+NS_IMPL_ISUPPORTS(TeardownRunnable, nsICancelableRunnable, nsIRunnable)
+
+class BroadcastChannelFeature MOZ_FINAL : public workers::WorkerFeature
+{
+  BroadcastChannel* mChannel;
+
+public:
+  explicit BroadcastChannelFeature(BroadcastChannel* aChannel)
+    : mChannel(aChannel)
+  {
+    MOZ_COUNT_CTOR(BroadcastChannelFeature);
+  }
+
+  virtual bool Notify(JSContext* aCx, workers::Status aStatus) MOZ_OVERRIDE
+  {
+    if (aStatus >= Canceling) {
+      mChannel->Shutdown();
+    }
+
+    return true;
+  }
+
+private:
+  ~BroadcastChannelFeature()
+  {
+    MOZ_COUNT_DTOR(BroadcastChannelFeature);
+  }
+};
+
+class PrefEnabledRunnable MOZ_FINAL : public WorkerMainThreadRunnable
+{
+public:
+  explicit PrefEnabledRunnable(WorkerPrivate* aWorkerPrivate)
+    : WorkerMainThreadRunnable(aWorkerPrivate)
+    , mEnabled(false)
+  { }
+
+  bool MainThreadRun() MOZ_OVERRIDE
+  {
+    AssertIsOnMainThread();
+    mEnabled = Preferences::GetBool("dom.broadcastChannel.enabled", false);
+    return true;
+  }
+
+  bool IsEnabled() const
+  {
+    return mEnabled;
+  }
+
+private:
+  bool mEnabled;
+};
+
+} // anonymous namespace
+
+/* static */ bool
+BroadcastChannel::IsEnabled(JSContext* aCx, JSObject* aGlobal)
+{
+  if (NS_IsMainThread()) {
+    return Preferences::GetBool("dom.broadcastChannel.enabled", false);
+  }
+
+  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+  MOZ_ASSERT(workerPrivate);
+  workerPrivate->AssertIsOnWorkerThread();
+
+  nsRefPtr<PrefEnabledRunnable> runnable =
+    new PrefEnabledRunnable(workerPrivate);
+  runnable->Dispatch(workerPrivate->GetJSContext());
+
+  return runnable->IsEnabled();
+}
+
+BroadcastChannel::BroadcastChannel(nsPIDOMWindow* aWindow,
+                                   const PrincipalInfo& aPrincipalInfo,
+                                   const nsAString& aOrigin,
+                                   const nsAString& aChannel)
+  : DOMEventTargetHelper(aWindow)
+  , mWorkerFeature(nullptr)
+  , mPrincipalInfo(new PrincipalInfo(aPrincipalInfo))
+  , mOrigin(aOrigin)
+  , mChannel(aChannel)
+  , mIsKeptAlive(false)
+  , mInnerID(0)
+  , mState(StateActive)
+{
+  // Window can be null in workers
+}
+
+BroadcastChannel::~BroadcastChannel()
+{
+  Shutdown();
+  MOZ_ASSERT(!mWorkerFeature);
+}
+
+JSObject*
+BroadcastChannel::WrapObject(JSContext* aCx)
+{
+  return BroadcastChannelBinding::Wrap(aCx, this);
+}
+
+/* static */ already_AddRefed<BroadcastChannel>
+BroadcastChannel::Constructor(const GlobalObject& aGlobal,
+                              const nsAString& aChannel,
+                              ErrorResult& aRv)
+{
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
+  // Window is null in workers.
+
+  nsAutoString origin;
+  PrincipalInfo principalInfo;
+  WorkerPrivate* workerPrivate = nullptr;
+
+  if (NS_IsMainThread()) {
+    nsCOMPtr<nsIGlobalObject> incumbent = mozilla::dom::GetIncumbentGlobal();
+
+    if (!incumbent) {
+      aRv.Throw(NS_ERROR_FAILURE);
+      return nullptr;
+    }
+
+    nsIPrincipal* principal = incumbent->PrincipalOrNull();
+    if (!principal) {
+      aRv.Throw(NS_ERROR_UNEXPECTED);
+      return nullptr;
+    }
+
+    bool isNullPrincipal;
+    aRv = principal->GetIsNullPrincipal(&isNullPrincipal);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return nullptr;
+    }
+
+    if (NS_WARN_IF(isNullPrincipal)) {
+      aRv.Throw(NS_ERROR_FAILURE);
+      return nullptr;
+    }
+
+    GetOrigin(principal, origin, aRv);
+
+    aRv = PrincipalToPrincipalInfo(principal, &principalInfo);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return nullptr;
+    }
+
+    nsIDocument* doc = window->GetExtantDoc();
+    // No bfcache when BroadcastChannel is used.
+    if (doc) {
+      doc->DisallowBFCaching();
+    }
+  } else {
+    JSContext* cx = aGlobal.Context();
+    workerPrivate = GetWorkerPrivateFromContext(cx);
+    MOZ_ASSERT(workerPrivate);
+
+    nsRefPtr<InitializeRunnable> runnable =
+      new InitializeRunnable(workerPrivate, origin, principalInfo, aRv);
+    runnable->Dispatch(cx);
+  }
+
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  nsRefPtr<BroadcastChannel> bc =
+    new BroadcastChannel(window, principalInfo, origin, aChannel);
+
+  // Register this component to PBackground.
+  PBackgroundChild* actor = BackgroundChild::GetForCurrentThread();
+  if (actor) {
+    bc->ActorCreated(actor);
+  } else {
+    BackgroundChild::GetOrCreateForCurrentThread(bc);
+  }
+
+  if (!workerPrivate) {
+    MOZ_ASSERT(window);
+    MOZ_ASSERT(window->IsInnerWindow());
+    bc->mInnerID = window->WindowID();
+
+    // Register as observer for inner-window-destroyed.
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    if (obs) {
+      obs->AddObserver(bc, "inner-window-destroyed", false);
+    }
+  } else {
+    bc->mWorkerFeature = new BroadcastChannelFeature(bc);
+    JSContext* cx = workerPrivate->GetJSContext();
+    if (NS_WARN_IF(!workerPrivate->AddFeature(cx, bc->mWorkerFeature))) {
+      NS_WARNING("Failed to register the BroadcastChannel worker feature.");
+      return nullptr;
+    }
+  }
+
+  return bc.forget();
+}
+
+void
+BroadcastChannel::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+                              ErrorResult& aRv)
+{
+  if (mState != StateActive) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  PostMessageInternal(aCx, aMessage, aRv);
+}
+
+void
+BroadcastChannel::PostMessageInternal(JSContext* aCx,
+                                      JS::Handle<JS::Value> aMessage,
+                                      ErrorResult& aRv)
+{
+  nsRefPtr<BroadcastChannelMessage> data = new BroadcastChannelMessage();
+
+  if (!WriteStructuredClone(aCx, aMessage, data->mBuffer, data->mClosure)) {
+    aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
+    return;
+  }
+
+  const nsTArray<nsRefPtr<File>>& blobs = data->mClosure.mBlobs;
+  for (uint32_t i = 0, len = blobs.Length(); i < len; ++i) {
+    if (!blobs[i]->Impl()->MayBeClonedToOtherThreads()) {
+      aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
+      return;
+    }
+  }
+
+  PostMessageData(data);
+}
+
+void
+BroadcastChannel::PostMessageData(BroadcastChannelMessage* aData)
+{
+  if (mActor) {
+    nsRefPtr<PostMessageRunnable> runnable =
+      new PostMessageRunnable(mActor, aData);
+
+    if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
+      NS_WARNING("Failed to dispatch to the current thread!");
+    }
+
+    return;
+  }
+
+  mPendingMessages.AppendElement(aData);
+}
+
+void
+BroadcastChannel::Close()
+{
+  if (mState != StateActive) {
+    return;
+  }
+
+  if (mPendingMessages.IsEmpty()) {
+    // We cannot call Shutdown() immediatelly because we could have some
+    // postMessage runnable already dispatched. Instead, we change the state to
+    // StateClosed and we shutdown the actor asynchrounsly.
+
+    mState = StateClosed;
+    nsRefPtr<CloseRunnable> runnable = new CloseRunnable(this);
+
+    if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
+      NS_WARNING("Failed to dispatch to the current thread!");
+    }
+  } else {
+    MOZ_ASSERT(!mActor);
+    mState = StateClosing;
+  }
+}
+
+void
+BroadcastChannel::ActorFailed()
+{
+  MOZ_CRASH("Failed to create a PBackgroundChild actor!");
+}
+
+void
+BroadcastChannel::ActorCreated(PBackgroundChild* aActor)
+{
+  MOZ_ASSERT(aActor);
+
+  if (mState == StateClosed) {
+    return;
+  }
+
+  PBroadcastChannelChild* actor =
+    aActor->SendPBroadcastChannelConstructor(*mPrincipalInfo, mOrigin, mChannel);
+
+  mActor = static_cast<BroadcastChannelChild*>(actor);
+  MOZ_ASSERT(mActor);
+
+  mActor->SetParent(this);
+
+  // Flush pending messages.
+  for (uint32_t i = 0; i < mPendingMessages.Length(); ++i) {
+    PostMessageData(mPendingMessages[i]);
+  }
+
+  mPendingMessages.Clear();
+
+  if (mState == StateClosing) {
+    Shutdown();
+  }
+}
+
+void
+BroadcastChannel::Shutdown()
+{
+  mState = StateClosed;
+
+  // If shutdown() is called we have to release the reference if we still keep
+  // it.
+  if (mIsKeptAlive) {
+    mIsKeptAlive = false;
+    Release();
+  }
+
+  if (mWorkerFeature) {
+    WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+    workerPrivate->RemoveFeature(workerPrivate->GetJSContext(), mWorkerFeature);
+    mWorkerFeature = nullptr;
+  }
+
+  if (mActor) {
+    mActor->SetParent(nullptr);
+
+    nsRefPtr<TeardownRunnable> runnable = new TeardownRunnable(mActor);
+    NS_DispatchToCurrentThread(runnable);
+
+    mActor = nullptr;
+  }
+}
+
+EventHandlerNonNull*
+BroadcastChannel::GetOnmessage()
+{
+  if (NS_IsMainThread()) {
+    return GetEventHandler(nsGkAtoms::onmessage, EmptyString());
+  }
+  return GetEventHandler(nullptr, NS_LITERAL_STRING("message"));
+}
+
+void
+BroadcastChannel::SetOnmessage(EventHandlerNonNull* aCallback)
+{
+  if (NS_IsMainThread()) {
+    SetEventHandler(nsGkAtoms::onmessage, EmptyString(), aCallback);
+  } else {
+    SetEventHandler(nullptr, NS_LITERAL_STRING("message"), aCallback);
+  }
+
+  UpdateMustKeepAlive();
+}
+
+void
+BroadcastChannel::AddEventListener(const nsAString& aType,
+                                   EventListener* aCallback,
+                                   bool aCapture,
+                                   const dom::Nullable<bool>& aWantsUntrusted,
+                                   ErrorResult& aRv)
+{
+  DOMEventTargetHelper::AddEventListener(aType, aCallback, aCapture,
+                                         aWantsUntrusted, aRv);
+
+  if (aRv.Failed()) {
+    return;
+  }
+
+  UpdateMustKeepAlive();
+}
+
+void
+BroadcastChannel::RemoveEventListener(const nsAString& aType,
+                                      EventListener* aCallback,
+                                      bool aCapture,
+                                      ErrorResult& aRv)
+{
+  DOMEventTargetHelper::RemoveEventListener(aType, aCallback, aCapture, aRv);
+
+  if (aRv.Failed()) {
+    return;
+  }
+
+  UpdateMustKeepAlive();
+}
+
+void
+BroadcastChannel::UpdateMustKeepAlive()
+{
+  bool toKeepAlive = HasListenersFor(NS_LITERAL_STRING("message"));
+  if (toKeepAlive == mIsKeptAlive) {
+    return;
+  }
+
+  mIsKeptAlive = toKeepAlive;
+
+  if (toKeepAlive) {
+    AddRef();
+  } else {
+    Release();
+  }
+}
+
+NS_IMETHODIMP
+BroadcastChannel::Observe(nsISupports* aSubject, const char* aTopic,
+                          const char16_t* aData)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!strcmp(aTopic, "inner-window-destroyed"));
+
+  // If the window is destroyed we have to release the reference that we are
+  // keeping.
+  if (!mIsKeptAlive) {
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
+  NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
+
+  uint64_t innerID;
+  nsresult rv = wrapper->GetData(&innerID);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (innerID == mInnerID) {
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    if (obs) {
+      obs->RemoveObserver(this, "inner-window-destroyed");
+    }
+
+    Shutdown();
+  }
+
+  return NS_OK;
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(BroadcastChannel)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(BroadcastChannel,
+                                                  DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(BroadcastChannel,
+                                                DOMEventTargetHelper)
+  tmp->Shutdown();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BroadcastChannel)
+  NS_INTERFACE_MAP_ENTRY(nsIIPCBackgroundChildCreateCallback)
+  NS_INTERFACE_MAP_ENTRY(nsIObserver)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(BroadcastChannel, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(BroadcastChannel, DOMEventTargetHelper)
+
+} // dom namespace
+} // mozilla namespace
new file mode 100644
--- /dev/null
+++ b/dom/broadcastchannel/BroadcastChannel.h
@@ -0,0 +1,130 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_BroadcastChannel_h
+#define mozilla_dom_BroadcastChannel_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "nsIIPCBackgroundChildCreateCallback.h"
+#include "nsIObserver.h"
+#include "nsTArray.h"
+#include "nsRefPtr.h"
+
+class nsPIDOMWindow;
+
+namespace mozilla {
+
+namespace ipc {
+class PrincipalInfo;
+}
+
+namespace dom {
+
+namespace workers {
+class WorkerFeature;
+}
+
+class BroadcastChannelChild;
+class BroadcastChannelMessage;
+
+class BroadcastChannel MOZ_FINAL
+  : public DOMEventTargetHelper
+  , public nsIIPCBackgroundChildCreateCallback
+  , public nsIObserver
+{
+  NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK
+  NS_DECL_NSIOBSERVER
+
+  typedef mozilla::ipc::PrincipalInfo PrincipalInfo;
+
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BroadcastChannel,
+                                           DOMEventTargetHelper)
+
+  static bool IsEnabled(JSContext* aCx, JSObject* aGlobal);
+
+  virtual JSObject*
+  WrapObject(JSContext* aCx) MOZ_OVERRIDE;
+
+  static already_AddRefed<BroadcastChannel>
+  Constructor(const GlobalObject& aGlobal, const nsAString& aChannel,
+              ErrorResult& aRv);
+
+  void GetName(nsAString& aName) const
+  {
+    aName = mChannel;
+  }
+
+  void PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+                   ErrorResult& aRv);
+
+  void Close();
+
+  EventHandlerNonNull* GetOnmessage();
+  void SetOnmessage(EventHandlerNonNull* aCallback);
+
+  using nsIDOMEventTarget::AddEventListener;
+  using nsIDOMEventTarget::RemoveEventListener;
+
+  virtual void AddEventListener(const nsAString& aType,
+                                EventListener* aCallback,
+                                bool aCapture,
+                                const Nullable<bool>& aWantsUntrusted,
+                                ErrorResult& aRv) MOZ_OVERRIDE;
+  virtual void RemoveEventListener(const nsAString& aType,
+                                   EventListener* aCallback,
+                                   bool aCapture,
+                                   ErrorResult& aRv) MOZ_OVERRIDE;
+
+  void Shutdown();
+
+  bool IsClosed() const
+  {
+    return mState != StateActive;
+  }
+
+private:
+  BroadcastChannel(nsPIDOMWindow* aWindow,
+                   const PrincipalInfo& aPrincipalInfo,
+                   const nsAString& aOrigin,
+                   const nsAString& aChannel);
+
+  ~BroadcastChannel();
+
+  void PostMessageData(BroadcastChannelMessage* aData);
+
+  void PostMessageInternal(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+                           ErrorResult& aRv);
+
+  void UpdateMustKeepAlive();
+
+  nsRefPtr<BroadcastChannelChild> mActor;
+  nsTArray<nsRefPtr<BroadcastChannelMessage>> mPendingMessages;
+
+  nsAutoPtr<workers::WorkerFeature> mWorkerFeature;
+
+  nsAutoPtr<PrincipalInfo> mPrincipalInfo;
+
+  nsString mOrigin;
+  nsString mChannel;
+
+  bool mIsKeptAlive;
+
+  uint64_t mInnerID;
+
+  enum {
+    StateActive,
+    StateClosing,
+    StateClosed
+  } mState;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_BroadcastChannel_h
new file mode 100644
--- /dev/null
+++ b/dom/broadcastchannel/BroadcastChannelChild.cpp
@@ -0,0 +1,133 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "BroadcastChannelChild.h"
+#include "BroadcastChannel.h"
+#include "jsapi.h"
+#include "mozilla/dom/ipc/BlobChild.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/MessageEvent.h"
+#include "mozilla/dom/MessageEventBinding.h"
+#include "mozilla/dom/StructuredCloneUtils.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "WorkerPrivate.h"
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+using namespace workers;
+
+BroadcastChannelChild::BroadcastChannelChild(const nsAString& aOrigin,
+                                             const nsAString& aChannel)
+  : mOrigin(aOrigin)
+  , mChannel(aChannel)
+  , mActorDestroyed(false)
+{
+}
+
+BroadcastChannelChild::~BroadcastChannelChild()
+{
+  MOZ_ASSERT(!mBC);
+}
+
+bool
+BroadcastChannelChild::RecvNotify(const ClonedMessageData& aData)
+{
+  // Make sure to retrieve all blobs from the message before returning to avoid
+  // leaking their actors.
+  nsTArray<nsRefPtr<File>> files;
+  if (!aData.blobsChild().IsEmpty()) {
+    files.SetCapacity(aData.blobsChild().Length());
+
+    for (uint32_t i = 0, len = aData.blobsChild().Length(); i < len; ++i) {
+      nsRefPtr<FileImpl> impl =
+        static_cast<BlobChild*>(aData.blobsChild()[i])->GetBlobImpl();
+
+      nsRefPtr<File> file = new File(mBC ? mBC->GetOwner() : nullptr, impl);
+      files.AppendElement(file);
+    }
+  }
+
+  nsCOMPtr<DOMEventTargetHelper> helper = mBC;
+  nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(helper);
+
+  // This object has been already closed by content or is going to be deleted
+  // soon. No notify is required.
+  if (!eventTarget || mBC->IsClosed()) {
+    return true;
+  }
+
+  // CheckInnerWindowCorrectness can be used also without a window when
+  // BroadcastChannel is running in a worker. In this case, it's a NOP.
+  if (NS_FAILED(mBC->CheckInnerWindowCorrectness())) {
+    return true;
+  }
+
+  AutoJSAPI jsapi;
+  nsCOMPtr<nsIGlobalObject> globalObject;
+
+  if (NS_IsMainThread()) {
+    globalObject = do_QueryInterface(mBC->GetParentObject());
+  } else {
+    WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+    MOZ_ASSERT(workerPrivate);
+    globalObject = workerPrivate->GlobalScope();
+  }
+
+  if (!globalObject || !jsapi.Init(globalObject)) {
+    NS_WARNING("Failed to initialize AutoJSAPI object.");
+    return true;
+  }
+
+  JSContext* cx = jsapi.cx();
+
+  const SerializedStructuredCloneBuffer& buffer = aData.data();
+  StructuredCloneData cloneData;
+  cloneData.mData = buffer.data;
+  cloneData.mDataLength = buffer.dataLength;
+  cloneData.mClosure.mBlobs.SwapElements(files);
+
+  JS::Rooted<JS::Value> value(cx, JS::NullValue());
+  if (cloneData.mDataLength && !ReadStructuredClone(cx, cloneData, &value)) {
+    JS_ClearPendingException(cx);
+    return false;
+  }
+
+  RootedDictionary<MessageEventInit> init(cx);
+  init.mBubbles = false;
+  init.mCancelable = false;
+  init.mOrigin.Construct(mOrigin);
+  init.mData = value;
+
+  ErrorResult rv;
+  nsRefPtr<MessageEvent> event =
+    MessageEvent::Constructor(mBC, NS_LITERAL_STRING("message"), init, rv);
+  if (rv.Failed()) {
+    NS_WARNING("Failed to create a MessageEvent object.");
+    return true;
+  }
+
+  event->SetTrusted(true);
+
+  bool status;
+  mBC->DispatchEvent(static_cast<Event*>(event.get()), &status);
+
+  return true;
+}
+
+void
+BroadcastChannelChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+  mActorDestroyed = true;
+}
+
+} // dom namespace
+} // mozilla namespace
new file mode 100644
--- /dev/null
+++ b/dom/broadcastchannel/BroadcastChannelChild.h
@@ -0,0 +1,60 @@
+/* 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_BroadcastChannelChild_h
+#define mozilla_dom_BroadcastChannelChild_h
+
+#include "mozilla/dom/PBroadcastChannelChild.h"
+
+namespace mozilla {
+
+namespace ipc {
+class BackgroundChildImpl;
+}
+
+namespace dom {
+
+class BroadcastChannel;
+
+class BroadcastChannelChild MOZ_FINAL : public PBroadcastChannelChild
+{
+  friend class mozilla::ipc::BackgroundChildImpl;
+
+public:
+  NS_INLINE_DECL_REFCOUNTING(BroadcastChannelChild)
+
+  void SetParent(BroadcastChannel* aBC)
+  {
+    mBC = aBC;
+  }
+
+  virtual bool RecvNotify(const ClonedMessageData& aData) MOZ_OVERRIDE;
+
+  bool IsActorDestroyed() const
+  {
+    return mActorDestroyed;
+  }
+
+private:
+  BroadcastChannelChild(const nsAString& aOrigin,
+                        const nsAString& aChannel);
+
+  ~BroadcastChannelChild();
+
+  void ActorDestroy(ActorDestroyReason aWhy);
+
+  // This raw pointer is actually the parent object.
+  // It's set to null when the parent object is deleted.
+  BroadcastChannel* mBC;
+
+  nsString mOrigin;
+  nsString mChannel;
+
+  bool mActorDestroyed;
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_BroadcastChannelChild_h
new file mode 100644
--- /dev/null
+++ b/dom/broadcastchannel/BroadcastChannelParent.cpp
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "BroadcastChannelParent.h"
+#include "BroadcastChannelService.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/ipc/BlobParent.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/unused.h"
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+BroadcastChannelParent::BroadcastChannelParent(
+                                            const nsAString& aOrigin,
+                                            const nsAString& aChannel)
+  : mService(BroadcastChannelService::GetOrCreate())
+  , mOrigin(aOrigin)
+  , mChannel(aChannel)
+{
+  AssertIsOnBackgroundThread();
+  mService->RegisterActor(this);
+}
+
+BroadcastChannelParent::~BroadcastChannelParent()
+{
+  AssertIsOnBackgroundThread();
+}
+
+bool
+BroadcastChannelParent::RecvPostMessage(const ClonedMessageData& aData)
+{
+  AssertIsOnBackgroundThread();
+
+  if (NS_WARN_IF(!mService)) {
+    return false;
+  }
+
+  mService->PostMessage(this, aData, mOrigin, mChannel);
+  return true;
+}
+
+bool
+BroadcastChannelParent::RecvClose()
+{
+  AssertIsOnBackgroundThread();
+
+  if (NS_WARN_IF(!mService)) {
+    return false;
+  }
+
+  mService->UnregisterActor(this);
+  mService = nullptr;
+
+  unused << Send__delete__(this);
+
+  return true;
+}
+
+void
+BroadcastChannelParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+  AssertIsOnBackgroundThread();
+
+  if (mService) {
+    // This object is about to be released and with it, also mService will be
+    // released too.
+    mService->UnregisterActor(this);
+  }
+}
+
+void
+BroadcastChannelParent::CheckAndDeliver(const ClonedMessageData& aData,
+                                        const nsString& aOrigin,
+                                        const nsString& aChannel)
+{
+  AssertIsOnBackgroundThread();
+
+  if (aOrigin == mOrigin && aChannel == mChannel) {
+    // We need to duplicate data only if we have blobs.
+    if (aData.blobsParent().IsEmpty()) {
+      unused << SendNotify(aData);
+      return;
+    }
+
+    // Duplicate the data for this parent.
+    ClonedMessageData newData(aData);
+
+    // Ricreate the BlobParent for this new message.
+    for (uint32_t i = 0, len = newData.blobsParent().Length(); i < len; ++i) {
+      nsRefPtr<FileImpl> impl =
+        static_cast<BlobParent*>(newData.blobsParent()[i])->GetBlobImpl();
+
+      PBlobParent* blobParent =
+        BackgroundParent::GetOrCreateActorForBlobImpl(Manager(), impl);
+      if (!blobParent) {
+        return;
+      }
+
+      newData.blobsParent()[i] = blobParent;
+    }
+
+    unused << SendNotify(newData);
+  }
+}
+
+} // dom namespace
+} // mozilla namespace
new file mode 100644
--- /dev/null
+++ b/dom/broadcastchannel/BroadcastChannelParent.h
@@ -0,0 +1,49 @@
+/* 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_BroadcastChannelParent_h
+#define mozilla_dom_BroadcastChannelParent_h
+
+#include "mozilla/dom/PBroadcastChannelParent.h"
+
+namespace mozilla {
+
+namespace ipc {
+class BackgroundParentImpl;
+}
+
+namespace dom {
+
+class BroadcastChannelService;
+
+class BroadcastChannelParent MOZ_FINAL : public PBroadcastChannelParent
+{
+  friend class mozilla::ipc::BackgroundParentImpl;
+
+public:
+  void CheckAndDeliver(const ClonedMessageData& aData,
+                       const nsString& aOrigin,
+                       const nsString& aChannel);
+
+private:
+  BroadcastChannelParent(const nsAString& aOrigin,
+                         const nsAString& aChannel);
+  ~BroadcastChannelParent();
+
+  virtual bool
+  RecvPostMessage(const ClonedMessageData& aData) MOZ_OVERRIDE;
+
+  virtual bool RecvClose() MOZ_OVERRIDE;
+
+  virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
+
+  nsRefPtr<BroadcastChannelService> mService;
+  nsString mOrigin;
+  nsString mChannel;
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_BroadcastChannelParent_h
new file mode 100644
--- /dev/null
+++ b/dom/broadcastchannel/BroadcastChannelService.cpp
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "BroadcastChannelService.h"
+#include "BroadcastChannelParent.h"
+#include "mozilla/ipc/BackgroundParent.h"
+
+#ifdef XP_WIN
+#undef PostMessage
+#endif
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+namespace {
+
+BroadcastChannelService* sInstance = nullptr;
+
+} // anonymous namespace
+
+BroadcastChannelService::BroadcastChannelService()
+{
+  AssertIsOnBackgroundThread();
+
+  // sInstance is a raw BroadcastChannelService*.
+  MOZ_ASSERT(!sInstance);
+  sInstance = this;
+}
+
+BroadcastChannelService::~BroadcastChannelService()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(sInstance == this);
+  MOZ_ASSERT(mAgents.Count() == 0);
+
+  sInstance = nullptr;
+}
+
+// static
+already_AddRefed<BroadcastChannelService>
+BroadcastChannelService::GetOrCreate()
+{
+  AssertIsOnBackgroundThread();
+
+  nsRefPtr<BroadcastChannelService> instance = sInstance;
+  if (!instance) {
+    instance = new BroadcastChannelService();
+  }
+  return instance.forget();
+}
+
+void
+BroadcastChannelService::RegisterActor(BroadcastChannelParent* aParent)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aParent);
+  MOZ_ASSERT(!mAgents.Contains(aParent));
+
+  mAgents.PutEntry(aParent);
+}
+
+void
+BroadcastChannelService::UnregisterActor(BroadcastChannelParent* aParent)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aParent);
+  MOZ_ASSERT(mAgents.Contains(aParent));
+
+  mAgents.RemoveEntry(aParent);
+}
+
+namespace {
+
+struct MOZ_STACK_CLASS PostMessageData MOZ_FINAL
+{
+  PostMessageData(BroadcastChannelParent* aParent,
+                  const ClonedMessageData& aData,
+                  const nsAString& aOrigin,
+                  const nsAString& aChannel)
+    : mParent(aParent)
+    , mData(aData)
+    , mOrigin(aOrigin)
+    , mChannel(aChannel)
+  {
+    MOZ_ASSERT(aParent);
+    MOZ_COUNT_CTOR(PostMessageData);
+  }
+
+  ~PostMessageData()
+  {
+    MOZ_COUNT_DTOR(PostMessageData);
+  }
+
+  BroadcastChannelParent* mParent;
+  const ClonedMessageData& mData;
+  const nsString mOrigin;
+  const nsString mChannel;
+};
+
+PLDHashOperator
+PostMessageEnumerator(nsPtrHashKey<BroadcastChannelParent>* aKey, void* aPtr)
+{
+  AssertIsOnBackgroundThread();
+
+  auto* data = static_cast<PostMessageData*>(aPtr);
+  BroadcastChannelParent* parent = aKey->GetKey();
+  MOZ_ASSERT(parent);
+
+  if (parent != data->mParent) {
+    parent->CheckAndDeliver(data->mData, data->mOrigin, data->mChannel);
+  }
+
+  return PL_DHASH_NEXT;
+}
+
+} // anonymous namespace
+
+void
+BroadcastChannelService::PostMessage(BroadcastChannelParent* aParent,
+                                     const ClonedMessageData& aData,
+                                     const nsAString& aOrigin,
+                                     const nsAString& aChannel)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aParent);
+  MOZ_ASSERT(mAgents.Contains(aParent));
+
+  PostMessageData data(aParent, aData, aOrigin, aChannel);
+  mAgents.EnumerateEntries(PostMessageEnumerator, &data);
+}
+
+} // dom namespace
+} // mozilla namespace
new file mode 100644
--- /dev/null
+++ b/dom/broadcastchannel/BroadcastChannelService.h
@@ -0,0 +1,47 @@
+/* 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_BroadcastChannelService_h
+#define mozilla_dom_BroadcastChannelService_h
+
+#include "nsISupportsImpl.h"
+#include "nsHashKeys.h"
+#include "nsTHashtable.h"
+
+#ifdef XP_WIN
+#undef PostMessage
+#endif
+
+namespace mozilla {
+namespace dom {
+
+class BroadcastChannelParent;
+class ClonedMessageData;
+
+class BroadcastChannelService MOZ_FINAL
+{
+public:
+  NS_INLINE_DECL_REFCOUNTING(BroadcastChannelService)
+
+  static already_AddRefed<BroadcastChannelService> GetOrCreate();
+
+  void RegisterActor(BroadcastChannelParent* aParent);
+  void UnregisterActor(BroadcastChannelParent* aParent);
+
+  void PostMessage(BroadcastChannelParent* aParent,
+                   const ClonedMessageData& aData,
+                   const nsAString& aOrigin,
+                   const nsAString& aChannel);
+
+private:
+  BroadcastChannelService();
+  ~BroadcastChannelService();
+
+  nsTHashtable<nsPtrHashKey<BroadcastChannelParent>> mAgents;
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_BroadcastChannelService_h
new file mode 100644
--- /dev/null
+++ b/dom/broadcastchannel/PBroadcastChannel.ipdl
@@ -0,0 +1,29 @@
+/* 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 protocol PBackground;
+include protocol PBlob;
+include DOMTypes;
+
+using struct mozilla::SerializedStructuredCloneBuffer from "ipc/IPCMessageUtils.h";
+
+namespace mozilla {
+namespace dom {
+
+// This protocol is used for the BroadcastChannel API
+protocol PBroadcastChannel
+{
+  manager PBackground;
+
+parent:
+  PostMessage(ClonedMessageData message);
+  Close();
+
+child:
+  Notify(ClonedMessageData message);
+  __delete__();
+};
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/broadcastchannel/moz.build
@@ -0,0 +1,31 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.mozilla.dom += [
+    'BroadcastChannel.h',
+]
+
+SOURCES += [
+    'BroadcastChannel.cpp',
+    'BroadcastChannelChild.cpp',
+    'BroadcastChannelParent.cpp',
+    'BroadcastChannelService.cpp',
+]
+
+IPDL_SOURCES += [
+    'PBroadcastChannel.ipdl',
+]
+
+LOCAL_INCLUDES += [
+    '../workers',
+]
+
+MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+FAIL_ON_WARNINGS = True
new file mode 100644
--- /dev/null
+++ b/dom/broadcastchannel/tests/broadcastchannel_pref_worker.js
@@ -0,0 +1,11 @@
+onmessage = function() {
+  var exists = true;
+  try {
+    var bc = new BroadcastChannel('foobar');
+  } catch(e) {
+    exists = false;
+  }
+
+  postMessage({ exists: exists });
+}
+
new file mode 100644
--- /dev/null
+++ b/dom/broadcastchannel/tests/broadcastchannel_sharedWorker.js
@@ -0,0 +1,12 @@
+onconnect = function(evt) {
+  evt.ports[0].onmessage = function(evt) {
+    var bc = new BroadcastChannel('foobar');
+    bc.addEventListener('message', function(event) {
+      evt.target.postMessage(event.data == "hello world from the window" ? "OK" : "KO");
+      bc.postMessage("hello world from the worker");
+      bc.close();
+    }, false);
+
+    evt.target.postMessage("READY");
+  }
+}
new file mode 100644
--- /dev/null
+++ b/dom/broadcastchannel/tests/broadcastchannel_worker.js
@@ -0,0 +1,18 @@
+onmessage = function(evt) {
+  if (evt.data != 0) {
+    var worker = new Worker("broadcastchannel_worker.js");
+    worker.onmessage = function(evt) {
+      postMessage(evt.data);
+    }
+    worker.postMessage(evt.data - 1);
+    return;
+  }
+
+  var bc = new BroadcastChannel('foobar');
+  bc.addEventListener('message', function(event) {
+    bc.postMessage(event.data == "hello world from the window" ? "hello world from the worker" : "KO");
+    bc.close();
+  }, false);
+
+  postMessage("READY");
+}
new file mode 100644
--- /dev/null
+++ b/dom/broadcastchannel/tests/broadcastchannel_worker_alive.js
@@ -0,0 +1,8 @@
+(new BroadcastChannel('foobar')).postMessage('READY');
+
+(new BroadcastChannel('foobar')).addEventListener('message', function(event) {
+  if (event.data != 'READY') {
+    event.target.postMessage(event.data);
+  }
+}, false);
+
new file mode 100644
--- /dev/null
+++ b/dom/broadcastchannel/tests/broadcastchannel_worker_any.js
@@ -0,0 +1,5 @@
+(new BroadcastChannel('foobar')).onmessage = function(event) {
+  event.target.postMessage(event.data);
+}
+
+postMessage("READY");
new file mode 100644
--- /dev/null
+++ b/dom/broadcastchannel/tests/iframe_broadcastchannel.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+  <script type="application/javascript">
+
+function is(a, b, msg) {
+  ok(a == b, msg);
+}
+
+function ok(a, msg) {
+  window.parent.postMessage({ status: a ? "OK" : "KO", message: msg }, "*");
+}
+
+ok("BroadcastChannel" in window, "BroadcastChannel exists");
+
+var bc = new BroadcastChannel("foobar");
+ok(bc, "BroadcastChannel can be created");
+is(bc.name, 'foobar', "BroadcastChannel.name is foobar");
+
+ok("postMessage" in bc, "BroadcastChannel has postMessage() method");
+
+bc.onmessage = function(evt) {
+  ok(evt instanceof MessageEvent, 'evt is a MessageEvent');
+  is(evt.target, bc, 'MessageEvent.target is bc');
+  is(evt.target.name, 'foobar', 'MessageEvent.target.name is foobar');
+  is(evt.target.name, bc.name, 'MessageEvent.target.name is bc.name');
+  is(evt.data, "Hello world from the window!", "Message received from the window");
+  bc.postMessage("Hello world from the iframe!");
+}
+
+  </script>
+</body>
+</html>
+
+
new file mode 100644
--- /dev/null
+++ b/dom/broadcastchannel/tests/mochitest.ini
@@ -0,0 +1,17 @@
+[DEFAULT]
+support-files =
+  iframe_broadcastchannel.html
+  broadcastchannel_pref_worker.js
+  broadcastchannel_sharedWorker.js
+  broadcastchannel_worker.js
+  broadcastchannel_worker_alive.js
+  broadcastchannel_worker_any.js
+
+[test_broadcastchannel_any.html]
+[test_broadcastchannel_basic.html]
+[test_broadcastchannel_close.html]
+[test_broadcastchannel_self.html]
+[test_broadcastchannel_pref.html]
+[test_broadcastchannel_sharedWorker.html]
+[test_broadcastchannel_worker.html]
+[test_broadcastchannel_worker_alive.html]
new file mode 100644
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_broadcastchannel_any.html
@@ -0,0 +1,110 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for BroadcastChannel</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<div id="content"></div>
+
+<script type="application/javascript">
+
+var tests = [
+ 'hello world',
+ 123,
+ null,
+ true,
+ new Date(),
+ [ 1, 'test', true, new Date() ],
+ { a: true, b:  null, c: new Date(), d: [ true, false, {} ] },
+ new Blob([123], { type: 'plain/text' })
+];
+
+var currentTest = null;
+
+function getType(a) {
+  if (a === null || a === undefined)
+    return 'null';
+
+  if (Array.isArray(a))
+    return 'array';
+
+  if (typeof a == 'object')
+    return 'object';
+
+  return 'primitive';
+}
+
+function compare(a, b) {
+  is (getType(a), getType(b), 'Type matches');
+
+  var type = getType(a);
+  if (type == 'array') {
+    is (a.length, b.length, 'Array.length matches');
+    for (var i = 0; i < a.length; ++i) {
+      compare(a[i], b[i]);
+    }
+
+    return;
+  }
+
+  if (type == 'object') {
+    ok (a !== b, 'They should not match');
+
+    var aProps = [];
+    for (var p in a) aProps.push(p);
+
+    var bProps = [];
+    for (var p in b) bProps.push(p);
+
+    is (aProps.length, bProps.length, 'Props match');
+    is (aProps.sort().toSource(), bProps.sort().toSource(), 'Props match - using toSource()');
+
+    for (var p in a) {
+      compare(a[p], b[p]);
+    }
+
+    return;
+  }
+
+  if (type != 'null') {
+    is (a.toSource(), b.toSource(), 'Matching using toSource()');
+  }
+}
+
+function runTest() {
+  var bc = new BroadcastChannel("foobar");
+  ok(bc, "BroadcastChannel can be created");
+
+  bc.onmessage = function(event) {
+    compare(event.data, currentTest);
+    next();
+  }
+
+  function next() {
+    if (!tests.length) {
+      SimpleTest.finish();
+      return;
+    }
+
+    currentTest = tests.shift();
+    bc.postMessage(currentTest);
+  }
+
+  var worker = new Worker("broadcastchannel_worker_any.js");
+  worker.onmessage = function(event) {
+    if (event.data == "READY") {
+      next();
+    }
+  };
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [["dom.broadcastChannel.enabled", true]]}, runTest);
+
+</script>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_broadcastchannel_basic.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for BroadcastChannel</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<div id="content"></div>
+
+<script type="application/javascript">
+
+function runTest() {
+  addEventListener('message', receiveMessage, false);
+  function receiveMessage(evt) {
+    if (evt.data.status == 'OK') {
+      ok(true, evt.data.message);
+    } else if (evt.data.status == 'KO') {
+      ok(false, evt.data.message);
+    } else {
+      ok(false, "Unknown message");
+    }
+  }
+
+  ok("BroadcastChannel" in window, "BroadcastChannel exists");
+
+  var bc = new BroadcastChannel("foobar");
+  ok(bc, "BroadcastChannel can be created");
+  is(bc.name, 'foobar', "BroadcastChannel.name is foobar");
+
+  ok("postMessage" in bc, "BroadcastChannel has postMessage() method");
+
+  bc.onmessage = function(evt) {
+    ok(evt instanceof MessageEvent, "This is a MessageEvent");
+    is(evt.target, bc, "MessageEvent.target is bc");
+    is(evt.target.name, 'foobar', "MessageEvent.target.name is foobar");
+    is(evt.target.name, bc.name, "MessageEvent.target.name == bc.name");
+    ok(evt.origin.indexOf('http://mochi.test:8888') == 0, "MessageEvent.origin is correct");
+    is(evt.data, "Hello world from the iframe!", "The message from the iframe has been received!");
+    SimpleTest.finish();
+  }
+
+  var div = document.getElementById("content");
+  ok(div, "Parent exists");
+
+  var ifr = document.createElement("iframe");
+  ifr.addEventListener("load", iframeLoaded, false);
+  ifr.setAttribute('src', "iframe_broadcastchannel.html");
+  div.appendChild(ifr);
+
+  function iframeLoaded() {
+    bc.postMessage("Hello world from the window!");
+  }
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [["dom.broadcastChannel.enabled", true]]}, runTest);
+
+</script>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_broadcastchannel_close.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for BroadcastChannel</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<div id="content"></div>
+
+<script type="application/javascript">
+
+function runTest() {
+  var receiver = new BroadcastChannel('foo');
+  var sequence = [ '2', 'done' ];
+  receiver.onmessage = function(e) {
+    if (!sequence.length) {
+      ok (false, 'No more data is expected');
+      return;
+    }
+
+    var data = sequence.shift();
+    is(e.data, data);
+
+    if (!sequence.length) {
+      SimpleTest.executeSoon(function() {
+        SimpleTest.finish();
+      });
+    }
+  }
+
+  var x = new BroadcastChannel('foo');
+  x.close();
+  try {
+    x.postMessage('1');
+    ok(false, "PostMessage should throw if called after a close().");
+  } catch(e) {
+    ok(true, "PostMessage should throw if called after a close().");
+  }
+
+  var y = new BroadcastChannel('foo');
+  y.postMessage('2');
+  y.close();
+  try {
+    y.postMessage('3');
+    ok(false, "PostMessage should throw if called after a close().");
+  } catch(e) {
+    ok(true, "PostMessage should throw if called after a close().");
+  }
+
+  var z = new BroadcastChannel('foo');
+  z.postMessage('done');
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [["dom.broadcastChannel.enabled", true]]}, runTest);
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_broadcastchannel_pref.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for BroadcastChannel</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<div id="content"></div>
+
+<script type="application/javascript">
+
+function testNoPref() {
+  SpecialPowers.pushPrefEnv({"set": [["dom.broadcastChannel.enabled", false]]}, function() {
+    ok(!("BroadcastChannel" in window), "BroadcastChannel should not exist");
+    runTests();
+  });
+}
+
+function testPref() {
+  SpecialPowers.pushPrefEnv({"set": [["dom.broadcastChannel.enabled", true]]}, function() {
+    ok("BroadcastChannel" in window, "BroadcastChannel should exist");
+    runTests();
+  });
+}
+
+function testNoPrefWorker() {
+  SpecialPowers.pushPrefEnv({"set": [["dom.broadcastChannel.enabled", false]]}, function() {
+    var worker = new Worker("broadcastchannel_pref_worker.js");
+    worker.onmessage = function(event) {
+      ok(!event.data.exists, "BroadcastChannel should not exist in workers");
+      runTests();
+    }
+    worker.postMessage('go!');
+  });
+}
+
+function testPrefWorker() {
+  SpecialPowers.pushPrefEnv({"set": [["dom.broadcastChannel.enabled", true]]}, function() {
+    var worker = new Worker("broadcastchannel_pref_worker.js");
+    worker.onmessage = function(event) {
+      ok(event.data.exists, "BroadcastChannel should exist in workers");
+      runTests();
+    }
+    worker.postMessage('go!');
+  });
+}
+
+var tests = [
+  testNoPref,
+  testPref,
+  testNoPrefWorker,
+  testPrefWorker
+];
+
+function runTests() {
+  if (tests.length == 0) {
+    SimpleTest.finish();
+    return;
+  }
+
+  var test = tests.shift();
+  test();
+}
+
+SimpleTest.waitForExplicitFinish();
+runTests();
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_broadcastchannel_self.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for BroadcastChannel</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<div id="content"></div>
+
+<script type="application/javascript">
+
+function runTest() {
+  x = new BroadcastChannel('foo');
+  y = new BroadcastChannel('foo');
+
+  function func(e) {
+    is(e.target, y, "The target is !x");
+
+    SimpleTest.executeSoon(function() {
+      SimpleTest.finish();
+    });
+  }
+
+  x.onmessage = func;
+  y.onmessage = func;
+
+  x.postMessage('foo');
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [["dom.broadcastChannel.enabled", true]]}, runTest);
+
+</script>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_broadcastchannel_sharedWorker.html
@@ -0,0 +1,55 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM BroadcastChannel in SharedWorkers
+-->
+<head>
+  <title>Test for BroadcastChannel in SharedWorkers</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" language="javascript">
+
+function runTests() {
+  var worker = new SharedWorker("broadcastchannel_sharedWorker.js");
+
+  var bc = new BroadcastChannel('foobar');
+
+  worker.port.onmessage = function(event) {
+    if (event.data == "READY") {
+      ok(true, "SharedWorker is ready!");
+      bc.postMessage('hello world from the window');
+    } else if(event.data == "OK") {
+      ok(true, "SharedWorker has received the message");
+    } else {
+      ok(false, "Something wrong happened");
+    }
+  };
+
+  bc.onmessage = function(event) {
+    is("hello world from the worker", event.data, "The message matches!");
+    bc.close();
+    SimpleTest.finish();
+  }
+
+  worker.port.postMessage('go');
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [["dom.broadcastChannel.enabled", true],
+                                   ["dom.workers.sharedWorkers.enabled", true]]}, runTests);
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_broadcastchannel_worker.html
@@ -0,0 +1,62 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM BroadcastChannel in workers
+-->
+<head>
+  <title>Test for BroadcastChannel in workers</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" language="javascript">
+
+function testWorker(x) {
+  var worker = new Worker("broadcastchannel_worker.js");
+
+  var bc = new BroadcastChannel('foobar');
+
+  worker.onmessage = function(event) {
+    if (event.data == "READY") {
+      ok(true, "Worker is ready!");
+      bc.postMessage('hello world from the window');
+    } else {
+      ok(false, "Something wrong happened");
+    }
+  };
+
+  bc.onmessage = function(event) {
+    is("hello world from the worker", event.data, "The message matches!");
+    bc.close();
+    runTests();
+  }
+
+  worker.postMessage(x);
+}
+
+var tests = [ 0, 3 ];
+function runTests() {
+  if (tests.length == 0) {
+    SimpleTest.finish();
+    return;
+  }
+
+  testWorker(tests.shift());
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [["dom.broadcastChannel.enabled", true]]}, runTests);
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_broadcastchannel_worker_alive.html
@@ -0,0 +1,56 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM BroadcastChannel in workers
+-->
+<head>
+  <title>Test for BroadcastChannel in workers</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" language="javascript">
+
+function runTests() {
+  var id = 0;
+  (new BroadcastChannel('foobar')).onmessage = function(event) {
+    info("MSG: " + event.data);
+
+    if (event.data == "READY") {
+      ok(true, "Worker is ready!");
+    } else {
+      is(id, event.data, "The message is correct: " + id);
+    }
+
+    for (var i = 0; i < 3; ++i) {
+      SpecialPowers.forceCC();
+      SpecialPowers.forceGC();
+    }
+
+    if (id == 5) {
+      SimpleTest.finish();
+      return;
+    }
+
+    event.target.postMessage(++id);
+  };
+
+  new Worker("broadcastchannel_worker_alive.js");
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [["dom.broadcastChannel.enabled", true]]}, runTests);
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/events/DOMEventTargetHelper.h
+++ b/dom/events/DOMEventTargetHelper.h
@@ -96,16 +96,21 @@ public:
       // nsISupports pointer. That must be fixed, or we'll crash...
       NS_ASSERTION(target_qi == target, "Uh, fix QI!");
     }
 #endif
 
     return static_cast<DOMEventTargetHelper*>(target);
   }
 
+  bool HasListenersFor(const nsAString& aType)
+  {
+    return mListenerManager && mListenerManager->HasListenersFor(aType);
+  }
+
   bool HasListenersFor(nsIAtom* aTypeWithOn)
   {
     return mListenerManager && mListenerManager->HasListenersFor(aTypeWithOn);
   }
 
   nsresult SetEventHandler(nsIAtom* aType,
                            JSContext* aCx,
                            const JS::Value& aValue);
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -96,18 +96,16 @@ EventListenerManager::EventListenerManag
   : mMayHavePaintEventListener(false)
   , mMayHaveMutationListeners(false)
   , mMayHaveCapturingListeners(false)
   , mMayHaveSystemGroupListeners(false)
   , mMayHaveTouchEventListener(false)
   , mMayHaveScrollWheelEventListener(false)
   , mMayHaveMouseEnterLeaveEventListener(false)
   , mMayHavePointerEnterLeaveEventListener(false)
-  , mMayHaveKeyEventListener(false)
-  , mMayHaveInputOrCompositionEventListener(false)
   , mClearingListeners(false)
   , mIsMainThreadELM(NS_IsMainThread())
   , mNoListenerForEvent(0)
   , mTarget(aTarget)
 {
   NS_ASSERTION(aTarget, "unexpected null pointer");
 
   if (mIsMainThreadELM) {
@@ -383,31 +381,17 @@ EventListenerManager::AddEventListenerIn
 #ifdef MOZ_GAMEPAD
   } else if (aType >= NS_GAMEPAD_START &&
              aType <= NS_GAMEPAD_END) {
     nsPIDOMWindow* window = GetInnerWindowForTarget();
     if (window) {
       window->SetHasGamepadEventListener();
     }
 #endif
-  } else if (aTypeAtom == nsGkAtoms::onkeydown ||
-             aTypeAtom == nsGkAtoms::onkeypress ||
-             aTypeAtom == nsGkAtoms::onkeyup) {
-    if (!aFlags.mInSystemGroup) {
-      mMayHaveKeyEventListener = true;
-    }
-  } else if (aTypeAtom == nsGkAtoms::oncompositionend ||
-             aTypeAtom == nsGkAtoms::oncompositionstart ||
-             aTypeAtom == nsGkAtoms::oncompositionupdate ||
-             aTypeAtom == nsGkAtoms::oninput) {
-    if (!aFlags.mInSystemGroup) {
-      mMayHaveInputOrCompositionEventListener = true;
-    }
   }
-
   if (aTypeAtom && mTarget) {
     mTarget->EventListenerAdded(aTypeAtom);
   }
 }
 
 bool
 EventListenerManager::IsDeviceType(uint32_t aType)
 {
@@ -1258,18 +1242,29 @@ EventListenerManager::MutationListenerBi
     }
   }
   return bits;
 }
 
 bool
 EventListenerManager::HasListenersFor(const nsAString& aEventName)
 {
-  nsCOMPtr<nsIAtom> atom = do_GetAtom(NS_LITERAL_STRING("on") + aEventName);
-  return HasListenersFor(atom);
+  if (mIsMainThreadELM) {
+    nsCOMPtr<nsIAtom> atom = do_GetAtom(NS_LITERAL_STRING("on") + aEventName);
+    return HasListenersFor(atom);
+  }
+
+  uint32_t count = mListeners.Length();
+  for (uint32_t i = 0; i < count; ++i) {
+    Listener* listener = &mListeners.ElementAt(i);
+    if (listener->mTypeString == aEventName) {
+      return true;
+    }
+  }
+  return false;
 }
 
 bool
 EventListenerManager::HasListenersFor(nsIAtom* aEventNameWithOn)
 {
 #ifdef DEBUG
   nsAutoString name;
   aEventNameWithOn->ToString(name);
--- a/dom/events/EventListenerManager.h
+++ b/dom/events/EventListenerManager.h
@@ -398,29 +398,16 @@ public:
    * Returns true if there may be a scroll wheel listener registered,
    * false if there definitely isn't.
    */
   bool MayHaveScrollWheelEventListener() { return mMayHaveScrollWheelEventListener; }
 
   bool MayHaveMouseEnterLeaveEventListener() { return mMayHaveMouseEnterLeaveEventListener; }
   bool MayHavePointerEnterLeaveEventListener() { return mMayHavePointerEnterLeaveEventListener; }
 
-  /**
-   * Returns true if there may be a key event listener (keydown, keypress,
-   * or keyup) registered, or false if there definitely isn't.
-   */
-  bool MayHaveKeyEventListener() { return mMayHaveKeyEventListener; }
-
-  /**
-   * Returns true if there may be an advanced input event listener (input,
-   * compositionstart, compositionupdate, or compositionend) registered,
-   * or false if there definitely isn't.
-   */
-  bool MayHaveInputOrCompositionEventListener() { return mMayHaveInputOrCompositionEventListener; }
-
   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
 
   uint32_t ListenerCount() const
   {
     return mListeners.Length();
   }
 
   void MarkForCC();
@@ -561,21 +548,19 @@ protected:
   uint32_t mMayHavePaintEventListener : 1;
   uint32_t mMayHaveMutationListeners : 1;
   uint32_t mMayHaveCapturingListeners : 1;
   uint32_t mMayHaveSystemGroupListeners : 1;
   uint32_t mMayHaveTouchEventListener : 1;
   uint32_t mMayHaveScrollWheelEventListener : 1;
   uint32_t mMayHaveMouseEnterLeaveEventListener : 1;
   uint32_t mMayHavePointerEnterLeaveEventListener : 1;
-  uint32_t mMayHaveKeyEventListener : 1;
-  uint32_t mMayHaveInputOrCompositionEventListener : 1;
   uint32_t mClearingListeners : 1;
   uint32_t mIsMainThreadELM : 1;
-  uint32_t mNoListenerForEvent : 20;
+  uint32_t mNoListenerForEvent : 23;
 
   nsAutoTObserverArray<Listener, 2> mListeners;
   dom::EventTarget* mTarget;  // WEAK
   nsCOMPtr<nsIAtom> mNoListenerForEventAtom;
 
   friend class ELMCreationDetector;
   static uint32_t sMainThreadCreatedCount;
 };
--- a/dom/events/IMEStateManager.cpp
+++ b/dom/events/IMEStateManager.cpp
@@ -4,24 +4,24 @@
  * 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 "prlog.h"
 
 #include "mozilla/IMEStateManager.h"
 
 #include "mozilla/Attributes.h"
-#include "mozilla/EventListenerManager.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/TextComposition.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/dom/HTMLFormElement.h"
+#include "mozilla/dom/TabParent.h"
 
 #include "HTMLInputElement.h"
 #include "IMEContentObserver.h"
 
 #include "nsCOMPtr.h"
 #include "nsContentUtils.h"
 #include "nsIContent.h"
 #include "nsIDocument.h"
@@ -177,36 +177,31 @@ GetNotifyIMEMessageName(IMEMessage aMess
 }
 #endif // #ifdef PR_LOGGING
 
 nsIContent* IMEStateManager::sContent = nullptr;
 nsPresContext* IMEStateManager::sPresContext = nullptr;
 bool IMEStateManager::sInstalledMenuKeyboardListener = false;
 bool IMEStateManager::sIsTestingIME = false;
 bool IMEStateManager::sIsGettingNewIMEState = false;
-bool IMEStateManager::sCheckForIMEUnawareWebApps = false;
 
 // sActiveIMEContentObserver points to the currently active IMEContentObserver.
 // sActiveIMEContentObserver is null if there is no focused editor.
 IMEContentObserver* IMEStateManager::sActiveIMEContentObserver = nullptr;
 TextCompositionArray* IMEStateManager::sTextCompositions = nullptr;
 
 // static
 void
 IMEStateManager::Init()
 {
 #ifdef PR_LOGGING
   if (!sISMLog) {
     sISMLog = PR_NewLogModule("IMEStateManager");
   }
 #endif
-  Preferences::AddBoolVarCache(
-    &sCheckForIMEUnawareWebApps,
-    "intl.ime.hack.on_ime_unaware_apps.fire_key_events_for_composition",
-    false);
 }
 
 // static
 void
 IMEStateManager::Shutdown()
 {
   PR_LOG(sISMLog, PR_LOG_ALWAYS,
     ("ISM: IMEStateManager::Shutdown(), "
@@ -389,16 +384,37 @@ IMEStateManager::OnChangeFocusInternal(n
   if (NS_WARN_IF(!widget)) {
     PR_LOG(sISMLog, PR_LOG_ERROR,
       ("ISM:   IMEStateManager::OnChangeFocusInternal(), FAILED due to "
        "no widget to manage its IME state"));
     return NS_OK;
   }
 
   IMEState newState = GetNewIMEState(aPresContext, aContent);
+
+  // In e10s, remote content may have IME focus.  The main process (i.e. this process)
+  // would attempt to set state to DISABLED if, for example, the user clicks
+  // some other remote content.  The content process would later re-ENABLE IME, meaning
+  // that all state-changes were unnecessary.
+  // Here we filter the common case where the main process knows that the remote
+  // process controls IME focus.  The DISABLED->re-ENABLED progression can
+  // still happen since remote content may be concurrently communicating its claim
+  // on focus to the main process... but this cannot cause bugs like missed keypresses.
+  // (It just means a lot of needless IPC.)
+  if ((newState.mEnabled == IMEState::DISABLED) && TabParent::GetIMETabParent()) {
+    PR_LOG(sISMLog, PR_LOG_DEBUG,
+      ("ISM:   IMEStateManager::OnChangeFocusInternal(), "
+       "Parent process cancels to set DISABLED state because the content process "
+       "has IME focus and has already sets IME state"));  
+    MOZ_ASSERT(XRE_IsParentProcess(),
+      "TabParent::GetIMETabParent() should never return non-null value "
+      "in the content process");
+    return NS_OK;
+  }
+
   if (!focusActuallyChanging) {
     // actual focus isn't changing, but if IME enabled state is changing,
     // we should do it.
     InputContext context = widget->GetInputContext();
     if (context.mIMEState.mEnabled == newState.mEnabled) {
       PR_LOG(sISMLog, PR_LOG_DEBUG,
         ("ISM:   IMEStateManager::OnChangeFocusInternal(), "
          "neither focus nor IME state is changing"));
@@ -748,35 +764,16 @@ public:
     }
     return NS_OK;
   }
 
 private:
   uint32_t mState;
 };
 
-static bool
-MayBeIMEUnawareWebApp(nsINode* aNode)
-{
-  bool haveKeyEventsListener = false;
-
-  while (aNode) {
-    EventListenerManager* const mgr = aNode->GetExistingListenerManager();
-    if (mgr) {
-      if (mgr->MayHaveInputOrCompositionEventListener()) {
-        return false;
-      }
-      haveKeyEventsListener |= mgr->MayHaveKeyEventListener();
-    }
-    aNode = aNode->GetParentNode();
-  }
-
-  return haveKeyEventsListener;
-}
-
 // static
 void
 IMEStateManager::SetIMEState(const IMEState& aState,
                              nsIContent* aContent,
                              nsIWidget* aWidget,
                              InputContextAction aAction)
 {
   PR_LOG(sISMLog, PR_LOG_ALWAYS,
@@ -789,19 +786,16 @@ IMEStateManager::SetIMEState(const IMESt
 
   NS_ENSURE_TRUE_VOID(aWidget);
 
   InputContext oldContext = aWidget->GetInputContext();
 
   InputContext context;
   context.mIMEState = aState;
 
-  context.mMayBeIMEUnaware = context.mIMEState.IsEditable() &&
-    sCheckForIMEUnawareWebApps && MayBeIMEUnawareWebApp(aContent);
-
   if (aContent && aContent->GetNameSpaceID() == kNameSpaceID_XHTML &&
       (aContent->Tag() == nsGkAtoms::input ||
        aContent->Tag() == nsGkAtoms::textarea)) {
     if (aContent->Tag() != nsGkAtoms::textarea) {
       // <input type=number> has an anonymous <input type=text> descendant
       // that gets focus whenever anyone tries to focus the number control. We
       // need to check if aContent is one of those anonymous text controls and,
       // if so, use the number control instead:
--- a/dom/events/IMEStateManager.h
+++ b/dom/events/IMEStateManager.h
@@ -159,17 +159,16 @@ protected:
 
   static bool IsEditableIMEState(nsIWidget* aWidget);
 
   static nsIContent*    sContent;
   static nsPresContext* sPresContext;
   static bool           sInstalledMenuKeyboardListener;
   static bool           sIsTestingIME;
   static bool           sIsGettingNewIMEState;
-  static bool           sCheckForIMEUnawareWebApps;
 
   class MOZ_STACK_CLASS GettingNewIMEStateBlocker MOZ_FINAL
   {
   public:
     GettingNewIMEStateBlocker()
       : mOldValue(IMEStateManager::sIsGettingNewIMEState)
     {
       IMEStateManager::sIsGettingNewIMEState = true;
--- a/dom/events/MessageEvent.cpp
+++ b/dom/events/MessageEvent.cpp
@@ -114,24 +114,33 @@ MessageEvent::GetSource(Nullable<OwningW
 
 /* static */ already_AddRefed<MessageEvent>
 MessageEvent::Constructor(const GlobalObject& aGlobal,
                           const nsAString& aType,
                           const MessageEventInit& aParam,
                           ErrorResult& aRv)
 {
   nsCOMPtr<EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports());
-  nsRefPtr<MessageEvent> event = new MessageEvent(t, nullptr, nullptr);
+  return Constructor(t, aType, aParam, aRv);
+}
+
+/* static */ already_AddRefed<MessageEvent>
+MessageEvent::Constructor(EventTarget* aEventTarget,
+                          const nsAString& aType,
+                          const MessageEventInit& aParam,
+                          ErrorResult& aRv)
+{
+  nsRefPtr<MessageEvent> event = new MessageEvent(aEventTarget, nullptr, nullptr);
 
   aRv = event->InitEvent(aType, aParam.mBubbles, aParam.mCancelable);
   if (aRv.Failed()) {
     return nullptr;
   }
 
-  bool trusted = event->Init(t);
+  bool trusted = event->Init(aEventTarget);
   event->SetTrusted(trusted);
 
   event->mData = aParam.mData;
 
   mozilla::HoldJSObjects(event.get());
 
   if (aParam.mOrigin.WasPassed()) {
     event->mOrigin = aParam.mOrigin.Value();
--- a/dom/events/MessageEvent.h
+++ b/dom/events/MessageEvent.h
@@ -69,16 +69,22 @@ public:
   }
 
   static already_AddRefed<MessageEvent>
   Constructor(const GlobalObject& aGlobal,
               const nsAString& aType,
               const MessageEventInit& aEventInit,
               ErrorResult& aRv);
 
+  static already_AddRefed<MessageEvent>
+  Constructor(EventTarget* aEventTarget,
+              const nsAString& aType,
+              const MessageEventInit& aEventInit,
+              ErrorResult& aRv);
+
 protected:
   ~MessageEvent();
 
 private:
   JS::Heap<JS::Value> mData;
   nsString mOrigin;
   nsString mLastEventId;
   nsCOMPtr<nsIDOMWindow> mWindowSource;
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -270,23 +270,23 @@ parent:
     EnableDisableCommands(nsString action,
                           nsCString[] enabledCommands,
                           nsCString[] disabledCommands);
 
     prio(urgent) sync GetInputContext() returns (int32_t IMEEnabled,
                                                  int32_t IMEOpen,
                                                  intptr_t NativeIMEContext);
 
-    prio(urgent) async SetInputContext(int32_t IMEEnabled,
-                                       int32_t IMEOpen,
-                                       nsString type,
-                                       nsString inputmode,
-                                       nsString actionHint,
-                                       int32_t cause,
-                                       int32_t focusChange);
+    prio(urgent) sync SetInputContext(int32_t IMEEnabled,
+                                      int32_t IMEOpen,
+                                      nsString type,
+                                      nsString inputmode,
+                                      nsString actionHint,
+                                      int32_t cause,
+                                      int32_t focusChange);
 
     sync IsParentWindowMainWidgetVisible() returns (bool visible);
 
     /**
      * Gets the DPI of the screen corresponding to this browser.
      */
     sync GetDPI() returns (float value);
 
--- a/dom/ipc/StructuredCloneUtils.cpp
+++ b/dom/ipc/StructuredCloneUtils.cpp
@@ -21,25 +21,27 @@
 
 using namespace mozilla::dom;
 
 namespace {
 
 void
 Error(JSContext* aCx, uint32_t aErrorId)
 {
-  MOZ_ASSERT(NS_IsMainThread());
-  NS_DOMStructuredCloneError(aCx, aErrorId);
+  if (NS_IsMainThread()) {
+    NS_DOMStructuredCloneError(aCx, aErrorId);
+  } else {
+    Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
+  }
 }
 
 JSObject*
 Read(JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag,
      uint32_t aData, void* aClosure)
 {
-  MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aClosure);
 
   StructuredCloneClosure* closure =
     static_cast<StructuredCloneClosure*>(aClosure);
 
   if (aTag == SCTAG_DOM_BLOB) {
     // nsRefPtr<File> needs to go out of scope before toObjectOrNull() is
     // called because the static analysis thinks dereferencing XPCOM objects
@@ -75,17 +77,16 @@ Read(JSContext* aCx, JSStructuredCloneRe
 
   return NS_DOMReadStructuredClone(aCx, aReader, aTag, aData, nullptr);
 }
 
 bool
 Write(JSContext* aCx, JSStructuredCloneWriter* aWriter,
       JS::Handle<JSObject*> aObj, void* aClosure)
 {
-  MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aClosure);
 
   StructuredCloneClosure* closure =
     static_cast<StructuredCloneClosure*>(aClosure);
 
   // See if the wrapped native is a File/Blob.
   {
     File* blob = nullptr;
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -1920,31 +1920,44 @@ bool
 TabParent::RecvSetInputContext(const int32_t& aIMEEnabled,
                                const int32_t& aIMEOpen,
                                const nsString& aType,
                                const nsString& aInputmode,
                                const nsString& aActionHint,
                                const int32_t& aCause,
                                const int32_t& aFocusChange)
 {
+  nsCOMPtr<nsIWidget> widget = GetWidget();
+  if (!widget || !AllowContentIME()) {
+    return true;
+  }
+
+  InputContext oldContext = widget->GetInputContext();
+
+  // Ignore if current widget IME setting is not DISABLED and didn't come
+  // from remote content.  Chrome content may have taken over.
+  if (oldContext.mIMEState.mEnabled != IMEState::DISABLED &&
+      oldContext.IsOriginMainProcess()) {
+    return true;
+  }
+
   // mIMETabParent (which is actually static) tracks which if any TabParent has IMEFocus
   // When the input mode is set to anything but IMEState::DISABLED,
   // mIMETabParent should be set to this
   mIMETabParent =
     aIMEEnabled != static_cast<int32_t>(IMEState::DISABLED) ? this : nullptr;
-  nsCOMPtr<nsIWidget> widget = GetWidget();
-  if (!widget || !AllowContentIME())
-    return true;
 
   InputContext context;
   context.mIMEState.mEnabled = static_cast<IMEState::Enabled>(aIMEEnabled);
   context.mIMEState.mOpen = static_cast<IMEState::Open>(aIMEOpen);
   context.mHTMLInputType.Assign(aType);
   context.mHTMLInputInputmode.Assign(aInputmode);
   context.mActionHint.Assign(aActionHint);
+  context.mOrigin = InputContext::ORIGIN_CONTENT;
+
   InputContextAction action(
     static_cast<InputContextAction::Cause>(aCause),
     static_cast<InputContextAction::FocusChange>(aFocusChange));
   widget->SetInputContext(context, action);
 
   nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
   if (!observerService)
     return true;
@@ -2190,17 +2203,21 @@ TabParent::UseAsyncPanZoom()
           GetScrollingBehavior() == ASYNC_PAN_ZOOM);
 }
 
 nsEventStatus
 TabParent::MaybeForwardEventToRenderFrame(WidgetInputEvent& aEvent,
                                           ScrollableLayerGuid* aOutTargetGuid,
                                           uint64_t* aOutInputBlockId)
 {
-  if (aEvent.mClass == eWheelEventClass) {
+  if (aEvent.mClass == eWheelEventClass
+#ifdef MOZ_WIDGET_GONK
+      || aEvent.mClass == eTouchEventClass
+#endif
+     ) {
     // Wheel events must be sent to APZ directly from the widget. New APZ-
     // aware events should follow suit and move there as well. However, we
     // do need to inform the child process of the correct target and block
     // id.
     if (aOutTargetGuid) {
       *aOutTargetGuid = InputAPZContext::GetTargetLayerGuid();
     }
     if (aOutInputBlockId) {
--- a/dom/media/fmp4/MP4Reader.cpp
+++ b/dom/media/fmp4/MP4Reader.cpp
@@ -87,16 +87,17 @@ InvokeAndRetry(ThisType* aThisVal, Retur
     ReturnType result = ((*aThisVal).*aMethod)();
     if (result) {
       return result;
     }
     MP4Stream::ReadRecord failure(-1, 0);
     if (NS_WARN_IF(!stream->LastReadFailed(&failure))) {
       return result;
     }
+    stream->ClearFailedRead();
 
     if (NS_WARN_IF(failure == prevFailure)) {
       NS_WARNING(nsPrintfCString("Failed reading the same block twice: offset=%lld, count=%lu",
                                  failure.mOffset, failure.mCount).get());
       return result;
     }
 
     prevFailure = failure;
--- a/dom/media/fmp4/MP4Stream.h
+++ b/dom/media/fmp4/MP4Stream.h
@@ -40,16 +40,18 @@ public:
     if (mFailedRead.isSome()) {
       *aOut = mFailedRead.ref();
       return true;
     }
 
     return false;
   }
 
+  void ClearFailedRead() { mFailedRead.reset(); }
+
   void Pin()
   {
     mResource->Pin();
     ++mPinCount;
   }
 
   void Unpin()
   {
--- a/dom/media/mediasource/MediaSourceReader.cpp
+++ b/dom/media/mediasource/MediaSourceReader.cpp
@@ -473,16 +473,36 @@ MediaSourceReader::SwitchVideoReader(int
     mVideoReader->SetIdle();
     mVideoReader = newReader;
     MSE_DEBUGV("MediaSourceReader(%p)::SwitchVideoReader switched reader to %p", this, mVideoReader.get());
     return true;
   }
   return false;
 }
 
+bool
+MediaSourceReader::IsDormantNeeded()
+{
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+  if (mVideoReader) {
+    return mVideoReader->IsDormantNeeded();
+  }
+
+  return false;
+}
+
+void
+MediaSourceReader::ReleaseMediaResources()
+{
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+  if (mVideoReader) {
+    mVideoReader->ReleaseMediaResources();
+  }
+}
+
 MediaDecoderReader*
 CreateReaderForType(const nsACString& aType, AbstractMediaDecoder* aDecoder)
 {
 #ifdef MOZ_FMP4
   // The MP4Reader that supports fragmented MP4 and uses
   // PlatformDecoderModules is hidden behind prefs for regular video
   // elements, but we always want to use it for MSE, so instantiate it
   // directly here.
--- a/dom/media/mediasource/MediaSourceReader.h
+++ b/dom/media/mediasource/MediaSourceReader.h
@@ -48,16 +48,19 @@ public:
 
   nsRefPtr<AudioDataPromise> RequestAudioData() MOZ_OVERRIDE;
   nsRefPtr<VideoDataPromise>
   RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold) MOZ_OVERRIDE;
 
   virtual size_t SizeOfVideoQueueInFrames() MOZ_OVERRIDE;
   virtual size_t SizeOfAudioQueueInFrames() MOZ_OVERRIDE;
 
+  virtual bool IsDormantNeeded() MOZ_OVERRIDE;
+  virtual void ReleaseMediaResources() MOZ_OVERRIDE;
+
   void OnAudioDecoded(AudioData* aSample);
   void OnAudioNotDecoded(NotDecodedReason aReason);
   void OnVideoDecoded(VideoData* aSample);
   void OnVideoNotDecoded(NotDecodedReason aReason);
 
   void OnVideoSeekCompleted(int64_t aTime);
   void OnAudioSeekCompleted(int64_t aTime);
   void OnSeekFailed(nsresult aResult);
--- a/dom/media/mediasource/SourceBufferResource.h
+++ b/dom/media/mediasource/SourceBufferResource.h
@@ -24,17 +24,17 @@
 extern PRLogModuleInfo* GetMediaSourceLog();
 extern PRLogModuleInfo* GetMediaSourceAPILog();
 
 #define MSE_DEBUG(...) PR_LOG(GetMediaSourceLog(), PR_LOG_DEBUG, (__VA_ARGS__))
 #else
 #define MSE_DEBUG(...)
 #endif
 
-#define UNIMPLEMENTED() MSE_DEBUG("SourceBufferResource(%p): UNIMPLEMENTED FUNCTION at %s:%d", this, __FILE__, __LINE__)
+#define UNIMPLEMENTED() { /* Logging this is too spammy to do by default */ }
 
 class nsIStreamListener;
 
 namespace mozilla {
 
 class MediaDecoder;
 
 namespace dom {
--- a/dom/media/test/test_info_leak.html
+++ b/dom/media/test/test_info_leak.html
@@ -46,17 +46,17 @@ function createTestArray() {
     t.type = test.type;
 
     tests.push(t);
   }
   return tests;
 }
 
 function log(msg) {
-  //dump(msg + "\n");
+  info(msg);
   var l = document.getElementById('log');
   l.innerHTML += msg + "<br>";
 }
 
 function finish(v) {
   log("finish: " + v.name);
   clearInterval(v.checkStateInterval);
 
@@ -66,17 +66,25 @@ function finish(v) {
   removeNodeAndSource(v);
 
   manager.finished(v.token);
   v = null;
 }
 
 function listener(evt) {
   var v = evt.target;
-  //log(filename(v.name) + ' got event ' + evt.type);
+  log(filename(v.name) + ': got ' + evt.type);
+
+  // On slow machines like B2G emulator, progress timer could time out before
+  // receiving any HTTP notification. We will ignore the 'stalled' event to
+  // pass the tests.
+  if (evt.type == 'stalled') {
+    return;
+  }
+
   ok(v.eventNum < gExpectedEvents.length, filename(v.name)  + " Too many events received");
   var expected = (v.eventNum < gExpectedEvents.length) ? gExpectedEvents[v.eventNum] : "NoEvent";
   is(evt.type, expected, filename(v.name) + " Events received in wrong order");
   v.eventNum++;
   if (v.eventNum == gExpectedEvents.length) {
     // In one second, move onto the next test. This give a chance for any
     // other events to come in. Note: we don't expect any events to come
     // in, unless we've leaked some info, and 1 second should be enough time
--- a/dom/media/tests/mochitest/templates.js
+++ b/dom/media/tests/mochitest/templates.js
@@ -530,17 +530,17 @@ var commandsPeerConnection = [
   ],
   [
     'PC_LOCAL_CHECK_ICE_CONNECTIONS',
     function (test) {
       test.pcLocal.getStats(null, function(stats) {
         test.pcLocal.checkStatsIceConnections(stats,
                                               test._offer_constraints,
                                               test._offer_options,
-                                              test.originalAnswer);
+                                              test._remote_answer);
         test.next();
       });
     }
   ],
   [
     'PC_REMOTE_CHECK_ICE_CONNECTIONS',
     function (test) {
       test.pcRemote.getStats(null, function(stats) {
--- a/dom/moz.build
+++ b/dom/moz.build
@@ -88,16 +88,17 @@ DIRS += [
     'plugins/ipc',
     'indexedDB',
     'system',
     'ipc',
     'identity',
     'workers',
     'camera',
     'audiochannel',
+    'broadcastchannel',
     'promise',
     'smil',
     'telephony',
     'tv',
     'voicemail',
     'inputmethod',
     'webidl',
     'xbl',
--- a/dom/network/TCPServerSocketParent.cpp
+++ b/dom/network/TCPServerSocketParent.cpp
@@ -56,17 +56,17 @@ TCPServerSocketParent::Init(PNeckoParent
   nsresult rv;
   mIntermediary = do_CreateInstance("@mozilla.org/tcp-socket-intermediary;1", &rv);
   if (NS_FAILED(rv)) {
     FireInteralError(this, __LINE__);
     return true;
   }
 
   rv = mIntermediary->Listen(this, aLocalPort, aBacklog, aBinaryType, GetAppId(),
-                             getter_AddRefs(mServerSocket));
+                             GetInBrowser(), getter_AddRefs(mServerSocket));
   if (NS_FAILED(rv) || !mServerSocket) {
     FireInteralError(this, __LINE__);
     return true;
   }
   return true;
 }
 
 uint32_t
@@ -77,16 +77,29 @@ TCPServerSocketParent::GetAppId()
   const InfallibleTArray<PBrowserParent*>& browsers = content->ManagedPBrowserParent();
   if (browsers.Length() > 0) {
     TabParent *tab = static_cast<TabParent*>(browsers[0]);
     appId = tab->OwnAppId();
   }
   return appId;
 };
 
+bool
+TCPServerSocketParent::GetInBrowser()
+{
+  bool inBrowser = false;
+  const PContentParent *content = Manager()->Manager();
+  const InfallibleTArray<PBrowserParent*>& browsers = content->ManagedPBrowserParent();
+  if (browsers.Length() > 0) {
+    TabParent *tab = static_cast<TabParent*>(browsers[0]);
+    inBrowser = tab->IsBrowserElement();
+  }
+  return inBrowser;
+}
+
 NS_IMETHODIMP
 TCPServerSocketParent::SendCallbackAccept(nsITCPSocketParent *socket)
 {
   TCPSocketParent* _socket = static_cast<TCPSocketParent*>(socket);
   PTCPSocketParent* _psocket = static_cast<PTCPSocketParent*>(_socket);
 
   _socket->AddIPDLReference();
 
--- a/dom/network/TCPServerSocketParent.h
+++ b/dom/network/TCPServerSocketParent.h
@@ -27,16 +27,17 @@ public:
 
   bool Init(PNeckoParent* neckoParent, const uint16_t& aLocalPort, const uint16_t& aBacklog,
             const nsString& aBinaryType);
 
   virtual bool RecvClose() MOZ_OVERRIDE;
   virtual bool RecvRequestDelete() MOZ_OVERRIDE;
 
   uint32_t GetAppId();
+  bool GetInBrowser();
 
   void AddIPDLReference();
   void ReleaseIPDLReference();
 
 private:
   ~TCPServerSocketParent() {}
 
   virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
--- a/dom/network/TCPSocket.js
+++ b/dom/network/TCPSocket.js
@@ -179,16 +179,17 @@ TCPSocket.prototype = {
   _onUpdateBufferedAmount: null,
   _trackingNumber: 0,
 
 #ifdef MOZ_WIDGET_GONK
   // Network statistics (Gonk-specific feature)
   _txBytes: 0,
   _rxBytes: 0,
   _appId: Ci.nsIScriptSecurityManager.NO_APP_ID,
+  _inBrowser: false,
   _activeNetwork: null,
 #endif
 
   // Public accessors.
   get readyState() {
     return this._readyState;
   },
   get binaryType() {
@@ -476,16 +477,24 @@ TCPSocket.prototype = {
   setAppId: function ts_setAppId(appId) {
 #ifdef MOZ_WIDGET_GONK
     this._appId = appId;
 #else
     // Do nothing because _appId only exists on Gonk-specific platform.
 #endif
   },
 
+  setInBrowser: function ts_setInBrowser(inBrowser) {
+#ifdef MOZ_WIDGET_GONK
+    this._inBrowser = inBrowser;
+#else
+    // Do nothing.
+#endif
+  },
+
   setOnUpdateBufferedAmountHandler: function(aFunction) {
     if (typeof(aFunction) == 'function') {
       this._onUpdateBufferedAmount = aFunction;
     } else {
       throw new Error("only function can be passed to " +
                       "setOnUpdateBufferedAmountHandler");
     }
   },
--- a/dom/network/TCPSocketParent.cpp
+++ b/dom/network/TCPSocketParent.cpp
@@ -88,16 +88,29 @@ TCPSocketParent::GetAppId()
   const InfallibleTArray<PBrowserParent*>& browsers = content->ManagedPBrowserParent();
   if (browsers.Length() > 0) {
     TabParent *tab = static_cast<TabParent*>(browsers[0]);
     appId = tab->OwnAppId();
   }
   return appId;
 };
 
+bool
+TCPSocketParent::GetInBrowser()
+{
+  bool inBrowser = false;
+  const PContentParent *content = Manager()->Manager();
+  const InfallibleTArray<PBrowserParent*>& browsers = content->ManagedPBrowserParent();
+  if (browsers.Length() > 0) {
+    TabParent *tab = static_cast<TabParent*>(browsers[0]);
+    inBrowser = tab->IsBrowserElement();
+  }
+  return inBrowser;
+}
+
 nsresult
 TCPSocketParent::OfflineNotification(nsISupports *aSubject)
 {
   nsCOMPtr<nsIAppOfflineInfo> info(do_QueryInterface(aSubject));
   if (!info) {
     return NS_OK;
   }
 
@@ -157,32 +170,33 @@ TCPSocketParent::RecvOpen(const nsString
   if (net::UsingNeckoIPCSecurity() &&
       !AssertAppProcessPermission(Manager()->Manager(), "tcp-socket")) {
     FireInteralError(this, __LINE__);
     return true;
   }
 
   // Obtain App ID
   uint32_t appId = GetAppId();
+  bool     inBrowser = GetInBrowser();
 
   if (NS_IsAppOffline(appId)) {
     NS_ERROR("Can't open socket because app is offline");
     FireInteralError(this, __LINE__);
     return true;
   }
 
   nsresult rv;
   mIntermediary = do_CreateInstance("@mozilla.org/tcp-socket-intermediary;1", &rv);
   if (NS_FAILED(rv)) {
     FireInteralError(this, __LINE__);
     return true;
   }
 
   rv = mIntermediary->Open(this, aHost, aPort, aUseSSL, aBinaryType, appId,
-                           getter_AddRefs(mSocket));
+                           inBrowser, getter_AddRefs(mSocket));
   if (NS_FAILED(rv) || !mSocket) {
     FireInteralError(this, __LINE__);
     return true;
   }
 
   return true;
 }
 
--- a/dom/network/TCPSocketParent.h
+++ b/dom/network/TCPSocketParent.h
@@ -58,16 +58,17 @@ public:
   virtual bool RecvSuspend() MOZ_OVERRIDE;
   virtual bool RecvResume() MOZ_OVERRIDE;
   virtual bool RecvClose() MOZ_OVERRIDE;
   virtual bool RecvData(const SendableData& aData,
                         const uint32_t& aTrackingNumber) MOZ_OVERRIDE;
   virtual bool RecvRequestDelete() MOZ_OVERRIDE;
   virtual nsresult OfflineNotification(nsISupports *) MOZ_OVERRIDE;
   virtual uint32_t GetAppId() MOZ_OVERRIDE;
+  bool GetInBrowser();
 
 private:
   virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/network/TCPSocketParentIntermediary.js
+++ b/dom/network/TCPSocketParentIntermediary.js
@@ -31,49 +31,53 @@ TCPSocketParentIntermediary.prototype = 
       }
     );
   },
 
   _onUpdateBufferedAmountHandler: function(aParentSide, aBufferedAmount, aTrackingNumber) {
     aParentSide.sendUpdateBufferedAmount(aBufferedAmount, aTrackingNumber);
   },
 
-  open: function(aParentSide, aHost, aPort, aUseSSL, aBinaryType, aAppId) {
+  open: function(aParentSide, aHost, aPort, aUseSSL, aBinaryType,
+                 aAppId, aInBrowser) {
     let baseSocket = Cc["@mozilla.org/tcp-socket;1"].createInstance(Ci.nsIDOMTCPSocket);
     let socket = baseSocket.open(aHost, aPort, {useSecureTransport: aUseSSL, binaryType: aBinaryType});
     if (!socket)
       return null;
 
     let socketInternal = socket.QueryInterface(Ci.nsITCPSocketInternal);
     socketInternal.setAppId(aAppId);
+    socketInternal.setInBrowser(aInBrowser);
 
     // Handle parent's request to update buffered amount.
     socketInternal.setOnUpdateBufferedAmountHandler(
       this._onUpdateBufferedAmountHandler.bind(this, aParentSide));
 
     // Handlers are set to the JS-implemented socket object on the parent side.
     this._setCallbacks(aParentSide, socket);
     return socket;
   },
 
-  listen: function(aTCPServerSocketParent, aLocalPort, aBacklog, aBinaryType, aAppId) {
+  listen: function(aTCPServerSocketParent, aLocalPort, aBacklog, aBinaryType,
+                   aAppId, aInBrowser) {
     let baseSocket = Cc["@mozilla.org/tcp-socket;1"].createInstance(Ci.nsIDOMTCPSocket);
     let serverSocket = baseSocket.listen(aLocalPort, { binaryType: aBinaryType }, aBacklog);
     if (!serverSocket)
       return null;
 
     let localPort = serverSocket.localPort;
 
     serverSocket["onconnect"] = function(socket) {
       var socketParent = Cc["@mozilla.org/tcp-socket-parent;1"]
                             .createInstance(Ci.nsITCPSocketParent);
       var intermediary = new TCPSocketParentIntermediary();
 
       let socketInternal = socket.QueryInterface(Ci.nsITCPSocketInternal);
       socketInternal.setAppId(aAppId);
+      socketInternal.setInBrowser(aInBrowser);
       socketInternal.setOnUpdateBufferedAmountHandler(
         intermediary._onUpdateBufferedAmountHandler.bind(intermediary, socketParent));
 
       // Handlers are set to the JS-implemented socket object on the parent side,
       // so that the socket parent object can communicate data
       // with the corresponding socket child object through IPC.
       intermediary._setCallbacks(socketParent, socket);
       // The members in the socket parent object are set with arguments,
--- a/dom/network/interfaces/nsIDOMTCPSocket.idl
+++ b/dom/network/interfaces/nsIDOMTCPSocket.idl
@@ -211,17 +211,17 @@ interface nsIDOMTCPSocket : nsISupports
 };
 
 /*
  * This interface is implemented in TCPSocket.js as an internal interfaces
  * for use in cross-process socket implementation.
  * Needed to account for multiple possible types that can be provided to
  * the socket callbacks as arguments.
  */
-[scriptable, uuid(b1235064-9a08-4714-ad03-1212e4562803)]
+[scriptable, uuid(ac2c4b69-cb79-4767-b1ce-bcf62945cd39)]
 interface nsITCPSocketInternal : nsISupports {
   // Trigger the callback for |type| and provide a DOMError() object with the given data
   void callListenerError(in DOMString type, in DOMString name);
 
   // Trigger the callback for |type| and provide a string argument
   void callListenerData(in DOMString type, in DOMString data);
 
   // Trigger the callback for |type| and provide an ArrayBuffer argument
@@ -271,16 +271,19 @@ interface nsITCPSocketInternal : nsISupp
   //        An object to create ArrayBuffer for this window. See Bug 831107.
   nsIDOMTCPSocket createAcceptedChild(in nsITCPSocketChild socketChild,
                                       in DOMString binaryType,
                                       in nsIDOMWindow window);
 
   // Set App ID.
   void setAppId(in unsigned long appId);
 
+  // Set inBrowser.
+  void setInBrowser(in boolean inBrowser);
+
   // Set a callback that handles the request from a TCP socket parent when that
   // socket parent wants to notify that its bufferedAmount is updated.
   void setOnUpdateBufferedAmountHandler(in jsval handler);
 
   // Providing child process with ability to pass more arguments to parent's
   // send() function.
   // @param trackingNumber
   //        To ensure the request to update bufferedAmount in child is after
--- a/dom/network/interfaces/nsITCPSocketParent.idl
+++ b/dom/network/interfaces/nsITCPSocketParent.idl
@@ -60,28 +60,30 @@ interface nsITCPSocketParent : nsISuppor
   readonly attribute unsigned short port;
 };
 
 // Intermediate class to handle sending multiple possible data types
 // and kicking off the chrome process socket object's connection.
 // This interface is the bridge of TCPSocketParent, which is written in C++,
 // and TCPSocket, which is written in Javascript. TCPSocketParentIntermediary
 // implements nsITCPSocketIntermediary in Javascript.
-[scriptable, uuid(0bc14635-c586-4046-b82f-27ff45e6c39c)]
+[scriptable, uuid(aa9bd46d-26bf-4ba8-9c18-ba02482c02f0)]
 interface nsITCPSocketIntermediary : nsISupports {
   // Open the connection to the server with the given parameters
   nsIDOMTCPSocket open(in nsITCPSocketParent parent,
                        in DOMString host, in unsigned short port,
                        in boolean useSSL, in DOMString binaryType,
-                       in unsigned long appId);
+                       in unsigned long appId,
+                       in boolean inBrowser);
 
   // Listen on a port
   nsIDOMTCPServerSocket listen(in nsITCPServerSocketParent parent,
                                in unsigned short port, in unsigned short backlog,
                                in DOMString binaryType,
-                               in unsigned long appId);
+                               in unsigned long appId,
+                               in boolean inBrowser);
 
   // Called when received a child request to send a string.
   void onRecvSendString(in DOMString data, in uint32_t trackingNumber);
 
   // Called when received a child request to send an array buffer.
   void onRecvSendArrayBuffer(in jsval data, in uint32_t trackingNumber);
 };
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -195,16 +195,18 @@ var interfaceNamesInGlobalScope =
      permission: ["bluetooth"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "BluetoothManager", b2g: true, permission: ["bluetooth"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "BluetoothStatusChangedEvent", b2g: true, permission: ["bluetooth"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "BoxObject", xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "BroadcastChannel", pref: "dom.broadcastChannel.enabled"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "CallEvent", b2g: true, pref: "dom.telephony.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "CallGroupErrorEvent", b2g: true, pref: "dom.telephony.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "CameraCapabilities", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "CameraClosedEvent", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
--- a/dom/tests/mochitest/pointerlock/mochitest.ini
+++ b/dom/tests/mochitest/pointerlock/mochitest.ini
@@ -20,9 +20,9 @@ support-files =
   file_targetOutOfFocus.html
   file_screenClientXYConst.html
   file_suppressSomeMouseEvents.html
   file_locksvgelement.html
   file_allowPointerLockSandboxFlag.html
   iframe_differentDOM.html
 
 [test_pointerlock-api.html]
-skip-if = (toolkit == "windows" && debug) || buildapp == 'b2g' || toolkit == 'android' || e10s #TIMED_OUT # b2g(window.open focus issues (using fullscreen)) b2g-debug(window.open focus issues (using fullscreen)) b2g-desktop(window.open focus issues (using fullscreen))
+skip-if = toolkit == "windows" || buildapp == 'b2g' || toolkit == 'android' || e10s # B2G - window.open focus issues using fullscreen, Windows - bug 919106 & bug 931445
new file mode 100644
--- /dev/null
+++ b/dom/webidl/BroadcastChannel.webidl
@@ -0,0 +1,22 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ *
+ * For more information on this interface, please see
+ * http://www.whatwg.org/specs/web-apps/current-work/multipage/web-messaging.html#broadcasting-to-other-browsing-contexts
+ */
+
+[Constructor(DOMString channel),
+ Exposed=(Window,Worker),
+ Func="BroadcastChannel::IsEnabled"]
+interface BroadcastChannel : EventTarget {
+  readonly attribute DOMString name;
+
+  [Throws]
+  void postMessage(any message);
+
+  void close();
+
+           attribute EventHandler onmessage;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -50,16 +50,17 @@ WEBIDL_FILES = [
     'AutocompleteInfo.webidl',
     'BarProp.webidl',
     'BatteryManager.webidl',
     'BeforeAfterKeyboardEvent.webidl',
     'BeforeUnloadEvent.webidl',
     'BiquadFilterNode.webidl',
     'Blob.webidl',
     'BoxObject.webidl',
+    'BroadcastChannel.webidl',
     'BrowserElement.webidl',
     'BrowserElementDictionaries.webidl',
     'CallsList.webidl',
     'CameraCapabilities.webidl',
     'CameraControl.webidl',
     'CameraManager.webidl',
     'CameraUtil.webidl',
     'CanvasRenderingContext2D.webidl',
--- a/dom/workers/test/test_worker_interfaces.js
+++ b/dom/workers/test/test_worker_interfaces.js
@@ -83,16 +83,18 @@ if (typeof Symbol === "function") {
 }
 
 // IMPORTANT: Do not change the list below without review from a DOM peer!
 var interfaceNamesInGlobalScope =
   [
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Blob",
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    { name: "BroadcastChannel", pref: "dom.broadcastChannel.enabled" },
+// IMPORTANT: Do not change this list without review from a DOM peer!
     "DedicatedWorkerGlobalScope",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "DataStore", b2g: true },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "DataStoreCursor", b2g: true },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "DOMError",
 // IMPORTANT: Do not change this list without review from a DOM peer!
--- a/dom/xbl/test/test_bug389322.xhtml
+++ b/dom/xbl/test/test_bug389322.xhtml
@@ -8,76 +8,77 @@ https://bugzilla.mozilla.org/show_bug.cg
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <script>var ctorRan = false;</script>
   <bindings xmlns="http://www.mozilla.org/xbl">
     <binding id="test">
       <implementation>
         <field name="field"><![CDATA[
           (function () {
              try {
-               let (x=1) (x);
+               eval("let x = 1;");
                var success = true;
              }
              catch (e) { success = false; }
              XPCNativeWrapper.unwrap(window).report("XBL fields", success)
              return ""
            }())
         ]]></field>
         <property name="property">
           <getter><![CDATA[
              try {
-               let (x=1) (x);
+               eval("let x = 1;");
                var success = true;
              }
              catch (e) { success = false; }
              XPCNativeWrapper.unwrap(window).report("XBL property getters", success)
              return 1
            ]]></getter>
           <setter><![CDATA[
             try {
-              let (x=1) (x);
+              eval("let x = 1;");
               var success = true
             }
             catch (e) { success = false }
             XPCNativeWrapper.unwrap(window).report("XBL property setters", success)
             return val
           ]]></setter>
         </property>
         <method name="method">
           <body><![CDATA[
             try {
-              let (x=1) (x);
+              eval("let x = 1;");
               var success = true;
+
             }
             catch (e) { success = false; }
             XPCNativeWrapper.unwrap(window).report("XBL methods", success)
           ]]></body>
         </method>
         <constructor><![CDATA[
           this.property += 1
           var x = this.field;
           this.method()
           try {
-            let (x=1) (x);
+            eval("let x = 1;");
             var success = true
           }
           catch (e) { success = false }
           var win = XPCNativeWrapper.unwrap(window);
           win.report("XBL constructors", success)
         
           var ev = document.createEvent("Events")
           ev.initEvent("custom", false, false)
           this.dispatchEvent(ev)
           win.ctorRan = true;
         ]]></constructor>
       </implementation>
       <handlers>
         <handler action='
           try {
-            let (x=1) (x);
+            eval("let x = 1;");
             var success = true
           }
           catch (e) { success = false }
           report("XBL event handlers", success);
         ' event="custom"/>
       </handlers>
     </binding>
   </bindings>
@@ -101,26 +102,25 @@ addLoadEvent(SimpleTest.finish);
 
 function report(testName, success) {
   is(success, true, "JS 1.7 should work in " + testName);
 }
 ]]>
 </script>
 <script type="text/javascript; version=1.7"><![CDATA[
   try {
-    let (x=1) (x);
+    eval("let x = 1;");
     var success = true;
   }
   catch (e) { success = false; }
   report("HTML script tags with explicit version", success)
 ]]></script>
 <script type="text/javascript"><![CDATA[
   try {
-    let (x=1) (x);
+    eval("let x = 1;");
     var success = false;
   }
   catch (e) { success = true; }
   is(success, true, "JS 1.7 should not work in versionless HTML script tags");
 ]]></script>
 </pre>
 </body>
 </html>
-
--- a/gfx/2d/Logging.h
+++ b/gfx/2d/Logging.h
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MOZILLA_GFX_LOGGING_H_
 #define MOZILLA_GFX_LOGGING_H_
 
 #include <string>
 #include <sstream>
 #include <stdio.h>
+#include <vector>
 
 #ifdef MOZ_LOGGING
 #include <prlog.h>
 #endif
 
 #if defined(MOZ_WIDGET_GONK) || defined(MOZ_WIDGET_ANDROID)
 #include "nsDebug.h"
 #endif
@@ -201,16 +202,22 @@ struct CriticalLogger {
 };
 
 // Implement this interface and init the Factory with an instance to
 // forward critical logs.
 class LogForwarder {
 public:
   virtual ~LogForwarder() {}
   virtual void Log(const std::string &aString) = 0;
+
+  // Provide a copy of the logs to the caller.  The int is the index
+  // of the Log call, if the number of logs exceeds some preset capacity
+  // we may not get all of them, so the indices help figure out which
+  // ones we did save.
+  virtual std::vector<std::pair<int32_t,std::string> > StringsVectorCopy() = 0;
 };
 
 class NoLog
 {
 public:
   NoLog() {}
   ~NoLog() {}
 
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -84,17 +84,17 @@ APZCTreeManager::CalculatePendingDisplay
 {
   return AsyncPanZoomController::CalculatePendingDisplayPort(
     aFrameMetrics, aVelocity, aEstimatedPaintDuration);
 }
 
 APZCTreeManager::APZCTreeManager()
     : mInputQueue(new InputQueue()),
       mTreeLock("APZCTreeLock"),
-      mHitResultForInputBlock(NoApzcHit),
+      mHitResultForInputBlock(HitNothing),
       mRetainedTouchIdentifier(-1),
       mTouchCount(0),
       mApzcTreeLog("apzctree")
 {
   MOZ_ASSERT(NS_IsMainThread());
   AsyncPanZoomController::InitializeGlobalState();
   mApzcTreeLog.ConditionOnPrefFunction(gfxPrefs::APZPrintTree);
 }
@@ -555,52 +555,52 @@ APZCTreeManager::ReceiveInputEvent(Input
                                    uint64_t* aOutInputBlockId)
 {
   // Initialize aOutInputBlockId to a sane value, and then later we overwrite
   // it if the input event goes into a block.
   if (aOutInputBlockId) {
     *aOutInputBlockId = InputBlockState::NO_BLOCK_ID;
   }
   nsEventStatus result = nsEventStatus_eIgnore;
-  HitTestResult hitResult = NoApzcHit;
+  HitTestResult hitResult = HitNothing;
   switch (aEvent.mInputType) {
     case MULTITOUCH_INPUT: {
       MultiTouchInput& touchInput = aEvent.AsMultiTouchInput();
       result = ProcessTouchInput(touchInput, aOutTargetGuid, aOutInputBlockId);
       break;
     } case SCROLLWHEEL_INPUT: {
       ScrollWheelInput& wheelInput = aEvent.AsScrollWheelInput();
       nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(wheelInput.mOrigin,
                                                             &hitResult);
       if (apzc) {
-        MOZ_ASSERT(hitResult == ApzcHitRegion || hitResult == ApzcContentRegion);
+        MOZ_ASSERT(hitResult == HitLayer || hitResult == HitDispatchToContentRegion);
 
         result = mInputQueue->ReceiveInputEvent(
           apzc,
-          /* aTargetConfirmed = */ hitResult == ApzcHitRegion,
+          /* aTargetConfirmed = */ hitResult == HitLayer,
           wheelInput, aOutInputBlockId);
 
         // Update the out-parameters so they are what the caller expects.
         apzc->GetGuid(aOutTargetGuid);
         Matrix4x4 transformToGecko = GetScreenToApzcTransform(apzc)
                                    * GetApzcToGeckoTransform(apzc);
         wheelInput.mOrigin =
           TransformTo<ScreenPixel>(transformToGecko, wheelInput.mOrigin);
       }
       break;
     } case PANGESTURE_INPUT: {
       PanGestureInput& panInput = aEvent.AsPanGestureInput();
       nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(panInput.mPanStartPoint,
                                                             &hitResult);
       if (apzc) {
-        MOZ_ASSERT(hitResult == ApzcHitRegion || hitResult == ApzcContentRegion);
+        MOZ_ASSERT(hitResult == HitLayer || hitResult == HitDispatchToContentRegion);
 
         result = mInputQueue->ReceiveInputEvent(
             apzc,
-            /* aTargetConfirmed = */ hitResult == ApzcHitRegion,
+            /* aTargetConfirmed = */ hitResult == HitLayer,
             panInput, aOutInputBlockId);
 
         // Update the out-parameters so they are what the caller expects.
         apzc->GetGuid(aOutTargetGuid);
         Matrix4x4 transformToGecko = GetScreenToApzcTransform(apzc)
                                    * GetApzcToGeckoTransform(apzc);
         panInput.mPanStartPoint = TransformTo<ScreenPixel>(
             transformToGecko, panInput.mPanStartPoint);
@@ -608,53 +608,53 @@ APZCTreeManager::ReceiveInputEvent(Input
             transformToGecko, panInput.mPanDisplacement, panInput.mPanStartPoint);
       }
       break;
     } case PINCHGESTURE_INPUT: {  // note: no one currently sends these
       PinchGestureInput& pinchInput = aEvent.AsPinchGestureInput();
       nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(pinchInput.mFocusPoint,
                                                             &hitResult);
       if (apzc) {
-        MOZ_ASSERT(hitResult == ApzcHitRegion || hitResult == ApzcContentRegion);
+        MOZ_ASSERT(hitResult == HitLayer || hitResult == HitDispatchToContentRegion);
 
         result = mInputQueue->ReceiveInputEvent(
             apzc,
-            /* aTargetConfirmed = */ hitResult == ApzcHitRegion,
+            /* aTargetConfirmed = */ hitResult == HitLayer,
             pinchInput, aOutInputBlockId);
 
         // Update the out-parameters so they are what the caller expects.
         apzc->GetGuid(aOutTargetGuid);
         Matrix4x4 outTransform = GetScreenToApzcTransform(apzc)
                                * GetApzcToGeckoTransform(apzc);
         pinchInput.mFocusPoint = TransformTo<ScreenPixel>(
             outTransform, pinchInput.mFocusPoint);
       }
       break;
     } case TAPGESTURE_INPUT: {  // note: no one currently sends these
       TapGestureInput& tapInput = aEvent.AsTapGestureInput();
       nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(tapInput.mPoint,
                                                             &hitResult);
       if (apzc) {
-        MOZ_ASSERT(hitResult == ApzcHitRegion || hitResult == ApzcContentRegion);
+        MOZ_ASSERT(hitResult == HitLayer || hitResult == HitDispatchToContentRegion);
 
         result = mInputQueue->ReceiveInputEvent(
             apzc,
-            /* aTargetConfirmed = */ hitResult == ApzcHitRegion,
+            /* aTargetConfirmed = */ hitResult == HitLayer,
             tapInput, aOutInputBlockId);
 
         // Update the out-parameters so they are what the caller expects.
         apzc->GetGuid(aOutTargetGuid);
         Matrix4x4 outTransform = GetScreenToApzcTransform(apzc)
                                * GetApzcToGeckoTransform(apzc);
         tapInput.mPoint = TransformTo<ScreenPixel>(outTransform, tapInput.mPoint);
       }
       break;
     }
   }
-  if (hitResult == OverscrolledApzc) {
+  if (hitResult == HitOverscrolledApzc) {
     result = nsEventStatus_eConsumeNoDefault;
   }
   return result;
 }
 
 already_AddRefed<AsyncPanZoomController>
 APZCTreeManager::GetTouchInputBlockAPZC(const MultiTouchInput& aEvent,
                                         HitTestResult* aOutHitResult)
@@ -701,19 +701,19 @@ APZCTreeManager::ProcessTouchInput(Multi
         mRetainedTouchIdentifier = mApzcForInputBlock->GetLastTouchIdentifier();
       }
       return nsEventStatus_eConsumeNoDefault;
     }
 
     // NS_TOUCH_START event contains all active touches of the current
     // session thus resetting mTouchCount.
     mTouchCount = aInput.mTouches.Length();
-    mHitResultForInputBlock = NoApzcHit;
+    mHitResultForInputBlock = HitNothing;
     nsRefPtr<AsyncPanZoomController> apzc = GetTouchInputBlockAPZC(aInput, &mHitResultForInputBlock);
-    // XXX the following check assumes mHitResultForInputBlock == ApzcHitRegion
+    // XXX the following check assumes mHitResultForInputBlock == HitLayer
     // (and that mApzcForInputBlock was the confirmed target of the previous
     // input block). Eventually it would be better to move this into InputQueue
     // and have it auto-generated when we start processing events in a new
     // event block.
     if (apzc != mApzcForInputBlock) {
       // If we're moving to a different APZC as our input target, then send a cancel event
       // to the old one so that it clears its internal state. Otherwise it could get left
       // in the middle of a panning touch block (for example) and not clean up properly.
@@ -748,36 +748,36 @@ APZCTreeManager::ProcessTouchInput(Multi
     }
     if (aInput.mTouches.IsEmpty()) {
       return nsEventStatus_eConsumeNoDefault;
     }
   }
 
   nsEventStatus result = nsEventStatus_eIgnore;
   if (mApzcForInputBlock) {
-    MOZ_ASSERT(mHitResultForInputBlock == ApzcHitRegion || mHitResultForInputBlock == ApzcContentRegion);
+    MOZ_ASSERT(mHitResultForInputBlock == HitLayer || mHitResultForInputBlock == HitDispatchToContentRegion);
 
     mApzcForInputBlock->GetGuid(aOutTargetGuid);
     result = mInputQueue->ReceiveInputEvent(mApzcForInputBlock,
-        /* aTargetConfirmed = */ mHitResultForInputBlock == ApzcHitRegion,
+        /* aTargetConfirmed = */ mHitResultForInputBlock == HitLayer,
         aInput, aOutInputBlockId);
 
     // For computing the event to pass back to Gecko, use up-to-date transforms
     // (i.e. not anything cached in an input block).
     // This ensures that transformToApzc and transformToGecko are in sync.
     Matrix4x4 transformToApzc = GetScreenToApzcTransform(mApzcForInputBlock);
     Matrix4x4 transformToGecko = GetApzcToGeckoTransform(mApzcForInputBlock);
     Matrix4x4 outTransform = transformToApzc * transformToGecko;
     for (size_t i = 0; i < aInput.mTouches.Length(); i++) {
       SingleTouchData& touchData = aInput.mTouches[i];
       touchData.mScreenPoint = TransformTo<ScreenPixel>(
           outTransform, touchData.mScreenPoint);
     }
   }
-  if (mHitResultForInputBlock == OverscrolledApzc) {
+  if (mHitResultForInputBlock == HitOverscrolledApzc) {
     result = nsEventStatus_eConsumeNoDefault;
   }
 
   if (aInput.mType == MultiTouchInput::MULTITOUCH_END) {
     if (mTouchCount >= aInput.mTouches.Length()) {
       // NS_TOUCH_END event contains only released touches thus decrementing.
       mTouchCount -= aInput.mTouches.Length();
     } else {
@@ -787,17 +787,17 @@ APZCTreeManager::ProcessTouchInput(Multi
   } else if (aInput.mType == MultiTouchInput::MULTITOUCH_CANCEL) {
     mTouchCount = 0;
   }
 
   // If it's the end of the touch sequence then clear out variables so we
   // don't keep dangling references and leak things.
   if (mTouchCount == 0) {
     mApzcForInputBlock = nullptr;
-    mHitResultForInputBlock = NoApzcHit;
+    mHitResultForInputBlock = HitNothing;
     mRetainedTouchIdentifier = -1;
   }
 
   return result;
 }
 
 void
 APZCTreeManager::TransformCoordinateToGecko(const ScreenIntPoint& aPoint,
@@ -818,28 +818,28 @@ APZCTreeManager::ProcessEvent(WidgetInpu
                               ScrollableLayerGuid* aOutTargetGuid,
                               uint64_t* aOutInputBlockId)
 {
   MOZ_ASSERT(NS_IsMainThread());
   nsEventStatus result = nsEventStatus_eIgnore;
 
   // Transform the refPoint.
   // If the event hits an overscrolled APZC, instruct the caller to ignore it.
-  HitTestResult hitResult = NoApzcHit;
+  HitTestResult hitResult = HitNothing;
   nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(ScreenPoint(aEvent.refPoint.x, aEvent.refPoint.y),
                                                         &hitResult);
   if (apzc) {
-    MOZ_ASSERT(hitResult == ApzcHitRegion || hitResult == ApzcContentRegion);
+    MOZ_ASSERT(hitResult == HitLayer || hitResult == HitDispatchToContentRegion);
     apzc->GetGuid(aOutTargetGuid);
     Matrix4x4 transformToApzc = GetScreenToApzcTransform(apzc);
     Matrix4x4 transformToGecko = GetApzcToGeckoTransform(apzc);
     Matrix4x4 outTransform = transformToApzc * transformToGecko;
     aEvent.refPoint = TransformTo<LayoutDevicePixel>(outTransform, aEvent.refPoint);
   }
-  if (hitResult == OverscrolledApzc) {
+  if (hitResult == HitOverscrolledApzc) {
     result = nsEventStatus_eConsumeNoDefault;
   }
   return result;
 }
 
 nsEventStatus
 APZCTreeManager::ProcessWheelEvent(WidgetWheelEvent& aEvent,
                                    ScrollableLayerGuid* aOutTargetGuid,
@@ -1185,23 +1185,23 @@ APZCTreeManager::GetTargetNode(const Scr
   nsRefPtr<HitTestingTreeNode> target = FindTargetNode(mRootNode, aGuid, aComparator);
   return target.forget();
 }
 
 already_AddRefed<AsyncPanZoomController>
 APZCTreeManager::GetTargetAPZC(const ScreenPoint& aPoint, HitTestResult* aOutHitResult)
 {
   MonitorAutoLock lock(mTreeLock);
-  HitTestResult hitResult = NoApzcHit;
+  HitTestResult hitResult = HitNothing;
   ParentLayerPoint point = ViewAs<ParentLayerPixel>(aPoint,
     PixelCastJustification::ScreenIsParentLayerForRoot);
   nsRefPtr<AsyncPanZoomController> target = GetAPZCAtPoint(mRootNode, point, &hitResult);
 
   // If we are in an overscrolled APZC, we should be returning nullptr.
-  MOZ_ASSERT(!(target && (hitResult == OverscrolledApzc)));
+  MOZ_ASSERT(!(target && (hitResult == HitOverscrolledApzc)));
   if (aOutHitResult) {
     *aOutHitResult = hitResult;
   }
   return target.forget();
 }
 
 static bool
 GuidComparatorIgnoringPresShell(const ScrollableLayerGuid& aOne, const ScrollableLayerGuid& aTwo)
@@ -1336,50 +1336,50 @@ APZCTreeManager::GetAPZCAtPoint(HitTesti
 
     // First check the subtree rooted at this node, because deeper nodes
     // are more "in front".
     Maybe<LayerPoint> hitTestPointForChildLayers = node->Untransform(aHitTestPoint);
     if (hitTestPointForChildLayers) {
       ParentLayerPoint childPoint = ViewAs<ParentLayerPixel>(hitTestPointForChildLayers.ref(),
         PixelCastJustification::MovingDownToChildren);
       result = GetAPZCAtPoint(node->GetLastChild(), childPoint, aOutHitResult);
-      if (*aOutHitResult == OverscrolledApzc) {
+      if (*aOutHitResult == HitOverscrolledApzc) {
         // We matched an overscrolled APZC, abort.
         return nullptr;
       }
     }
 
     // If we didn't match anything in the subtree, check |node|.
-    if (!result) {
+    if (*aOutHitResult == HitNothing) {
       APZCTM_LOG("Testing ParentLayer point %s (Layer %s) against node %p\n",
           Stringify(aHitTestPoint).c_str(),
           hitTestPointForChildLayers ? Stringify(hitTestPointForChildLayers.ref()).c_str() : "nil",
           node);
       HitTestResult hitResult = node->HitTest(aHitTestPoint);
-      if (hitResult != HitTestResult::NoApzcHit) {
+      if (hitResult != HitTestResult::HitNothing) {
         result = node->GetNearestContainingApzc();
         APZCTM_LOG("Successfully matched APZC %p via node %p (hit result %d)\n",
              result, node, hitResult);
-        MOZ_ASSERT(hitResult == ApzcHitRegion || hitResult == ApzcContentRegion);
-        // If event regions are disabled, *aOutHitResult will be ApzcHitRegion
+        MOZ_ASSERT(hitResult == HitLayer || hitResult == HitDispatchToContentRegion);
+        // If event regions are disabled, *aOutHitResult will be HitLayer
         *aOutHitResult = hitResult;
       }
     }
 
     // If we are overscrolled, and the point matches us or one of our children,
     // the result is inside an overscrolled APZC, inform our caller of this
     // (callers typically ignore events targeted at overscrolled APZCs).
-    if (result && apzc && apzc->IsOverscrolled()) {
+    if (*aOutHitResult != HitNothing && apzc && apzc->IsOverscrolled()) {
       APZCTM_LOG("Result is inside overscrolled APZC %p\n", apzc);
-      *aOutHitResult = OverscrolledApzc;
+      *aOutHitResult = HitOverscrolledApzc;
       return nullptr;
     }
 
-    if (result) {
-      if (!gfxPrefs::LayoutEventRegionsEnabled()) {
+    if (*aOutHitResult != HitNothing) {
+      if (result && !gfxPrefs::LayoutEventRegionsEnabled()) {
         // When event-regions are disabled, we treat scrollinfo layers as
         // regular scrollable layers. Unfortunately, their "hit region" (which
         // we create from the composition bounds) is their full area, and they
         // sit on top of their non-scrollinfo siblings. This means they will get
         // a HitTestingTreeNode with a hit region that will aggressively match
         // any input events that might be directed to sub-APZCs of their non-
         // scrollinfo siblings. Therefore, we need to keep looping through to
         // see if there are any other non-scrollinfo siblings that have children
--- a/gfx/layers/apz/src/APZUtils.h
+++ b/gfx/layers/apz/src/APZUtils.h
@@ -6,18 +6,18 @@
 
 #ifndef mozilla_layers_APZUtils_h
 #define mozilla_layers_APZUtils_h
 
 namespace mozilla {
 namespace layers {
 
 enum HitTestResult {
-  NoApzcHit,
-  ApzcHitRegion,
-  ApzcContentRegion,
-  OverscrolledApzc,
+  HitNothing,
+  HitLayer,
+  HitDispatchToContentRegion,
+  HitOverscrolledApzc,
 };
 
 }
 }
 
 #endif // mozilla_layers_APZUtils_h
--- a/gfx/layers/apz/src/HitTestingTreeNode.cpp
+++ b/gfx/layers/apz/src/HitTestingTreeNode.cpp
@@ -190,34 +190,34 @@ HitTestingTreeNode::HitTest(const Parent
   MOZ_ASSERT(!IsOutsideClip(aPoint));
 
   // When event regions are disabled and we have an APZC on this node, we are
   // actually storing the touch-sensitive section of the composition bounds in
   // the clip region, and we don't need to check against the mEventRegions.
   // If there's no APZC, then we do need to check against the mEventRegions
   // (which contains the layer's visible region) for obscuration purposes.
   if (!gfxPrefs::LayoutEventRegionsEnabled() && GetApzc()) {
-    return HitTestResult::ApzcHitRegion;
+    return HitTestResult::HitLayer;
   }
 
   // convert into Layer coordinate space
   Maybe<LayerPoint> pointInLayerPixels = Untransform(aPoint);
   if (!pointInLayerPixels) {
-    return HitTestResult::NoApzcHit;
+    return HitTestResult::HitNothing;
   }
   LayerIntPoint point = RoundedToInt(pointInLayerPixels.ref());
 
   // test against event regions in Layer coordinate space
   if (!mEventRegions.mHitRegion.Contains(point.x, point.y)) {
-    return HitTestResult::NoApzcHit;
+    return HitTestResult::HitNothing;
   }
   if (mEventRegions.mDispatchToContentHitRegion.Contains(point.x, point.y)) {
-    return HitTestResult::ApzcContentRegion;
+    return HitTestResult::HitDispatchToContentRegion;
   }
-  return HitTestResult::ApzcHitRegion;
+  return HitTestResult::HitLayer;
 }
 
 void
 HitTestingTreeNode::Dump(const char* aPrefix) const
 {
   if (mPrevSibling) {
     mPrevSibling->Dump(aPrefix);
   }
--- a/gfx/tests/gtest/TestAsyncPanZoomController.cpp
+++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp
@@ -2547,16 +2547,35 @@ protected:
     regions.mHitRegion = nsIntRegion(nsIntRect(0, 100, 200, 100));
     layers[2]->SetEventRegions(regions);
 
     registration = MakeUnique<ScopedLayerTreeRegistration>(0, root, mcc);
     manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
     rootApzc = ApzcOf(root);
   }
 
+  void CreateBug1119497LayerTree() {
+    const char* layerTreeSyntax = "c(tt)";
+    // LayerID                     0 12
+    // 0 is the root and doesn't have an APZC
+    // 1 is behind 2 and does have an APZC
+    // 2 entirely covers 1 and should take all the input events
+    nsIntRegion layerVisibleRegions[] = {
+      nsIntRegion(nsIntRect(0, 0, 100, 100)),
+      nsIntRegion(nsIntRect(0, 0, 100, 100)),
+      nsIntRegion(nsIntRect(0, 0, 100, 100)),
+    };
+    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers);
+
+    SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1);
+
+    registration = MakeUnique<ScopedLayerTreeRegistration>(0, root, mcc);
+    manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
+  }
+
   void CreateBug1117712LayerTree() {
     const char* layerTreeSyntax = "c(c(t)t)";
     // LayerID                     0 1 2 3
     // 0 is the root
     // 1 is a container layer whose sole purpose to make a non-empty ancestor
     //   transform for 2, so that 2's screen-to-apzc and apzc-to-gecko
     //   transforms are different from 3's.
     // 2 is a small layer that is the actual target
@@ -2671,17 +2690,30 @@ TEST_F(APZEventRegionsTester, Obscuratio
   TestAsyncPanZoomController* child = ApzcOf(layers[2]);
 
   int time = 0;
   ApzcPanNoFling(parent, time, 75, 25);
 
   HitTestResult result;
   nsRefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(ScreenPoint(50, 75), &result);
   EXPECT_EQ(child, hit.get());
-  EXPECT_EQ(HitTestResult::ApzcHitRegion, result);
+  EXPECT_EQ(HitTestResult::HitLayer, result);
+}
+
+TEST_F(APZEventRegionsTester, Bug1119497) {
+  SCOPED_GFX_PREF(LayoutEventRegionsEnabled, bool, true);
+
+  CreateBug1119497LayerTree();
+
+  HitTestResult result;
+  nsRefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(ScreenPoint(50, 50), &result);
+  // We should hit layers[2], so |result| will be HitLayer but there's no
+  // actual APZC in that parent chain, so |hit| should be nullptr.
+  EXPECT_EQ(nullptr, hit.get());
+  EXPECT_EQ(HitTestResult::HitLayer, result);
 }
 
 TEST_F(APZEventRegionsTester, Bug1117712) {
   SCOPED_GFX_PREF(LayoutEventRegionsEnabled, bool, true);
 
   CreateBug1117712LayerTree();
 
   TestAsyncPanZoomController* apzc2 = ApzcOf(layers[2]);
--- a/gfx/thebes/VsyncSource.h
+++ b/gfx/thebes/VsyncSource.h
@@ -60,19 +60,18 @@ public:
       nsTArray<nsRefPtr<CompositorVsyncDispatcher>> mCompositorVsyncDispatchers;
       nsRefPtr<RefreshTimerVsyncDispatcher> mRefreshTimerVsyncDispatcher;
   };
 
   void AddCompositorVsyncDispatcher(CompositorVsyncDispatcher* aCompositorVsyncDispatcher);
   void RemoveCompositorVsyncDispatcher(CompositorVsyncDispatcher* aCompositorVsyncDispatcher);
 
   nsRefPtr<RefreshTimerVsyncDispatcher> GetRefreshTimerVsyncDispatcher();
+  virtual Display& GetGlobalDisplay() = 0; // Works across all displays
 
 protected:
-  virtual Display& GetGlobalDisplay() = 0; // Works across all displays
-
   virtual ~VsyncSource() {}
 };
 
 } // namespace gfx
 } // namespace mozilla
 
 #endif /* GFX_VSYNCSOURCE_H */
--- a/gfx/thebes/gfxAndroidPlatform.cpp
+++ b/gfx/thebes/gfxAndroidPlatform.cpp
@@ -432,17 +432,16 @@ public:
   {
   }
 
   virtual Display& GetGlobalDisplay() MOZ_OVERRIDE
   {
     return mGlobalDisplay;
   }
 
-protected:
   class GonkDisplay MOZ_FINAL : public VsyncSource::Display
   {
   public:
     GonkDisplay() : mVsyncEnabled(false)
     {
       EnableVsync();
     }
 
@@ -481,14 +480,18 @@ private:
 }; // GonkVsyncSource
 #endif
 
 already_AddRefed<mozilla::gfx::VsyncSource>
 gfxAndroidPlatform::CreateHardwareVsyncSource()
 {
 #ifdef MOZ_WIDGET_GONK
     nsRefPtr<VsyncSource> vsyncSource = new GonkVsyncSource();
+    if (!vsyncSource->GetGlobalDisplay().IsVsyncEnabled()) {
+        NS_WARNING("Error enabling gonk vsync. Falling back to software vsync\n");
+        return gfxPlatform::CreateHardwareVsyncSource();
+    }
     return vsyncSource.forget();
 #else
     NS_WARNING("Hardware vsync not supported on android yet");
     return nullptr;
 #endif
 }
--- a/gfx/thebes/gfxDWriteFontList.cpp
+++ b/gfx/thebes/gfxDWriteFontList.cpp
@@ -1536,17 +1536,17 @@ gfxDWriteFontList::GlobalFontFallback(co
         return nullptr;
     }
 
     gfxFontFamily *family = FindFamily(mFallbackRenderer->FallbackFamilyName());
     if (family) {
         gfxFontEntry *fontEntry;
         bool needsBold;  // ignored in the system fallback case
         fontEntry = family->FindFontForStyle(*aMatchStyle, needsBold);
-        if (fontEntry && fontEntry->TestCharacterMap(aCh)) {
+        if (fontEntry && fontEntry->HasCharacter(aCh)) {
             *aMatchedFamily = family;
             return fontEntry;
         }
         Telemetry::Accumulate(Telemetry::BAD_FALLBACK_FONT, true);
     }
 
     return nullptr;
 }
--- a/gfx/thebes/gfxFontEntry.cpp
+++ b/gfx/thebes/gfxFontEntry.cpp
@@ -1468,17 +1468,17 @@ gfxFontFamily::FindFontForChar(GlobalFon
     gfxFontEntry *fe =
         FindFontForStyle(aMatchData->mStyle ? *aMatchData->mStyle
                                             : gfxFontStyle(),
                          needsBold);
 
     if (fe && !fe->SkipDuringSystemFallback()) {
         int32_t rank = 0;
 
-        if (fe->TestCharacterMap(aMatchData->mCh)) {
+        if (fe->HasCharacter(aMatchData->mCh)) {
             rank += RANK_MATCHED_CMAP;
             aMatchData->mCount++;
 #ifdef PR_LOGGING
             PRLogModuleInfo *log = gfxPlatform::GetLog(eGfxLog_textrun);
 
             if (MOZ_UNLIKELY(PR_LOG_TEST(log, PR_LOG_DEBUG))) {
                 uint32_t unicodeRange = FindCharUnicodeRange(aMatchData->mCh);
                 uint32_t script = GetScriptCode(aMatchData->mCh);
@@ -1515,17 +1515,17 @@ gfxFontFamily::FindFontForChar(GlobalFon
 }
 
 void
 gfxFontFamily::SearchAllFontsForChar(GlobalFontMatch *aMatchData)
 {
     uint32_t i, numFonts = mAvailableFonts.Length();
     for (i = 0; i < numFonts; i++) {
         gfxFontEntry *fe = mAvailableFonts[i];
-        if (fe && fe->TestCharacterMap(aMatchData->mCh)) {
+        if (fe && fe->HasCharacter(aMatchData->mCh)) {
             int32_t rank = RANK_MATCHED_CMAP;
             rank += CalcStyleMatch(fe, aMatchData->mStyle);
             if (rank > aMatchData->mMatchRank
                 || (rank == aMatchData->mMatchRank &&
                     Compare(fe->Name(), aMatchData->mBestMatch->Name()) > 0))
             {
                 aMatchData->mBestMatch = fe;
                 aMatchData->mMatchedFamily = this;
--- a/gfx/thebes/gfxFontEntry.h
+++ b/gfx/thebes/gfxFontEntry.h
@@ -158,17 +158,16 @@ public:
     inline bool HasCharacter(uint32_t ch) {
         if (mCharacterMap && mCharacterMap->test(ch)) {
             return true;
         }
         return TestCharacterMap(ch);
     }
 
     virtual bool SkipDuringSystemFallback() { return false; }
-    virtual bool TestCharacterMap(uint32_t aCh);
     nsresult InitializeUVSMap();
     uint16_t GetUVSGlyph(uint32_t aCh, uint32_t aVS);
 
     // All concrete gfxFontEntry subclasses (except gfxUserFontEntry) need
     // to override this, otherwise the font will never be used as it will
     // be considered to support no characters.
     // ReadCMAP() must *always* set the mCharacterMap pointer to a valid
     // gfxCharacterMap, even if empty, as other code assumes this pointer
@@ -472,16 +471,19 @@ protected:
     hb_blob_t* GetTableFromFontData(const void* aFontData, uint32_t aTableTag);
 
     // lookup the cmap in cached font data
     virtual already_AddRefed<gfxCharacterMap>
     GetCMAPFromFontInfo(FontInfoData *aFontInfoData,
                         uint32_t& aUVSOffset,
                         bool& aSymbolFont);
 
+    // helper for HasCharacter(), which is what client code should call
+    virtual bool TestCharacterMap(uint32_t aCh);
+
     // Font's unitsPerEm from the 'head' table, if available (will be set to
     // kInvalidUPEM for non-sfnt font formats)
     uint16_t mUnitsPerEm;
 
     // Shaper-specific face objects, shared by all instantiations of the same
     // physical font, regardless of size.
     // Usually, only one of these will actually be created for any given font
     // entry, depending on the font tables that are present.
--- a/gfx/thebes/gfxHarfBuzzShaper.cpp
+++ b/gfx/thebes/gfxHarfBuzzShaper.cpp
@@ -36,40 +36,55 @@ using namespace mozilla::unicode; // for
 gfxHarfBuzzShaper::gfxHarfBuzzShaper(gfxFont *aFont)
     : gfxFontShaper(aFont),
       mHBFace(aFont->GetFontEntry()->GetHBFace()),
       mHBFont(nullptr),
       mKernTable(nullptr),
       mHmtxTable(nullptr),
       mVmtxTable(nullptr),
       mVORGTable(nullptr),
+      mLocaTable(nullptr),
+      mGlyfTable(nullptr),
       mCmapTable(nullptr),
       mCmapFormat(-1),
       mSubtableOffset(0),
       mUVSTableOffset(0),
       mNumLongHMetrics(0),
       mNumLongVMetrics(0),
       mUseFontGetGlyph(aFont->ProvidesGetGlyph()),
       mUseFontGlyphWidths(false),
       mInitialized(false),
-      mVerticalInitialized(false)
+      mVerticalInitialized(false),
+      mLocaLongOffsets(false)
 {
 }
 
 gfxHarfBuzzShaper::~gfxHarfBuzzShaper()
 {
     if (mCmapTable) {
         hb_blob_destroy(mCmapTable);
     }
     if (mHmtxTable) {
         hb_blob_destroy(mHmtxTable);
     }
     if (mKernTable) {
         hb_blob_destroy(mKernTable);
     }
+    if (mVmtxTable) {
+        hb_blob_destroy(mVmtxTable);
+    }
+    if (mVORGTable) {
+        hb_blob_destroy(mVORGTable);
+    }
+    if (mLocaTable) {
+        hb_blob_destroy(mLocaTable);
+    }
+    if (mGlyfTable) {
+        hb_blob_destroy(mGlyfTable);
+    }
     if (mHBFont) {
         hb_font_destroy(mHBFont);
     }
     if (mHBFace) {
         hb_face_destroy(mHBFace);
     }
 }
 
@@ -325,16 +340,82 @@ gfxHarfBuzzShaper::GetGlyphVOrigin(hb_co
                                 int16_t(lo->vertOriginY));
         } else {
             *aY = -FloatToFixed(GetFont()->FUnitsToDevUnitsFactor() *
                                 int16_t(vorg->defaultVertOriginY));
         }
         return;
     }
 
+    if (mVmtxTable) {
+        if (mLocaTable && mGlyfTable) {
+            // TrueType outlines: use glyph bbox + top sidebearing
+            uint32_t offset; // offset of glyph record in the 'glyf' table
+            uint32_t len;
+            const char* data = hb_blob_get_data(mLocaTable, &len);
+            if (mLocaLongOffsets) {
+                if ((aGlyph + 1) * sizeof(AutoSwap_PRUint32) > len) {
+                    *aY = 0;
+                    return;
+                }
+                const AutoSwap_PRUint32* offsets =
+                    reinterpret_cast<const AutoSwap_PRUint32*>(data);
+                offset = offsets[aGlyph];
+                if (offset == offsets[aGlyph + 1]) {
+                    // empty glyph
+                    *aY = 0;
+                    return;
+                }
+            } else {
+                if ((aGlyph + 1) * sizeof(AutoSwap_PRUint16) > len) {
+                    *aY = 0;
+                    return;
+                }
+                const AutoSwap_PRUint16* offsets =
+                    reinterpret_cast<const AutoSwap_PRUint16*>(data);
+                offset = uint16_t(offsets[aGlyph]);
+                if (offset == uint16_t(offsets[aGlyph + 1])) {
+                    // empty glyph
+                    *aY = 0;
+                    return;
+                }
+                offset *= 2;
+            }
+
+            struct Glyf { // we only need the bounding-box at the beginning
+                          // of the glyph record, not the actual outline data
+                AutoSwap_PRInt16 numberOfContours;
+                AutoSwap_PRInt16 xMin;
+                AutoSwap_PRInt16 yMin;
+                AutoSwap_PRInt16 xMax;
+                AutoSwap_PRInt16 yMax;
+            };
+            data = hb_blob_get_data(mGlyfTable, &len);
+            if (offset + sizeof(Glyf) > len) {
+                *aY = 0;
+                return;
+            }
+            const Glyf* glyf = reinterpret_cast<const Glyf*>(data + offset);
+
+            if (aGlyph >= uint32_t(mNumLongVMetrics)) {
+                aGlyph = mNumLongVMetrics - 1;
+            }
+            const GlyphMetrics* metrics =
+                reinterpret_cast<const GlyphMetrics*>
+                    (hb_blob_get_data(mVmtxTable, nullptr));
+            *aY = -FloatToFixed(mFont->FUnitsToDevUnitsFactor() *
+                                (int16_t(metrics->metrics[aGlyph].lsb) +
+                                 int16_t(glyf->yMax)));
+            return;
+        } else {
+            // XXX TODO: CFF outlines - need to get glyph extents.
+            // For now, fall through to default code below.
+        }
+    }
+
     // XXX should we consider using OS/2 sTypo* metrics if available?
 
     gfxFontEntry::AutoTable hheaTable(GetFont()->GetFontEntry(),
                                       TRUETYPE_TAG('h','h','e','a'));
     if (hheaTable) {
         uint32_t len;
         const MetricsHeader* hhea =
             reinterpret_cast<const MetricsHeader*>(hb_blob_get_data(hheaTable,
@@ -1139,16 +1220,30 @@ gfxHarfBuzzShaper::InitializeVertical()
                               sizeof(VORGrec)) {
                 // VORG table is an unknown version, or not large enough
                 // to be valid -- discard it.
                 NS_WARNING("discarding invalid VORG table");
                 hb_blob_destroy(mVORGTable);
                 mVORGTable = nullptr;
             }
         }
+    } else if (mVmtxTable) {
+        // Otherwise, try to load loca and glyf tables so that we can read
+        // bounding boxes (needed to support vertical glyph origin).
+        uint32_t len;
+        gfxFontEntry::AutoTable headTable(entry,
+                                          TRUETYPE_TAG('h','e','a','d'));
+        const HeadTable* head =
+            reinterpret_cast<const HeadTable*>(hb_blob_get_data(headTable,
+                                                                &len));
+        if (len >= sizeof(HeadTable)) {
+            mLocaLongOffsets = int16_t(head->indexToLocFormat) > 0;
+            mLocaTable = entry->GetFontTable(TRUETYPE_TAG('l','o','c','a'));
+            mGlyfTable = entry->GetFontTable(TRUETYPE_TAG('g','l','y','f'));
+        }
     }
 
     return true;
 }
 
 bool
 gfxHarfBuzzShaper::ShapeText(gfxContext      *aContext,
                              const char16_t *aText,
--- a/gfx/thebes/gfxHarfBuzzShaper.h
+++ b/gfx/thebes/gfxHarfBuzzShaper.h
@@ -125,16 +125,20 @@ protected:
     mutable hb_blob_t *mKernTable;
 
     // Cached copy of the hmtx table.
     mutable hb_blob_t *mHmtxTable;
 
     // For vertical fonts, cached vmtx and VORG table, if present.
     mutable hb_blob_t *mVmtxTable;
     mutable hb_blob_t *mVORGTable;
+    // And for vertical TrueType (not CFF) fonts that have vmtx,
+    // we also use loca and glyf to get glyph bounding boxes.
+    mutable hb_blob_t *mLocaTable;
+    mutable hb_blob_t *mGlyfTable;
 
     // Cached pointer to cmap subtable to be used for char-to-glyph mapping.
     // This comes from GetFontTablePtr; if it is non-null, our destructor
     // must call ReleaseFontTablePtr to avoid permanently caching the table.
     mutable hb_blob_t *mCmapTable;
     mutable int32_t    mCmapFormat;
     mutable uint32_t   mSubtableOffset;
     mutable uint32_t   mUVSTableOffset;
@@ -152,11 +156,12 @@ protected:
     // directly
     bool mUseFontGetGlyph;
     // Whether the font implements GetGlyphWidth, or we should read tables
     // directly to get ideal widths
     bool mUseFontGlyphWidths;
 
     bool mInitialized;
     bool mVerticalInitialized;
+    bool mLocaLongOffsets;
 };
 
 #endif /* GFX_HARFBUZZSHAPER_H */
--- a/gfx/thebes/gfxMacPlatformFontList.mm
+++ b/gfx/thebes/gfxMacPlatformFontList.mm
@@ -857,17 +857,17 @@ gfxMacPlatformFontList::GlobalFontFallba
             nsDependentString familyName(reinterpret_cast<char16_t*>(buffer.Elements()), len);
 
             bool needsBold;  // ignored in the system fallback case
 
             gfxFontFamily *family = FindFamily(familyName);
             if (family) {
                 fontEntry = family->FindFontForStyle(*aMatchStyle, needsBold);
                 if (fontEntry) {
-                    if (fontEntry->TestCharacterMap(aCh)) {
+                    if (fontEntry->HasCharacter(aCh)) {
                         *aMatchedFamily = family;
                     } else {
                         fontEntry = nullptr;
                         cantUseFallbackFont = true;
                     }
                 }
             }
         }
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -164,16 +164,18 @@ public:
 /// this gets called to be large - it is meant for critical errors only.
 
 class CrashStatsLogForwarder: public mozilla::gfx::LogForwarder
 {
 public:
   explicit CrashStatsLogForwarder(const char* aKey);
   virtual void Log(const std::string& aString) MOZ_OVERRIDE;
 
+  virtual std::vector<std::pair<int32_t,std::string> > StringsVectorCopy();
+
   void SetCircularBufferSize(uint32_t aCapacity);
 
 private:
   // Helpers for the Log()
   bool UpdateStringsVector(const std::string& aString);
   void UpdateCrashReport();
 
 private:
@@ -196,16 +198,23 @@ CrashStatsLogForwarder::CrashStatsLogFor
 void CrashStatsLogForwarder::SetCircularBufferSize(uint32_t aCapacity)
 {
   MutexAutoLock lock(mMutex);
 
   mMaxCapacity = aCapacity;
   mBuffer.reserve(static_cast<size_t>(aCapacity));
 }
 
+std::vector<std::pair<int32_t,std::string> >
+CrashStatsLogForwarder::StringsVectorCopy()
+{
+  MutexAutoLock lock(mMutex);
+  return mBuffer;
+}
+
 bool
 CrashStatsLogForwarder::UpdateStringsVector(const std::string& aString)
 {
   // We want at least the first one and the last one.  Otherwise, no point.
   if (mMaxCapacity < 2) {
     return false;
   }
 
--- a/gfx/thebes/gfxPlatformFontList.cpp
+++ b/gfx/thebes/gfxPlatformFontList.cpp
@@ -559,17 +559,17 @@ gfxPlatformFontList::SystemFindFontForCh
         bool needsBold;  // ignored in the system fallback case
 
         fontEntry =
             mReplacementCharFallbackFamily->FindFontForStyle(*aStyle,
                                                              needsBold);
 
         // this should never fail, as we must have found U+FFFD in order to set
         // mReplacementCharFallbackFamily at all, but better play it safe
-        if (fontEntry && fontEntry->TestCharacterMap(aCh)) {
+        if (fontEntry && fontEntry->HasCharacter(aCh)) {
             return fontEntry;
         }
     }
 
     TimeStamp start = TimeStamp::Now();
 
     // search commonly available fonts
     bool common = true;
@@ -665,17 +665,17 @@ gfxPlatformFontList::CommonFontFallback(
         if (!fallback)
             continue;
 
         gfxFontEntry *fontEntry;
         bool needsBold;  // ignored in the system fallback case
 
         // use first font in list that supports a given character
         fontEntry = fallback->FindFontForStyle(*aMatchStyle, needsBold);
-        if (fontEntry && fontEntry->TestCharacterMap(aCh)) {
+        if (fontEntry && fontEntry->HasCharacter(aCh)) {
             *aMatchedFamily = fallback;
             return fontEntry;
         }
     }
 
     return nullptr;
 }
 
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -214,16 +214,17 @@ private:
   DECL_GFX_PREF(Live, "gfx.perf-warnings.enabled",             PerfWarnings, bool, false);
   DECL_GFX_PREF(Once, "gfx.work-around-driver-bugs",           WorkAroundDriverBugs, bool, true);
 
   DECL_GFX_PREF(Live, "gfx.draw-color-bars",                   CompositorDrawColorBars, bool, false);
 
   // Use vsync events generated by hardware
   DECL_GFX_PREF(Once, "gfx.vsync.hw-vsync.enabled",            HardwareVsyncEnabled, bool, false);
   DECL_GFX_PREF(Once, "gfx.vsync.compositor",                  VsyncAlignedCompositor, bool, false);
+  DECL_GFX_PREF(Once, "gfx.vsync.refreshdriver",               VsyncAlignedRefreshDriver, bool, false);
   // On b2g, in really bad cases, I've seen up to 80 ms delays between touch events and the main thread
   // processing them. So 80 ms / 16 = 5 vsync events. Double it up just to be on the safe side, so 10.
   DECL_GFX_PREF(Once, "gfx.vsync.compositor.unobserve-count",  CompositorUnobserveCount, int32_t, 10);
   DECL_GFX_PREF(Once, "gfx.touch.resample",                    TouchResampling, bool, false);
   // These times should be in milliseconds
   DECL_GFX_PREF(Once, "gfx.touch.resample.max-predict",        TouchResampleMaxPredict, int32_t, 8);
   DECL_GFX_PREF(Once, "gfx.touch.resample.vsync-adjust",       TouchVsyncSampleAdjust, int32_t, 5);
   DECL_GFX_PREF(Once, "gfx.touch.resample.delay-threshold",    TouchResampleVsyncDelayThreshold, int32_t, 20);
--- a/gfx/thebes/gfxTextRun.cpp
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -3092,17 +3092,17 @@ gfxFontGroup::WhichPrefFontSupportsChar(
             if (family == mLastPrefFamily && mLastPrefFont->HasCharacter(aCh)) {
                 font = mLastPrefFont;
                 return font.forget();
             }
 
             bool needsBold;
             gfxFontEntry *fe = family->FindFontForStyle(mStyle, needsBold);
             // if ch in cmap, create and return a gfxFont
-            if (fe && fe->TestCharacterMap(aCh)) {
+            if (fe && fe->HasCharacter(aCh)) {
                 nsRefPtr<gfxFont> prefFont = fe->FindOrMakeFont(&mStyle, needsBold);
                 if (!prefFont) continue;
                 mLastPrefFamily = family;
                 mLastPrefFont = prefFont;
                 mLastPrefLang = charLang;
                 mLastPrefFirstFont = (i == 0 && j == 0);
                 return prefFont.forget();
             }
--- a/ipc/glue/BackgroundChildImpl.cpp
+++ b/ipc/glue/BackgroundChildImpl.cpp
@@ -1,21 +1,23 @@
 /* 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 "BackgroundChildImpl.h"
 
 #include "ActorsChild.h" // IndexedDB
+#include "BroadcastChannelChild.h"
 #include "FileDescriptorSetChild.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/dom/PBlobChild.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryChild.h"
 #include "mozilla/dom/ipc/BlobChild.h"
 #include "mozilla/ipc/PBackgroundTestChild.h"
+#include "mozilla/layout/VsyncChild.h"
 #include "nsID.h"
 #include "nsTraceRefcnt.h"
 
 namespace {
 
 class TestChild MOZ_FINAL : public mozilla::ipc::PBackgroundTestChild
 {
   friend class mozilla::ipc::BackgroundChildImpl;
@@ -180,16 +182,55 @@ BackgroundChildImpl::DeallocPFileDescrip
                                                 PFileDescriptorSetChild* aActor)
 {
   MOZ_ASSERT(aActor);
 
   delete static_cast<FileDescriptorSetChild*>(aActor);
   return true;
 }
 
+BackgroundChildImpl::PVsyncChild*
+BackgroundChildImpl::AllocPVsyncChild()
+{
+  return new mozilla::layout::VsyncChild();
+}
+
+bool
+BackgroundChildImpl::DeallocPVsyncChild(PVsyncChild* aActor)
+{
+  MOZ_ASSERT(aActor);
+
+  delete static_cast<mozilla::layout::VsyncChild*>(aActor);
+  return true;
+}
+
+// -----------------------------------------------------------------------------
+// BroadcastChannel API
+// -----------------------------------------------------------------------------
+
+dom::PBroadcastChannelChild*
+BackgroundChildImpl::AllocPBroadcastChannelChild(const PrincipalInfo& aPrincipalInfo,
+                                                 const nsString& aOrigin,
+                                                 const nsString& aChannel)
+{
+  nsRefPtr<dom::BroadcastChannelChild> agent =
+    new dom::BroadcastChannelChild(aOrigin, aChannel);
+  return agent.forget().take();
+}
+
+bool
+BackgroundChildImpl::DeallocPBroadcastChannelChild(
+                                                 PBroadcastChannelChild* aActor)
+{
+  nsRefPtr<dom::BroadcastChannelChild> child =
+    dont_AddRef(static_cast<dom::BroadcastChannelChild*>(aActor));
+  MOZ_ASSERT(child);
+  return true;
+}
+
 } // namespace ipc
 } // namespace mozilla
 
 bool
 TestChild::Recv__delete__(const nsCString& aTestArg)
 {
   MOZ_RELEASE_ASSERT(aTestArg == mTestArg,
                      "BackgroundTest message was corrupted!");
--- a/ipc/glue/BackgroundChildImpl.h
+++ b/ipc/glue/BackgroundChildImpl.h
@@ -65,16 +65,30 @@ protected:
   DeallocPBlobChild(PBlobChild* aActor) MOZ_OVERRIDE;
 
   virtual PFileDescriptorSetChild*
   AllocPFileDescriptorSetChild(const FileDescriptor& aFileDescriptor)
                                MOZ_OVERRIDE;
 
   virtual bool
   DeallocPFileDescriptorSetChild(PFileDescriptorSetChild* aActor) MOZ_OVERRIDE;
+
+  virtual PVsyncChild*
+  AllocPVsyncChild() MOZ_OVERRIDE;
+
+  virtual bool
+  DeallocPVsyncChild(PVsyncChild* aActor) MOZ_OVERRIDE;
+
+  virtual PBroadcastChannelChild*
+  AllocPBroadcastChannelChild(const PrincipalInfo& aPrincipalInfo,
+                              const nsString& aOrigin,
+                              const nsString& aChannel) MOZ_OVERRIDE;
+
+  virtual bool
+  DeallocPBroadcastChannelChild(PBroadcastChannelChild* aActor) MOZ_OVERRIDE;
 };
 
 class BackgroundChildImpl::ThreadLocal MOZ_FINAL
 {
   friend class nsAutoPtr<ThreadLocal>;
 
 public:
   nsAutoPtr<mozilla::dom::indexedDB::ThreadLocal> mIndexedDBThreadLocal;
--- a/ipc/glue/BackgroundParentImpl.cpp
+++ b/ipc/glue/BackgroundParentImpl.cpp
@@ -1,21 +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/. */
 
 #include "BackgroundParentImpl.h"
 
+#include "BroadcastChannelParent.h"
 #include "FileDescriptorSetParent.h"
+#include "mozilla/AppProcessChecker.h"
 #include "mozilla/Assertions.h"
+#include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/PBlobParent.h"
 #include "mozilla/dom/indexedDB/ActorsParent.h"
 #include "mozilla/dom/ipc/BlobParent.h"
 #include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/BackgroundUtils.h"
 #include "mozilla/ipc/PBackgroundTestParent.h"
+#include "mozilla/layout/VsyncParent.h"
+#include "nsNetUtil.h"
+#include "nsRefPtr.h"
 #include "nsThreadUtils.h"
 #include "nsTraceRefcnt.h"
 #include "nsXULAppAPI.h"
 
 #ifdef DISABLE_ASSERTS_FOR_FUZZING
 #define ASSERT_UNLESS_FUZZING(...) do { } while (0)
 #else
 #define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false)
@@ -57,16 +64,19 @@ public:
   ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
 };
 
 } // anonymous namespace
 
 namespace mozilla {
 namespace ipc {
 
+using mozilla::dom::ContentParent;
+using mozilla::dom::BroadcastChannelParent;
+
 BackgroundParentImpl::BackgroundParentImpl()
 {
   AssertIsInMainProcess();
   AssertIsOnMainThread();
 
   MOZ_COUNT_CTOR(mozilla::ipc::BackgroundParentImpl);
 }
 
@@ -202,16 +212,152 @@ BackgroundParentImpl::DeallocPFileDescri
   AssertIsInMainProcess();
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aActor);
 
   delete static_cast<FileDescriptorSetParent*>(aActor);
   return true;
 }
 
+BackgroundParentImpl::PVsyncParent*
+BackgroundParentImpl::AllocPVsyncParent()
+{
+  AssertIsInMainProcess();
+  AssertIsOnBackgroundThread();
+
+  nsRefPtr<mozilla::layout::VsyncParent> actor =
+      mozilla::layout::VsyncParent::Create();
+  // There still has one ref-count after return, and it will be released in
+  // DeallocPVsyncParent().
+  return actor.forget().take();
+}
+
+bool
+BackgroundParentImpl::DeallocPVsyncParent(PVsyncParent* aActor)
+{
+  AssertIsInMainProcess();
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+
+  // This actor already has one ref-count. Please check AllocPVsyncParent().
+  nsRefPtr<mozilla::layout::VsyncParent> actor =
+      dont_AddRef(static_cast<mozilla::layout::VsyncParent*>(aActor));
+  return true;
+}
+
+mozilla::dom::PBroadcastChannelParent*
+BackgroundParentImpl::AllocPBroadcastChannelParent(
+                                            const PrincipalInfo& aPrincipalInfo,
+                                            const nsString& aOrigin,
+                                            const nsString& aChannel)
+{
+  AssertIsInMainProcess();
+  AssertIsOnBackgroundThread();
+
+  return new BroadcastChannelParent(aOrigin, aChannel);
+}
+
+namespace {
+
+class CheckPrincipalRunnable MOZ_FINAL : public nsRunnable
+{
+public:
+  CheckPrincipalRunnable(already_AddRefed<ContentParent> aParent,
+                         const PrincipalInfo& aPrincipalInfo,
+                         const nsString& aOrigin)
+    : mContentParent(aParent)
+    , mPrincipalInfo(aPrincipalInfo)
+    , mOrigin(aOrigin)
+    , mBackgroundThread(NS_GetCurrentThread())
+  {
+    AssertIsInMainProcess();
+    AssertIsOnBackgroundThread();
+
+    MOZ_ASSERT(mContentParent);
+    MOZ_ASSERT(mBackgroundThread);
+  }
+
+  NS_IMETHODIMP Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    nsCOMPtr<nsIPrincipal> principal = PrincipalInfoToPrincipal(mPrincipalInfo);
+    AssertAppPrincipal(mContentParent, principal);
+
+    bool isNullPrincipal;
+    nsresult rv = principal->GetIsNullPrincipal(&isNullPrincipal);
+    if (NS_WARN_IF(NS_FAILED(rv)) || isNullPrincipal) {
+      mContentParent->KillHard();
+      return NS_OK;
+    }
+
+    nsCOMPtr<nsIURI> uri;
+    rv = NS_NewURI(getter_AddRefs(uri), mOrigin);
+    if (NS_FAILED(rv) || !uri) {
+      mContentParent->KillHard();
+      return NS_OK;
+    }
+
+    rv = principal->CheckMayLoad(uri, false, false);
+    if (NS_FAILED(rv)) {
+      mContentParent->KillHard();
+      return NS_OK;
+    }
+
+    mContentParent = nullptr;
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<ContentParent> mContentParent;
+  PrincipalInfo mPrincipalInfo;
+  nsString mOrigin;
+  nsCOMPtr<nsIThread> mBackgroundThread;
+};
+
+} // anonymous namespace
+
+bool
+BackgroundParentImpl::RecvPBroadcastChannelConstructor(
+                                            PBroadcastChannelParent* actor,
+                                            const PrincipalInfo& aPrincipalInfo,
+                                            const nsString& aOrigin,
+                                            const nsString& aChannel)
+{
+  AssertIsInMainProcess();
+  AssertIsOnBackgroundThread();
+
+  nsRefPtr<ContentParent> parent = BackgroundParent::GetContentParent(this);
+
+  // If the ContentParent is null we are dealing with a same-process actor.
+  if (!parent) {
+    MOZ_ASSERT(aPrincipalInfo.type() != PrincipalInfo::TNullPrincipalInfo);
+    return true;
+  }
+
+  nsRefPtr<CheckPrincipalRunnable> runnable =
+    new CheckPrincipalRunnable(parent.forget(), aPrincipalInfo, aOrigin);
+  nsresult rv = NS_DispatchToMainThread(runnable);
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(rv));
+
+  return true;
+}
+
+bool
+BackgroundParentImpl::DeallocPBroadcastChannelParent(
+                                                PBroadcastChannelParent* aActor)
+{
+  AssertIsInMainProcess();
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+
+  delete static_cast<BroadcastChannelParent*>(aActor);
+  return true;
+}
+
 } // namespace ipc
 } // namespace mozilla
 
 void
 TestParent::ActorDestroy(ActorDestroyReason aWhy)
 {
   AssertIsInMainProcess();
   AssertIsOnBackgroundThread();
--- a/ipc/glue/BackgroundParentImpl.h
+++ b/ipc/glue/BackgroundParentImpl.h
@@ -4,16 +4,21 @@
 
 #ifndef mozilla_ipc_backgroundparentimpl_h__
 #define mozilla_ipc_backgroundparentimpl_h__
 
 #include "mozilla/Attributes.h"
 #include "mozilla/ipc/PBackgroundParent.h"
 
 namespace mozilla {
+
+namespace layout {
+class VsyncParent;
+}
+
 namespace ipc {
 
 // Instances of this class should never be created directly. This class is meant
 // to be inherited in BackgroundImpl.
 class BackgroundParentImpl : public PBackgroundParent
 {
 protected:
   BackgroundParentImpl();
@@ -53,14 +58,34 @@ protected:
 
   virtual PFileDescriptorSetParent*
   AllocPFileDescriptorSetParent(const FileDescriptor& aFileDescriptor)
                                 MOZ_OVERRIDE;
 
   virtual bool
   DeallocPFileDescriptorSetParent(PFileDescriptorSetParent* aActor)
                                   MOZ_OVERRIDE;
+
+  virtual PVsyncParent*
+  AllocPVsyncParent() MOZ_OVERRIDE;
+
+  virtual bool
+  DeallocPVsyncParent(PVsyncParent* aActor) MOZ_OVERRIDE;
+
+  virtual PBroadcastChannelParent*
+  AllocPBroadcastChannelParent(const PrincipalInfo& aPrincipalInfo,
+                               const nsString& aOrigin,
+                               const nsString& aChannel) MOZ_OVERRIDE;
+
+  virtual bool
+  RecvPBroadcastChannelConstructor(PBroadcastChannelParent* actor,
+                                   const PrincipalInfo& aPrincipalInfo,
+                                   const nsString& origin,
+                                   const nsString& channel) MOZ_OVERRIDE;
+
+  virtual bool
+  DeallocPBroadcastChannelParent(PBroadcastChannelParent* aActor) MOZ_OVERRIDE;
 };
 
 } // namespace ipc
 } // namespace mozilla
 
 #endif // mozilla_ipc_backgroundparentimpl_h__
--- a/ipc/glue/PBackground.ipdl
+++ b/ipc/glue/PBackground.ipdl
@@ -1,36 +1,45 @@
 /* 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 protocol PBackgroundIDBFactory;
 include protocol PBackgroundTest;
 include protocol PBlob;
+include protocol PBroadcastChannel;
 include protocol PFileDescriptorSet;
+include protocol PVsync;
 
 include DOMTypes;
+include PBackgroundSharedTypes;
 include PBackgroundIDBSharedTypes;
 
 namespace mozilla {
 namespace ipc {
 
 sync protocol PBackground
 {
   manages PBackgroundIDBFactory;
   manages PBackgroundTest;
   manages PBlob;
+  manages PBroadcastChannel;
   manages PFileDescriptorSet;
+  manages PVsync;
 
 parent:
   // Only called at startup during mochitests to check the basic infrastructure.
   PBackgroundTest(nsCString testArg);
 
   PBackgroundIDBFactory(LoggingInfo loggingInfo);
 
+  PVsync();
+
+  PBroadcastChannel(PrincipalInfo pInfo, nsString origin, nsString channel);
+
 both:
   PBlob(BlobConstructorParams params);
 
   PFileDescriptorSet(FileDescriptor fd);
 };
 
 } // namespace ipc
 } // namespace mozilla
--- a/ipc/glue/moz.build
+++ b/ipc/glue/moz.build
@@ -125,16 +125,17 @@ SOURCES += [
     'BackgroundParentImpl.cpp',
     'FileDescriptorSetChild.cpp',
     'FileDescriptorSetParent.cpp',
     'GeckoChildProcessHost.cpp',
     'URIUtils.cpp',
 ]
 
 LOCAL_INCLUDES += [
+    '/dom/broadcastchannel',
     '/dom/indexedDB',
     '/xpcom/build',
 ]
 
 IPDL_SOURCES = [
     'InputStreamParams.ipdlh',
     'PBackground.ipdl',
     'PBackgroundSharedTypes.ipdlh',
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -488,17 +488,23 @@ namespace js {
 
 struct Class
 {
     JS_CLASS_MEMBERS(FinalizeOp);
     ClassSpec          spec;
     ClassExtension      ext;
     ObjectOps           ops;
 
-    /* Class is not native and its map is not a scope. */
+    /*
+     * Objects of this class aren't native objects. They don't have Shapes that
+     * describe their properties and layout. Classes using this flag must
+     * provide their own property behavior, either by being proxy classes (do
+     * this) or by overriding all the ObjectOps except getElements, watch,
+     * unwatch, and thisObject (don't do this).
+     */
     static const uint32_t NON_NATIVE = JSCLASS_INTERNAL_FLAG2;
 
     bool isNative() const {
         return !(flags & NON_NATIVE);
     }
 
     bool hasPrivate() const {
         return !!(flags & JSCLASS_HAS_PRIVATE);
--- a/js/src/asmjs/AsmJSLink.cpp
+++ b/js/src/asmjs/AsmJSLink.cpp
@@ -1006,17 +1006,17 @@ CreateExportObject(JSContext *cx, Handle
 
         RootedFunction fun(cx, NewExportedFunction(cx, func, moduleObj, i));
         if (!fun)
             return nullptr;
 
         MOZ_ASSERT(func.maybeFieldName() != nullptr);
         RootedId id(cx, NameToId(func.maybeFieldName()));
         RootedValue val(cx, ObjectValue(*fun));
-        if (!DefineNativeProperty(cx, obj, id, val, nullptr, nullptr, JSPROP_ENUMERATE))
+        if (!NativeDefineProperty(cx, obj, id, val, nullptr, nullptr, JSPROP_ENUMERATE))
             return nullptr;
     }
 
     return obj;
 }
 
 static const unsigned MODULE_FUN_SLOT = 0;
 
--- a/js/src/builtin/AtomicsObject.cpp
+++ b/js/src/builtin/AtomicsObject.cpp
@@ -1054,17 +1054,17 @@ AtomicsObject::initClass(JSContext *cx, 
     if (!JS_DefineFunctions(cx, Atomics, AtomicsMethods))
         return nullptr;
     if (!JS_DefineConstDoubles(cx, Atomics, AtomicsConstants))
         return nullptr;
 
     RootedValue AtomicsValue(cx, ObjectValue(*Atomics));
 
     // Everything is set up, install Atomics on the global object.
-    if (!JSObject::defineProperty(cx, global, cx->names().Atomics, AtomicsValue, nullptr, nullptr, 0))
+    if (!DefineProperty(cx, global, cx->names().Atomics, AtomicsValue, nullptr, nullptr, 0))
         return nullptr;
 
     global->setConstructor(JSProto_Atomics, AtomicsValue);
     return Atomics;
 }
 
 JSObject *
 js_InitAtomicsClass(JSContext *cx, HandleObject obj)
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -266,17 +266,17 @@ EvalKernel(JSContext *cx, const CallArgs
         if (!ComputeThis(cx, caller))
             return false;
         thisv = caller.thisValue();
     } else {
         MOZ_ASSERT(args.callee().global() == *scopeobj);
         staticLevel = 0;
 
         // Use the global as 'this', modulo outerization.
-        JSObject *thisobj = JSObject::thisObject(cx, scopeobj);
+        JSObject *thisobj = GetThisObject(cx, scopeobj);
         if (!thisobj)
             return false;
         thisv = ObjectValue(*thisobj);
     }
 
     Rooted<JSFlatString*> flatStr(cx, str->ensureFlat(cx));
     if (!flatStr)
         return false;
@@ -504,17 +504,17 @@ js::ExecuteInGlobalAndReturnScope(JSCont
         return false;
 
     if (!scope->setQualifiedVarObj(cx))
         return false;
 
     if (!scope->setUnqualifiedVarObj(cx))
         return false;
 
-    JSObject *thisobj = JSObject::thisObject(cx, global);
+    JSObject *thisobj = GetThisObject(cx, global);
     if (!thisobj)
         return false;
 
     RootedValue thisv(cx, ObjectValue(*thisobj));
     RootedValue rval(cx);
     if (!ExecuteKernel(cx, script, *scope, thisv, EXECUTE_GLOBAL,
                        NullFramePtr() /* evalInFrame */, rval.address()))
     {
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -452,18 +452,18 @@ intl_availableLocales(JSContext *cx, Cou
         if (!lang)
             return false;
         char *p;
         while ((p = strchr(lang, '_')))
             *p = '-';
         RootedAtom a(cx, Atomize(cx, lang, strlen(lang)));
         if (!a)
             return false;
-        if (!JSObject::defineProperty(cx, locales, a->asPropertyName(), t, nullptr, nullptr,
-                                      JSPROP_ENUMERATE))
+        if (!DefineProperty(cx, locales, a->asPropertyName(), t, nullptr, nullptr,
+                            JSPROP_ENUMERATE))
         {
             return false;
         }
     }
 #endif
     result.setObject(*locales);
     return true;
 }
@@ -610,17 +610,17 @@ Collator(JSContext *cx, CallArgs args, b
         if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
             // 10.1.2.1 step 4
             obj = ToObject(cx, self);
             if (!obj)
                 return false;
 
             // 10.1.2.1 step 5
             bool extensible;
-            if (!JSObject::isExtensible(cx, obj, &extensible))
+            if (!IsExtensible(cx, obj, &extensible))
                 return false;
             if (!extensible)
                 return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE);
         } else {
             // 10.1.2.1 step 3.a
             construct = true;
         }
     }
@@ -698,34 +698,34 @@ InitCollatorClass(JSContext *cx, HandleO
     /*
      * Install the getter for Collator.prototype.compare, which returns a bound
      * comparison function for the specified Collator object (suitable for
      * passing to methods like Array.prototype.sort).
      */
     RootedValue getter(cx);
     if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().CollatorCompareGet, &getter))
         return nullptr;
-    if (!JSObject::defineProperty(cx, proto, cx->names().compare, UndefinedHandleValue,
-                                  JS_DATA_TO_FUNC_PTR(JSPropertyOp, &getter.toObject()),
-                                  nullptr, JSPROP_GETTER | JSPROP_SHARED))
+    if (!DefineProperty(cx, proto, cx->names().compare, UndefinedHandleValue,
+                        JS_DATA_TO_FUNC_PTR(JSPropertyOp, &getter.toObject()),
+                        nullptr, JSPROP_GETTER | JSPROP_SHARED))
     {
         return nullptr;
     }
 
     RootedValue options(cx);
     if (!CreateDefaultOptions(cx, &options))
         return nullptr;
 
     // 10.2.1 and 10.3
     if (!IntlInitialize(cx, proto, cx->names().InitializeCollator, UndefinedHandleValue, options))
         return nullptr;
 
     // 8.1
     RootedValue ctorValue(cx, ObjectValue(*ctor));
-    if (!JSObject::defineProperty(cx, Intl, cx->names().Collator, ctorValue, nullptr, nullptr, 0))
+    if (!DefineProperty(cx, Intl, cx->names().Collator, ctorValue, nullptr, nullptr, 0))
         return nullptr;
 
     return ctor;
 }
 
 bool
 GlobalObject::initCollatorProto(JSContext *cx, Handle<GlobalObject*> global)
 {
@@ -803,17 +803,17 @@ js::intl_availableCollations(JSContext *
             collation = "phonebk";
         else if (equal(collation, "traditional"))
             collation = "trad";
 
         RootedString jscollation(cx, JS_NewStringCopyZ(cx, collation));
         if (!jscollation)
             return false;
         RootedValue element(cx, StringValue(jscollation));
-        if (!JSObject::defineElement(cx, collations, index++, element))
+        if (!DefineElement(cx, collations, index++, element))
             return false;
     }
 
     args.rval().setObject(*collations);
     return true;
 }
 
 /**
@@ -824,32 +824,32 @@ static UCollator *
 NewUCollator(JSContext *cx, HandleObject collator)
 {
     RootedValue value(cx);
 
     RootedObject internals(cx);
     if (!GetInternals(cx, collator, &internals))
         return nullptr;
 
-    if (!JSObject::getProperty(cx, internals, internals, cx->names().locale, &value))
+    if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
         return nullptr;
     JSAutoByteString locale(cx, value.toString());
     if (!locale)
         return nullptr;
 
     // UCollator options with default values.
     UColAttributeValue uStrength = UCOL_DEFAULT;
     UColAttributeValue uCaseLevel = UCOL_OFF;
     UColAttributeValue uAlternate = UCOL_DEFAULT;
     UColAttributeValue uNumeric = UCOL_OFF;
     // Normalization is always on to meet the canonical equivalence requirement.
     UColAttributeValue uNormalization = UCOL_ON;
     UColAttributeValue uCaseFirst = UCOL_DEFAULT;
 
-    if (!JSObject::getProperty(cx, internals, internals, cx->names().usage, &value))
+    if (!GetProperty(cx, internals, internals, cx->names().usage, &value))
         return nullptr;
     JSAutoByteString usage(cx, value.toString());
     if (!usage)
         return nullptr;
     if (equal(usage, "search")) {
         // ICU expects search as a Unicode locale extension on locale.
         // Unicode locale extensions must occur before private use extensions.
         const char *oldLocale = locale.ptr();
@@ -878,49 +878,49 @@ NewUCollator(JSContext *cx, HandleObject
         locale.clear();
         locale.initBytes(newLocale);
     }
 
     // We don't need to look at the collation property - it can only be set
     // via the Unicode locale extension and is therefore already set on
     // locale.
 
-    if (!JSObject::getProperty(cx, internals, internals, cx->names().sensitivity, &value))
+    if (!GetProperty(cx, internals, internals, cx->names().sensitivity, &value))
         return nullptr;
     JSAutoByteString sensitivity(cx, value.toString());
     if (!sensitivity)
         return nullptr;
     if (equal(sensitivity, "base")) {
         uStrength = UCOL_PRIMARY;
     } else if (equal(sensitivity, "accent")) {
         uStrength = UCOL_SECONDARY;
     } else if (equal(sensitivity, "case")) {
         uStrength = UCOL_PRIMARY;
         uCaseLevel = UCOL_ON;
     } else {
         MOZ_ASSERT(equal(sensitivity, "variant"));
         uStrength = UCOL_TERTIARY;
     }
 
-    if (!JSObject::getProperty(cx, internals, internals, cx->names().ignorePunctuation, &value))
+    if (!GetProperty(cx, internals, internals, cx->names().ignorePunctuation, &value))
         return nullptr;
     // According to the ICU team, UCOL_SHIFTED causes punctuation to be
     // ignored. Looking at Unicode Technical Report 35, Unicode Locale Data
     // Markup Language, "shifted" causes whitespace and punctuation to be
     // ignored - that's a bit more than asked for, but there's no way to get
     // less.
     if (value.toBoolean())
         uAlternate = UCOL_SHIFTED;
 
-    if (!JSObject::getProperty(cx, internals, internals, cx->names().numeric, &value))
+    if (!GetProperty(cx, internals, internals, cx->names().numeric, &value))
         return nullptr;
     if (!value.isUndefined() && value.toBoolean())
         uNumeric = UCOL_ON;
 
-    if (!JSObject::getProperty(cx, internals, internals, cx->names().caseFirst, &value))
+    if (!GetProperty(cx, internals, internals, cx->names().caseFirst, &value))
         return nullptr;
     if (!value.isUndefined()) {
         JSAutoByteString caseFirst(cx, value.toString());
         if (!caseFirst)
             return nullptr;
         if (equal(caseFirst, "upper"))
             uCaseFirst = UCOL_UPPER_FIRST;
         else if (equal(caseFirst, "lower"))
@@ -1096,17 +1096,17 @@ NumberFormat(JSContext *cx, CallArgs arg
         if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
             // 11.1.2.1 step 4
             obj = ToObject(cx, self);
             if (!obj)
                 return false;
 
             // 11.1.2.1 step 5
             bool extensible;
-            if (!JSObject::isExtensible(cx, obj, &extensible))
+            if (!IsExtensible(cx, obj, &extensible))
                 return false;
             if (!extensible)
                 return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE);
         } else {
             // 11.1.2.1 step 3.a
             construct = true;
         }
     }
@@ -1186,38 +1186,35 @@ InitNumberFormatClass(JSContext *cx, Han
     /*
      * Install the getter for NumberFormat.prototype.format, which returns a
      * bound formatting function for the specified NumberFormat object (suitable
      * for passing to methods like Array.prototype.map).
      */
     RootedValue getter(cx);
     if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().NumberFormatFormatGet, &getter))
         return nullptr;
-    if (!JSObject::defineProperty(cx, proto, cx->names().format, UndefinedHandleValue,
-                                  JS_DATA_TO_FUNC_PTR(JSPropertyOp, &getter.toObject()),
-                                  nullptr, JSPROP_GETTER | JSPROP_SHARED))
+    if (!DefineProperty(cx, proto, cx->names().format, UndefinedHandleValue,
+                        JS_DATA_TO_FUNC_PTR(JSPropertyOp, &getter.toObject()),
+                        nullptr, JSPROP_GETTER | JSPROP_SHARED))
     {
         return nullptr;
     }
 
     RootedValue options(cx);
     if (!CreateDefaultOptions(cx, &options))
         return nullptr;
 
     // 11.2.1 and 11.3
     if (!IntlInitialize(cx, proto, cx->names().InitializeNumberFormat, UndefinedHandleValue, options))
         return nullptr;
 
     // 8.1
     RootedValue ctorValue(cx, ObjectValue(*ctor));
-    if (!JSObject::defineProperty(cx, Intl, cx->names().NumberFormat, ctorValue, nullptr, nullptr,
-                                  0))
-    {
+    if (!DefineProperty(cx, Intl, cx->names().NumberFormat, ctorValue, nullptr, nullptr, 0))
         return nullptr;
-    }
 
     return ctor;
 }
 
 bool
 GlobalObject::initNumberFormatProto(JSContext *cx, Handle<GlobalObject*> global)
 {
     RootedNativeObject proto(cx, global->createBlankPrototype(cx, &NumberFormatClass));
@@ -1278,17 +1275,17 @@ static UNumberFormat *
 NewUNumberFormat(JSContext *cx, HandleObject numberFormat)
 {
     RootedValue value(cx);
 
     RootedObject internals(cx);
     if (!GetInternals(cx, numberFormat, &internals))
        return nullptr;
 
-    if (!JSObject::getProperty(cx, internals, internals, cx->names().locale, &value))
+    if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
         return nullptr;
     JSAutoByteString locale(cx, value.toString());
     if (!locale)
         return nullptr;
 
     // UNumberFormat options with default values
     UNumberFormatStyle uStyle = UNUM_DECIMAL;
     const UChar *uCurrency = nullptr;
@@ -1301,35 +1298,35 @@ NewUNumberFormat(JSContext *cx, HandleOb
 
     // Sprinkle appropriate rooting flavor over things the GC might care about.
     RootedString currency(cx);
     AutoStableStringChars stableChars(cx);
 
     // We don't need to look at numberingSystem - it can only be set via
     // the Unicode locale extension and is therefore already set on locale.
 
-    if (!JSObject::getProperty(cx, internals, internals, cx->names().style, &value))
+    if (!GetProperty(cx, internals, internals, cx->names().style, &value))
         return nullptr;
     JSAutoByteString style(cx, value.toString());
     if (!style)
         return nullptr;
 
     if (equal(style, "currency")) {
-        if (!JSObject::getProperty(cx, internals, internals, cx->names().currency, &value))
+        if (!GetProperty(cx, internals, internals, cx->names().currency, &value))
             return nullptr;
         currency = value.toString();
         MOZ_ASSERT(currency->length() == 3, "IsWellFormedCurrencyCode permits only length-3 strings");
         if (!currency->ensureFlat(cx) || !stableChars.initTwoByte(cx, currency))
             return nullptr;
         // uCurrency remains owned by stableChars.
         uCurrency = Char16ToUChar(stableChars.twoByteRange().start().get());
         if (!uCurrency)
             return nullptr;
 
-        if (!JSObject::getProperty(cx, internals, internals, cx->names().currencyDisplay, &value))
+        if (!GetProperty(cx, internals, internals, cx->names().currencyDisplay, &value))
             return nullptr;
         JSAutoByteString currencyDisplay(cx, value.toString());
         if (!currencyDisplay)
             return nullptr;
         if (equal(currencyDisplay, "code")) {
             uStyle = UNUM_CURRENCY_ISO;
         } else if (equal(currencyDisplay, "symbol")) {
             uStyle = UNUM_CURRENCY;
@@ -1341,53 +1338,53 @@ NewUNumberFormat(JSContext *cx, HandleOb
         uStyle = UNUM_PERCENT;
     } else {
         MOZ_ASSERT(equal(style, "decimal"));
         uStyle = UNUM_DECIMAL;
     }
 
     RootedId id(cx, NameToId(cx->names().minimumSignificantDigits));
     bool hasP;
-    if (!JSObject::hasProperty(cx, internals, id, &hasP))
+    if (!HasProperty(cx, internals, id, &hasP))
         return nullptr;
     if (hasP) {
-        if (!JSObject::getProperty(cx, internals, internals, cx->names().minimumSignificantDigits,
-                                   &value))
+        if (!GetProperty(cx, internals, internals, cx->names().minimumSignificantDigits,
+                         &value))
         {
             return nullptr;
         }
         uMinimumSignificantDigits = int32_t(value.toNumber());
-        if (!JSObject::getProperty(cx, internals, internals, cx->names().maximumSignificantDigits,
-                                   &value))
+        if (!GetProperty(cx, internals, internals, cx->names().maximumSignificantDigits,
+                         &value))
         {
             return nullptr;
         }
         uMaximumSignificantDigits = int32_t(value.toNumber());
     } else {
-        if (!JSObject::getProperty(cx, internals, internals, cx->names().minimumIntegerDigits,
-                                   &value))
+        if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits,
+                         &value))
         {
             return nullptr;
         }
         uMinimumIntegerDigits = int32_t(value.toNumber());
-        if (!JSObject::getProperty(cx, internals, internals, cx->names().minimumFractionDigits,
-                                   &value))
+        if (!GetProperty(cx, internals, internals, cx->names().minimumFractionDigits,
+                         &value))
         {
             return nullptr;
         }
         uMinimumFractionDigits = int32_t(value.toNumber());
-        if (!JSObject::getProperty(cx, internals, internals, cx->names().maximumFractionDigits,
-                                   &value))
+        if (!GetProperty(cx, internals, internals, cx->names().maximumFractionDigits,
+                         &value))
         {
             return nullptr;
         }
         uMaximumFractionDigits = int32_t(value.toNumber());
     }
 
-    if (!JSObject::getProperty(cx, internals, internals, cx->names().useGrouping, &value))
+    if (!GetProperty(cx, internals, internals, cx->names().useGrouping, &value))
         return nullptr;
     uUseGrouping = value.toBoolean();
 
     UErrorCode status = U_ZERO_ERROR;
     UNumberFormat *nf = unum_open(uStyle, nullptr, 0, icuLocale(locale.ptr()), nullptr, &status);
     if (U_FAILURE(status)) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
         return nullptr;
@@ -1553,17 +1550,17 @@ DateTimeFormat(JSContext *cx, CallArgs a
         if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
             // 12.1.2.1 step 4
             obj = ToObject(cx, self);
             if (!obj)
                 return false;
 
             // 12.1.2.1 step 5
             bool extensible;
-            if (!JSObject::isExtensible(cx, obj, &extensible))
+            if (!IsExtensible(cx, obj, &extensible))
                 return false;
             if (!extensible)
                 return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE);
         } else {
             // 12.1.2.1 step 3.a
             construct = true;
         }
     }
@@ -1642,38 +1639,35 @@ InitDateTimeFormatClass(JSContext *cx, H
     /*
      * Install the getter for DateTimeFormat.prototype.format, which returns a
      * bound formatting function for the specified DateTimeFormat object
      * (suitable for passing to methods like Array.prototype.map).
      */
     RootedValue getter(cx);
     if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().DateTimeFormatFormatGet, &getter))
         return nullptr;
-    if (!JSObject::defineProperty(cx, proto, cx->names().format, UndefinedHandleValue,
-                                  JS_DATA_TO_FUNC_PTR(JSPropertyOp, &getter.toObject()),
-                                  nullptr, JSPROP_GETTER | JSPROP_SHARED))
+    if (!DefineProperty(cx, proto, cx->names().format, UndefinedHandleValue,
+                        JS_DATA_TO_FUNC_PTR(JSPropertyOp, &getter.toObject()),
+                        nullptr, JSPROP_GETTER | JSPROP_SHARED))
     {
         return nullptr;
     }
 
     RootedValue options(cx);
     if (!CreateDefaultOptions(cx, &options))
         return nullptr;
 
     // 12.2.1 and 12.3
     if (!IntlInitialize(cx, proto, cx->names().InitializeDateTimeFormat, UndefinedHandleValue, options))
         return nullptr;
 
     // 8.1
     RootedValue ctorValue(cx, ObjectValue(*ctor));
-    if (!JSObject::defineProperty(cx, Intl, cx->names().DateTimeFormat, ctorValue,
-                                  nullptr, nullptr, 0))
-    {
+    if (!DefineProperty(cx, Intl, cx->names().DateTimeFormat, ctorValue, nullptr, nullptr, 0))
         return nullptr;
-    }
 
     return ctor;
 }
 
 bool
 GlobalObject::initDateTimeFormatProto(JSContext *cx, Handle<GlobalObject*> global)
 {
     RootedNativeObject proto(cx, global->createBlankPrototype(cx, &DateTimeFormatClass));
@@ -1735,17 +1729,17 @@ js::intl_availableCalendars(JSContext *c
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
         return false;
     }
     ucal_close(cal);
     RootedString jscalendar(cx, JS_NewStringCopyZ(cx, bcp47CalendarName(calendar)));
     if (!jscalendar)
         return false;
     RootedValue element(cx, StringValue(jscalendar));
-    if (!JSObject::defineElement(cx, calendars, index++, element))
+    if (!DefineElement(cx, calendars, index++, element))
         return false;
 
     // Now get the calendars that "would make a difference", i.e., not the default.
     UEnumeration *values = ucal_getKeywordValuesForLocale("ca", locale.ptr(), false, &status);
     if (U_FAILURE(status)) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
         return false;
     }
@@ -1763,17 +1757,17 @@ js::intl_availableCalendars(JSContext *c
             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
             return false;
         }
 
         jscalendar = JS_NewStringCopyZ(cx, bcp47CalendarName(calendar));
         if (!jscalendar)
             return false;
         element = StringValue(jscalendar);
-        if (!JSObject::defineElement(cx, calendars, index++, element))
+        if (!DefineElement(cx, calendars, index++, element))
             return false;
     }
 
     args.rval().setObject(*calendars);
     return true;
 }
 
 bool
@@ -1840,53 +1834,52 @@ static UDateFormat *
 NewUDateFormat(JSContext *cx, HandleObject dateTimeFormat)
 {
     RootedValue value(cx);
 
     RootedObject internals(cx);
     if (!GetInternals(cx, dateTimeFormat, &internals))
        return nullptr;
 
-    if (!JSObject::getProperty(cx, internals, internals, cx->names().locale, &value)) {
+    if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
         return nullptr;
-    }
     JSAutoByteString locale(cx, value.toString());
     if (!locale)
         return nullptr;
 
     // UDateFormat options with default values.
     const UChar *uTimeZone = nullptr;
     uint32_t uTimeZoneLength = 0;
     const UChar *uPattern = nullptr;
     uint32_t uPatternLength = 0;
 
     // We don't need to look at calendar and numberingSystem - they can only be
     // set via the Unicode locale extension and are therefore already set on
     // locale.
 
     RootedId id(cx, NameToId(cx->names().timeZone));
     bool hasP;
-    if (!JSObject::hasProperty(cx, internals, id, &hasP))
+    if (!HasProperty(cx, internals, id, &hasP))
         return nullptr;
 
     AutoStableStringChars timeZoneChars(cx);
     if (hasP) {
-        if (!JSObject::getProperty(cx, internals, internals, cx->names().timeZone, &value))
+        if (!GetProperty(cx, internals, internals, cx->names().timeZone, &value))
             return nullptr;
         if (!value.isUndefined()) {
             JSFlatString *flat = value.toString()->ensureFlat(cx);
             if (!flat || !timeZoneChars.initTwoByte(cx, flat))
                 return nullptr;
             uTimeZone = Char16ToUChar(timeZoneChars.twoByteRange().start().get());
             if (!uTimeZone)
                 return nullptr;
             uTimeZoneLength = u_strlen(uTimeZone);
         }
     }
-    if (!JSObject::getProperty(cx, internals, internals, cx->names().pattern, &value))
+    if (!GetProperty(cx, internals, internals, cx->names().pattern, &value))
         return nullptr;
 
     AutoStableStringChars patternChars(cx);
     JSFlatString *flat = value.toString()->ensureFlat(cx);
     if (!flat || !patternChars.initTwoByte(cx, flat))
         return nullptr;
 
     uPattern = Char16ToUChar(patternChars.twoByteRange().start().get());
@@ -2031,17 +2024,17 @@ js_InitIntlClass(JSContext *cx, HandleOb
     // called with this being "the standard built-in Intl object". The global
     // object reserves slots to track standard built-in objects, but doesn't
     // normally keep references to non-constructors. This makes sure there is one.
     RootedObject Intl(cx, global->getOrCreateIntlObject(cx));
     if (!Intl)
         return nullptr;
 
     RootedValue IntlValue(cx, ObjectValue(*Intl));
-    if (!JSObject::defineProperty(cx, global, cx->names().Intl, IntlValue, nullptr, nullptr, 0))
+    if (!DefineProperty(cx, global, cx->names().Intl, IntlValue, nullptr, nullptr, 0))
         return nullptr;
 
     if (!JS_DefineFunctions(cx, Intl, intl_static_methods))
         return nullptr;
 
     if (!InitCollatorClass(cx, Intl, global))
         return nullptr;
     if (!InitNumberFormatClass(cx, Intl, global))
--- a/js/src/builtin/MapObject.cpp
+++ b/js/src/builtin/MapObject.cpp
@@ -1228,17 +1228,17 @@ MapObject::construct(JSContext *cx, unsi
 {
     Rooted<MapObject*> obj(cx, MapObject::create(cx));
     if (!obj)
         return false;
 
     CallArgs args = CallArgsFromVp(argc, vp);
     if (!args.get(0).isNullOrUndefined()) {
         RootedValue adderVal(cx);
-        if (!JSObject::getProperty(cx, obj, obj, cx->names().set, &adderVal))
+        if (!GetProperty(cx, obj, obj, cx->names().set, &adderVal))
             return false;
 
         if (!IsCallable(adderVal))
             return ReportIsNotFunction(cx, adderVal);
 
         bool isOriginalAdder = IsNativeFunction(adderVal, MapObject::set);
         RootedValue mapVal(cx, ObjectValue(*obj));
         FastInvokeGuard fig(cx, adderVal);
@@ -1263,21 +1263,21 @@ MapObject::construct(JSContext *cx, unsi
                 return false;
             }
 
             pairObj = &pairVal.toObject();
             if (!pairObj)
                 return false;
 
             RootedValue key(cx);
-            if (!JSObject::getElement(cx, pairObj, pairObj, 0, &key))
+            if (!GetElement(cx, pairObj, pairObj, 0, &key))
                 return false;
 
             RootedValue val(cx);
-            if (!JSObject::getElement(cx, pairObj, pairObj, 1, &val))
+            if (!GetElement(cx, pairObj, pairObj, 1, &val))
                 return false;
 
             if (isOriginalAdder) {
                 if (!hkey.setValue(cx, key))
                     return false;
 
                 RelocatableValue rval(val);
                 if (!map->put(hkey, rval)) {
@@ -1817,17 +1817,17 @@ SetObject::construct(JSContext *cx, unsi
 {
     Rooted<SetObject*> obj(cx, SetObject::create(cx));
     if (!obj)
         return false;
 
     CallArgs args = CallArgsFromVp(argc, vp);
     if (!args.get(0).isNullOrUndefined()) {
         RootedValue adderVal(cx);
-        if (!JSObject::getProperty(cx, obj, obj, cx->names().add, &adderVal))
+        if (!GetProperty(cx, obj, obj, cx->names().add, &adderVal))
             return false;
 
         if (!IsCallable(adderVal))
             return ReportIsNotFunction(cx, adderVal);
 
         bool isOriginalAdder = IsNativeFunction(adderVal, SetObject::add);
         RootedValue setVal(cx, ObjectValue(*obj));
         FastInvokeGuard fig(cx, adderVal);
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -190,17 +190,17 @@ js::ObjectToSource(JSContext *cx, Handle
     if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_SYMBOLS, &idv))
         return nullptr;
 
     bool comma = false;
     for (size_t i = 0; i < idv.length(); ++i) {
         RootedId id(cx, idv[i]);
         RootedObject obj2(cx);
         RootedShape shape(cx);
-        if (!JSObject::lookupGeneric(cx, obj, id, &obj2, &shape))
+        if (!LookupProperty(cx, obj, id, &obj2, &shape))
             return nullptr;
 
         /*  Decide early whether we prefer get/set or old getter/setter syntax. */
         int valcnt = 0;
         if (shape) {
             bool doGet = true;
             if (obj2->isNative() && !IsImplicitDenseOrTypedArrayElement(shape)) {
                 unsigned attrs = shape->attributes();
@@ -215,17 +215,17 @@ js::ObjectToSource(JSContext *cx, Handle
                     val[valcnt].set(shape->setterValue());
                     gsop[valcnt].set(cx->names().set);
                     valcnt++;
                 }
             }
             if (doGet) {
                 valcnt = 1;
                 gsop[0].set(nullptr);
-                if (!JSObject::getGeneric(cx, obj, obj, id, val[0]))
+                if (!GetProperty(cx, obj, obj, id, val[0]))
                     return nullptr;
             }
         }
 
         /* Convert id to a string. */
         RootedString idstr(cx);
         if (JSID_IS_SYMBOL(id)) {
             RootedValue v(cx, SymbolValue(JSID_TO_SYMBOL(id)));
@@ -329,17 +329,17 @@ JS_BasicObjectToString(JSContext *cx, Ha
         return cx->names().objectString;
     if (obj->is<ArrayObject>())
         return cx->names().objectArray;
     if (obj->is<JSFunction>())
         return cx->names().objectFunction;
     if (obj->is<NumberObject>())
         return cx->names().objectNumber;
 
-    const char *className = JSObject::className(cx, obj);
+    const char *className = GetObjectClassName(cx, obj);
 
     if (strcmp(className, "Window") == 0)
         return cx->names().objectWindow;
 
     StringBuffer sb(cx);
     if (!sb.append("[object ") || !sb.append(className, strlen(className)) ||
         !sb.append("]"))
     {
@@ -416,17 +416,17 @@ js::obj_getPrototypeOf(JSContext *cx, un
 
     /* Steps 1-2. */
     RootedObject obj(cx, ToObject(cx, args.get(0)));
     if (!obj)
         return false;
 
     /* Step 3. */
     RootedObject proto(cx);
-    if (!JSObject::getProto(cx, obj, &proto))
+    if (!GetPrototype(cx, obj, &proto))
         return false;
     args.rval().setObjectOrNull(proto);
     return true;
 }
 
 static bool
 obj_setPrototypeOf(JSContext *cx, unsigned argc, Value *vp)
 {
@@ -462,17 +462,17 @@ obj_setPrototypeOf(JSContext *cx, unsign
         return true;
     }
 
     /* Step 5-6. */
     RootedObject obj(cx, &args[0].toObject());
     RootedObject newProto(cx, args[1].toObjectOrNull());
 
     bool success;
-    if (!JSObject::setProto(cx, obj, newProto, &success))
+    if (!SetPrototype(cx, obj, newProto, &success))
         return false;
 
     /* Step 7. */
     if (!success) {
         UniquePtr<char[], JS::FreePolicy> bytes(DecompileValueGenerator(cx, JSDVG_SEARCH_STACK,
                                                                         args[0], NullPtr()));
         if (!bytes)
             return false;
@@ -530,17 +530,17 @@ obj_watch(JSContext *cx, unsigned argc, 
     RootedObject callable(cx, ValueToCallable(cx, args[1], args.length() - 2));
     if (!callable)
         return false;
 
     RootedId propid(cx);
     if (!ValueToId<CanGC>(cx, args[0], &propid))
         return false;
 
-    if (!JSObject::watch(cx, obj, propid, callable))
+    if (!WatchProperty(cx, obj, propid, callable))
         return false;
 
     args.rval().setUndefined();
     return true;
 }
 
 static bool
 obj_unwatch(JSContext *cx, unsigned argc, Value *vp)
@@ -557,17 +557,17 @@ obj_unwatch(JSContext *cx, unsigned argc
     RootedId id(cx);
     if (args.length() != 0) {
         if (!ValueToId<CanGC>(cx, args[0], &id))
             return false;
     } else {
         id = JSID_VOID;
     }
 
-    if (!JSObject::unwatch(cx, obj, id))
+    if (!UnwatchProperty(cx, obj, id))
         return false;
 
     args.rval().setUndefined();
     return true;
 }
 
 #endif /* JS_HAS_OBJ_WATCHPOINT */
 
@@ -699,17 +699,19 @@ js::obj_getOwnPropertyDescriptor(JSConte
         return false;
 
     // Steps 3-4.
     RootedId id(cx);
     if (!ValueToId<CanGC>(cx, args.get(1), &id))
         return false;
 
     // Steps 5-7.
-    return GetOwnPropertyDescriptor(cx, obj, id, args.rval());
+    Rooted<PropertyDescriptor> desc(cx);
+    return GetOwnPropertyDescriptor(cx, obj, id, &desc) &&
+           NewPropertyDescriptorObject(cx, desc, args.rval());
 }
 
 // ES6 draft rev27 (2014/08/24) 19.1.2.14 Object.keys(O)
 static bool
 obj_keys(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return GetOwnPropertyKeys(cx, args, JSITER_OWNONLY);
@@ -807,18 +809,22 @@ js::obj_defineProperty(JSContext *cx, un
     RootedObject obj(cx);
     if (!GetFirstArgumentAsObject(cx, args, "Object.defineProperty", &obj))
         return false;
 
     RootedId id(cx);
     if (!ValueToId<CanGC>(cx, args.get(1), &id))
         return false;
 
-    bool junk;
-    if (!DefineOwnProperty(cx, obj, id, args.get(2), &junk))
+    Rooted<PropDesc> desc(cx);
+    if (!desc.initialize(cx, args.get(2)))
+        return false;
+
+    bool ignored;
+    if (!StandardDefineProperty(cx, obj, id, desc, true, &ignored))
         return false;
 
     args.rval().setObject(*obj);
     return true;
 }
 
 /* ES5 15.2.3.7: Object.defineProperties(O, Properties) */
 static bool
@@ -854,17 +860,17 @@ obj_isExtensible(JSContext *cx, unsigned
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
     bool extensible = false;
 
     // Step 2.
     if (args.get(0).isObject()) {
         RootedObject obj(cx, &args.get(0).toObject());
-        if (!JSObject::isExtensible(cx, obj, &extensible))
+        if (!IsExtensible(cx, obj, &extensible))
             return false;
     }
     args.rval().setBoolean(extensible);
     return true;
 }
 
 // ES6 20141014 draft 19.1.2.15 Object.preventExtensions(O)
 static bool
@@ -876,17 +882,17 @@ obj_preventExtensions(JSContext *cx, uns
     // Step 1.
     if (!args.get(0).isObject())
         return true;
 
     // Steps 2-3.
     RootedObject obj(cx, &args.get(0).toObject());
 
     bool status;
-    if (!JSObject::preventExtensions(cx, obj, &status))
+    if (!PreventExtensions(cx, obj, &status))
         return false;
 
     // Step 4.
     if (!status) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_CHANGE_EXTENSIBILITY);
         return false;
     }
 
@@ -902,32 +908,32 @@ obj_freeze(JSContext *cx, unsigned argc,
     args.rval().set(args.get(0));
 
     // Step 1.
     if (!args.get(0).isObject())
         return true;
 
     // Steps 2-5.
     RootedObject obj(cx, &args.get(0).toObject());
-    return JSObject::freeze(cx, obj);
+    return SetIntegrityLevel(cx, obj, IntegrityLevel::Frozen);
 }
 
 // ES6 draft rev27 (2014/08/24) 19.1.2.12 Object.isFrozen(O)
 static bool
 obj_isFrozen(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
     bool frozen = true;
 
     // Step 2.
     if (args.get(0).isObject()) {
         RootedObject obj(cx, &args.get(0).toObject());
-        if (!JSObject::isFrozen(cx, obj, &frozen))
+        if (!TestIntegrityLevel(cx, obj, IntegrityLevel::Frozen, &frozen))
             return false;
     }
     args.rval().setBoolean(frozen);
     return true;
 }
 
 // ES6 draft rev27 (2014/08/24) 19.1.2.17 Object.seal(O)
 static bool
@@ -937,32 +943,32 @@ obj_seal(JSContext *cx, unsigned argc, V
     args.rval().set(args.get(0));
 
     // Step 1.
     if (!args.get(0).isObject())
         return true;
 
     // Steps 2-5.
     RootedObject obj(cx, &args.get(0).toObject());
-    return JSObject::seal(cx, obj);
+    return SetIntegrityLevel(cx, obj, IntegrityLevel::Sealed);
 }
 
 // ES6 draft rev27 (2014/08/24) 19.1.2.13 Object.isSealed(O)
 static bool
 obj_isSealed(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
     bool sealed = true;
 
     // Step 2.
     if (args.get(0).isObject()) {
         RootedObject obj(cx, &args.get(0).toObject());
-        if (!JSObject::isSealed(cx, obj, &sealed))
+        if (!TestIntegrityLevel(cx, obj, IntegrityLevel::Sealed, &sealed))
             return false;
     }
     args.rval().setBoolean(sealed);
     return true;
 }
 
 static bool
 ProtoGetter(JSContext *cx, unsigned argc, Value *vp)
@@ -973,17 +979,17 @@ ProtoGetter(JSContext *cx, unsigned argc
         ReportIncompatible(cx, args);
         return false;
     }
     if (thisv.isPrimitive() && !BoxNonStrictThis(cx, args))
         return false;
 
     RootedObject obj(cx, &args.thisv().toObject());
     RootedObject proto(cx);
-    if (!JSObject::getProto(cx, obj, &proto))
+    if (!GetPrototype(cx, obj, &proto))
         return false;
     args.rval().setObjectOrNull(proto);
     return true;
 }
 
 namespace js {
 size_t sSetProtoCalled = 0;
 }
@@ -1020,17 +1026,17 @@ ProtoSetter(JSContext *cx, unsigned argc
     if (args.length() == 0 || !args[0].isObjectOrNull()) {
         args.rval().setUndefined();
         return true;
     }
 
     Rooted<JSObject*> newProto(cx, args[0].toObjectOrNull());
 
     bool success;
-    if (!JSObject::setProto(cx, obj, newProto, &success))
+    if (!SetPrototype(cx, obj, newProto, &success))
         return false;
 
     if (!success) {
         js_ReportValueError(cx, JSMSG_SETPROTOTYPEOF_FAIL, JSDVG_IGNORE_STACK, thisv, js::NullPtr());
         return false;
     }
 
     args.rval().setUndefined();
@@ -1153,18 +1159,18 @@ FinishObjectClassInit(JSContext *cx, JS:
     } else {
         intrinsicsHolder = NewObjectWithGivenProto<PlainObject>(cx, proto, self, TenuredObject);
         if (!intrinsicsHolder)
             return false;
     }
     self->setIntrinsicsHolder(intrinsicsHolder);
     /* Define a property 'global' with the current global as its value. */
     RootedValue global(cx, ObjectValue(*self));
-    if (!JSObject::defineProperty(cx, intrinsicsHolder, cx->names().global, global,
-                                  nullptr, nullptr, JSPROP_PERMANENT | JSPROP_READONLY))
+    if (!DefineProperty(cx, intrinsicsHolder, cx->names().global, global,
+                        nullptr, nullptr, JSPROP_PERMANENT | JSPROP_READONLY))
     {
         return false;
     }
 
     /*
      * Define self-hosted functions after setting the intrinsics holder
      * (which is needed to define self-hosted functions)
      */
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -73,21 +73,21 @@ js::CreateRegExpMatchResult(JSContext *c
     arr->setSlot(0, Int32Value(matches[0].start));
 
     /* Set the |input| property. (TemplateObject positions it in slot 1) */
     arr->setSlot(1, StringValue(input));
 
 #ifdef DEBUG
     RootedValue test(cx);
     RootedId id(cx, NameToId(cx->names().index));
-    if (!baseops::GetProperty(cx, arr, id, &test))
+    if (!NativeGetProperty(cx, arr, id, &test))
         return false;
     MOZ_ASSERT(test == arr->getSlot(0));
     id = NameToId(cx->names().input);
-    if (!baseops::GetProperty(cx, arr, id, &test))
+    if (!NativeGetProperty(cx, arr, id, &test))
         return false;
     MOZ_ASSERT(test == arr->getSlot(1));
 #endif
 
     rval.setObject(*arr);
     return true;
 }
 
@@ -246,17 +246,17 @@ CompileRegExpObject(JSContext *cx, RegEx
             flags = g->getFlags();
         }
 
         /*
          * 'toSource' is a permanent read-only property, so this is equivalent
          * to executing RegExpObject::getSource on the unwrapped object.
          */
         RootedValue v(cx);
-        if (!JSObject::getProperty(cx, sourceObj, sourceObj, cx->names().source, &v))
+        if (!GetProperty(cx, sourceObj, sourceObj, cx->names().source, &v))
             return false;
 
         // For proxies like CPOWs, we can't assume the result of a property get
         // for 'source' is atomized.
         Rooted<JSAtom*> sourceAtom(cx, AtomizeString(cx, v.toString()));
         RegExpObject *reobj = builder.build(sourceAtom, flags);
         if (!reobj)
             return false;
@@ -389,45 +389,45 @@ regexp_flags(JSContext *cx, unsigned arg
     }
     RootedObject thisObj(cx, &args.thisv().toObject());
 
     /* Step 3. */
     StringBuffer sb(cx);
 
     /* Steps 4-6. */
     RootedValue global(cx);
-    if (!JSObject::getProperty(cx, thisObj, thisObj, cx->names().global, &global))
+    if (!GetProperty(cx, thisObj, thisObj, cx->names().global, &global))
         return false;
     if (ToBoolean(global) && !sb.append('g'))
         return false;
 
     /* Steps 7-9. */
     RootedValue ignoreCase(cx);
-    if (!JSObject::getProperty(cx, thisObj, thisObj, cx->names().ignoreCase, &ignoreCase))
+    if (!GetProperty(cx, thisObj, thisObj, cx->names().ignoreCase, &ignoreCase))
         return false;
     if (ToBoolean(ignoreCase) && !sb.append('i'))
         return false;
 
     /* Steps 10-12. */
     RootedValue multiline(cx);
-    if (!JSObject::getProperty(cx, thisObj, thisObj, cx->names().multiline, &multiline))
+    if (!GetProperty(cx, thisObj, thisObj, cx->names().multiline, &multiline))
         return false;
     if (ToBoolean(multiline) && !sb.append('m'))
         return false;
 
     /* Steps 13-15. */
     RootedValue unicode(cx);
-    if (!JSObject::getProperty(cx, thisObj, thisObj, cx->names().unicode, &unicode))
+    if (!GetProperty(cx, thisObj, thisObj, cx->names().unicode, &unicode))
         return false;
     if (ToBoolean(unicode) && !sb.append('u'))
         return false;
 
     /* Steps 16-18. */
     RootedValue sticky(cx);
-    if (!JSObject::getProperty(cx, thisObj, thisObj, cx->names().sticky, &sticky))
+    if (!GetProperty(cx, thisObj, thisObj, cx->names().sticky, &sticky))
         return false;
     if (ToBoolean(sticky) && !sb.append('y'))
         return false;
 
     /* Step 19. */
     args.rval().setString(sb.finishString());
     return true;
 }
--- a/js/src/builtin/SIMD.cpp
+++ b/js/src/builtin/SIMD.cpp
@@ -374,44 +374,44 @@ SIMDObject::initClass(JSContext *cx, Han
     float32x4Object = CreateSimdClass<Float32x4Defn>(cx, global,
                                                      cx->names().float32x4);
     if (!float32x4Object)
         return nullptr;
 
     // Define float32x4 functions and install as a property of the SIMD object.
     RootedValue float32x4Value(cx, ObjectValue(*float32x4Object));
     if (!JS_DefineFunctions(cx, float32x4Object, Float32x4Methods) ||
-        !JSObject::defineProperty(cx, SIMD, cx->names().float32x4,
-                                  float32x4Value, nullptr, nullptr,
-                                  JSPROP_READONLY | JSPROP_PERMANENT))
+        !DefineProperty(cx, SIMD, cx->names().float32x4,
+                        float32x4Value, nullptr, nullptr,
+                        JSPROP_READONLY | JSPROP_PERMANENT))
     {
         return nullptr;
     }
 
     // int32x4
     RootedObject int32x4Object(cx);
     int32x4Object = CreateSimdClass<Int32x4Defn>(cx, global,
                                                  cx->names().int32x4);
     if (!int32x4Object)
         return nullptr;
 
     // Define int32x4 functions and install as a property of the SIMD object.
     RootedValue int32x4Value(cx, ObjectValue(*int32x4Object));
     if (!JS_DefineFunctions(cx, int32x4Object, Int32x4Methods) ||
-        !JSObject::defineProperty(cx, SIMD, cx->names().int32x4,
-                                  int32x4Value, nullptr, nullptr,
-                                  JSPROP_READONLY | JSPROP_PERMANENT))
+        !DefineProperty(cx, SIMD, cx->names().int32x4,
+                        int32x4Value, nullptr, nullptr,
+                        JSPROP_READONLY | JSPROP_PERMANENT))
     {
         return nullptr;
     }
 
     RootedValue SIMDValue(cx, ObjectValue(*SIMD));
 
     // Everything is set up, install SIMD on the global object.
-    if (!JSObject::defineProperty(cx, global, cx->names().SIMD, SIMDValue, nullptr, nullptr, 0))
+    if (!DefineProperty(cx, global, cx->names().SIMD, SIMDValue, nullptr, nullptr, 0))
         return nullptr;
 
     global->setConstructor(JSProto_SIMD, SIMDValue);
     global->setFloat32x4TypeDescr(*float32x4Object);
     global->setInt32x4TypeDescr(*int32x4Object);
     return SIMD;
 }
 
--- a/js/src/builtin/SymbolObject.cpp
+++ b/js/src/builtin/SymbolObject.cpp
@@ -74,17 +74,17 @@ SymbolObject::initClass(JSContext *cx, H
 
     // Define the well-known symbol properties, such as Symbol.iterator.
     ImmutablePropertyNamePtr *names = &cx->names().iterator;
     RootedValue value(cx);
     unsigned attrs = JSPROP_READONLY | JSPROP_PERMANENT;
     WellKnownSymbols *wks = cx->runtime()->wellKnownSymbols;
     for (size_t i = 0; i < JS::WellKnownSymbolLimit; i++) {
         value.setSymbol(wks->get(i));
-        if (!DefineNativeProperty(cx, ctor, names[i], value, nullptr, nullptr, attrs))
+        if (!NativeDefineProperty(cx, ctor, names[i], value, nullptr, nullptr, attrs))
             return nullptr;
     }
 
     if (!LinkConstructorAndPrototype(cx, ctor, proto) ||
         !DefinePropertiesAndFunctions(cx, proto, properties, methods) ||
         !DefinePropertiesAndFunctions(cx, ctor, nullptr, staticMethods) ||
         !GlobalObject::initBuiltinConstructor(cx, global, JSProto_Symbol, ctor, proto))
     {
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1215,27 +1215,16 @@ DisplayName(JSContext *cx, unsigned argc
     }
 
     JSFunction *fun = &args[0].toObject().as<JSFunction>();
     JSString *str = fun->displayAtom();
     args.rval().setString(str ? str : cx->runtime()->emptyString);
     return true;
 }
 
-bool
-js::testingFunc_inParallelSection(JSContext *cx, unsigned argc, jsval *vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-
-    // If we were actually *in* a parallel section, then this function
-    // would be inlined to TRUE in ion-generated code.
-    args.rval().setBoolean(false);
-    return true;
-}
-
 static bool
 ShellObjectMetadataCallback(JSContext *cx, JSObject **pmetadata)
 {
     RootedObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
     if (!obj)
         return false;
 
     RootedObject stack(cx, NewDenseEmptyArray(cx));
@@ -2217,17 +2206,17 @@ SetImmutablePrototype(JSContext *cx, uns
     if (!args.get(0).isObject()) {
         JS_ReportError(cx, "setImmutablePrototype: object expected");
         return false;
     }
 
     RootedObject obj(cx, &args[0].toObject());
 
     bool succeeded;
-    if (!JSObject::setImmutablePrototype(cx, obj, &succeeded))
+    if (!js::SetImmutablePrototype(cx, obj, &succeeded))
         return false;
 
     args.rval().setBoolean(succeeded);
     return true;
 }
 
 static const JSFunctionSpecWithHelp TestingFunctions[] = {
     JS_FN_HELP("gc", ::GC, 0, 0,
@@ -2449,20 +2438,16 @@ gc::ZealModeHelpText),
     JS_FN_HELP("isLazyFunction", IsLazyFunction, 1, 0,
 "isLazyFunction(fun)",
 "  True if fun is a lazy JSFunction."),
 
     JS_FN_HELP("isRelazifiableFunction", IsRelazifiableFunction, 1, 0,
 "isRelazifiableFunction(fun)",
 "  Ture if fun is a JSFunction with a relazifiable JSScript."),
 
-    JS_FN_HELP("inParallelSection", testingFunc_inParallelSection, 0, 0,
-"inParallelSection()",
-"  True if this code is executing within a parallel section."),
-
     JS_FN_HELP("setObjectMetadataCallback", SetObjectMetadataCallback, 1, 0,
 "setObjectMetadataCallback(fn)",
 "  Specify function to supply metadata for all newly created objects."),
 
     JS_FN_HELP("setObjectMetadata", SetObjectMetadata, 2, 0,
 "setObjectMetadata(obj, metadataObj)",
 "  Change the metadata for an object."),
 
--- a/js/src/builtin/TestingFunctions.h
+++ b/js/src/builtin/TestingFunctions.h
@@ -10,19 +10,16 @@
 #include "NamespaceImports.h"
 
 namespace js {
 
 bool
 DefineTestingFunctions(JSContext *cx, HandleObject obj, bool fuzzingSafe);
 
 bool
-testingFunc_inParallelSection(JSContext *cx, unsigned argc, Value *vp);
-
-bool
 testingFunc_bailout(JSContext *cx, unsigned argc, Value *vp);
 
 bool
 testingFunc_assertFloat32(JSContext *cx, unsigned argc, Value *vp);
 
 } /* namespace js */
 
 #endif /* builtin_TestingFunctions_h */
--- a/js/src/builtin/TypedArray.js
+++ b/js/src/builtin/TypedArray.js
@@ -535,8 +535,160 @@ function TypedArrayIncludes(searchElemen
 
         // Step d.
         k++;
     }
 
     // Step 11.
     return false;
 }
+
+// ES6 draft rev30 (2014/12/24) 22.2.2.1 %TypedArray%.from(source[, mapfn[, thisArg]]).
+function TypedArrayStaticFrom(source, mapfn = undefined, thisArg = undefined) {
+    // Step 1.
+    var C = this;
+
+    // Step 2.
+    if (!IsConstructor(C))
+        ThrowError(JSMSG_NOT_CONSTRUCTOR, DecompileArg(1, C));
+
+    // Step 3.
+    var f = mapfn;
+
+    // Step 4.
+    if (f !== undefined && !IsCallable(f))
+        ThrowError(JSMSG_NOT_FUNCTION, DecompileArg(1, f));
+
+    // Steps 5-6.
+    return TypedArrayFrom(C, undefined, source, f, thisArg);
+}
+
+// ES6 draft rev30 (2014/12/24) 22.2.2.1.1 TypedArrayFrom().
+function TypedArrayFrom(constructor, target, items, mapfn, thisArg) {
+    // Step 1.
+    var C = constructor;
+
+    // Step 2.
+    assert(C === undefined || target === undefined,
+           "Neither of 'constructor' and 'target' is undefined");
+
+    // Step 3.
+    assert(IsConstructor(C) || C === undefined,
+           "'constructor' is neither an constructor nor undefined");
+
+    // Step 4.
+    assert(target === undefined || IsTypedArray(target),
+           "'target' is neither a typed array nor undefined");
+
+    // Step 5.
+    assert(IsCallable(mapfn) || mapfn === undefined,
+           "'target' is neither a function nor undefined");
+
+    // Steps 6-7.
+    var mapping = mapfn !== undefined;
+    var T = thisArg;
+
+    // Steps 8-9.
+    var usingIterator = GetMethod(items, std_iterator);
+
+    // Step 10.
+    if (usingIterator !== undefined) {
+        // Steps 10.a-b.
+        var iterator = GetIterator(items, usingIterator);
+
+        // Step 10.c.
+        var values = new List();
+
+        // Steps 10.d-e.
+        while (true) {
+            // Steps 10.e.i-ii.
+            var next = iterator.next();
+            if (!IsObject(next))
+                ThrowError(JSMSG_NEXT_RETURNED_PRIMITIVE);
+
+            // Steps 10.e.iii-vi.
+            if (next.done)
+                break;
+            values.push(next.value);
+        }
+
+        // Step 10.f.
+        var len = values.length;
+
+        // Steps 10.g-h.
+        // There is no need to implement the 22.2.2.1.2 - TypedArrayAllocOrInit() method,
+        // since `%TypedArray%(object)` currently doesn't call this self-hosted TypedArrayFrom().
+        var targetObj = new C(len);
+
+        // Steps 10.i-j.
+        for (var k = 0; k < len; k++) {
+            // Steps 10.j.i-ii.
+            var kValue = values[k];
+
+            // Steps 10.j.iii-iv.
+            var mappedValue = mapping ? callFunction(mapfn, T, kValue, k) : kValue;
+
+            // Steps 10.j.v-vi.
+            targetObj[k] = mappedValue;
+        }
+
+        // Step 10.k.
+        // asserting that `values` is empty here would require removing them one by one from
+        // the list's start in the loop above. That would introduce unacceptable overhead.
+        // Additionally, the loop's logic is simple enough not to require the assert.
+
+        // Step 10.l.
+        return targetObj;
+    }
+
+    // Step 11 is an assertion: items is not an Iterator. Testing this is
+    // literally the very last thing we did, so we don't assert here.
+
+    // Steps 12-13.
+    var arrayLike = ToObject(items);
+
+    // Steps 14-16.
+    var len = ToLength(arrayLike.length);
+
+    // Steps 17-18.
+    // See comment for steps 10.g-h.
+    var targetObj = new C(len);
+
+    // Steps 19-20.
+    for (var k = 0; k < len; k++) {
+        // Steps 20.a-c.
+        var kValue = arrayLike[k];
+
+        // Steps 20.d-e.
+        var mappedValue = mapping ? callFunction(mapfn, T, kValue, k) : kValue;
+
+        // Steps 20.f-g.
+        targetObj[k] = mappedValue;
+    }
+
+    // Step 21.
+    return targetObj;
+}
+
+// ES6 draft rev30 (2014/12/24) 22.2.2.2 %TypedArray%.of(...items).
+function TypedArrayStaticOf(/*...items*/) {
+    // Step 1.
+    var len = arguments.length;
+
+    // Step 2.
+    var items = arguments;
+
+    // Step 3.
+    var C = this;
+
+    // Steps 4-5.
+    if (!IsConstructor(C))
+        ThrowError(JSMSG_NOT_CONSTRUCTOR, typeof C);
+
+    var newObj = new C(len);
+
+    // Steps 6-7.
+    for (var k = 0; k < len; k++)
+        newObj[k] = items[k]
+
+    // Step 8.
+    return newObj;
+}
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -174,17 +174,17 @@ Reify(JSContext *cx,
 }
 
 // Extracts the `prototype` property from `obj`, throwing if it is
 // missing or not an object.
 static JSObject *
 GetPrototype(JSContext *cx, HandleObject obj)
 {
     RootedValue prototypeVal(cx);
-    if (!JSObject::getProperty(cx, obj, obj, cx->names().prototype,
+    if (!GetProperty(cx, obj, obj, cx->names().prototype,
                                &prototypeVal))
     {
         return nullptr;
     }
     if (!prototypeVal.isObject()) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
                              JSMSG_INVALID_PROTOTYPE);
         return nullptr;
@@ -524,40 +524,40 @@ const JSFunctionSpec ArrayMetaTypeDescr:
 
 bool
 js::CreateUserSizeAndAlignmentProperties(JSContext *cx, HandleTypeDescr descr)
 {
     // If data is transparent, also store the public slots.
     if (descr->transparent()) {
         // byteLength
         RootedValue typeByteLength(cx, Int32Value(descr->size()));
-        if (!JSObject::defineProperty(cx, descr, cx->names().byteLength, typeByteLength,
-                                      nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
+        if (!DefineProperty(cx, descr, cx->names().byteLength, typeByteLength,
+                            nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
         {
             return false;
         }
 
         // byteAlignment
         RootedValue typeByteAlignment(cx, Int32Value(descr->alignment()));
-        if (!JSObject::defineProperty(cx, descr, cx->names().byteAlignment, typeByteAlignment,
-                                      nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
+        if (!DefineProperty(cx, descr, cx->names().byteAlignment, typeByteAlignment,
+                            nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
         {
             return false;
         }
     } else {
         // byteLength
-        if (!JSObject::defineProperty(cx, descr, cx->names().byteLength, UndefinedHandleValue,
-                                      nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
+        if (!DefineProperty(cx, descr, cx->names().byteLength, UndefinedHandleValue,
+                            nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
         {
             return false;
         }
 
         // byteAlignment
-        if (!JSObject::defineProperty(cx, descr, cx->names().byteAlignment, UndefinedHandleValue,
-                                      nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
+        if (!DefineProperty(cx, descr, cx->names().byteAlignment, UndefinedHandleValue,
+                            nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
         {
             return false;
         }
     }
 
     return true;
 }
 
@@ -581,25 +581,25 @@ ArrayMetaTypeDescr::create(JSContext *cx
     obj->initReservedSlot(JS_DESCR_SLOT_STRING_REPR, StringValue(stringRepr));
     obj->initReservedSlot(JS_DESCR_SLOT_ALIGNMENT, Int32Value(elementType->alignment()));
     obj->initReservedSlot(JS_DESCR_SLOT_SIZE, Int32Value(size));
     obj->initReservedSlot(JS_DESCR_SLOT_OPAQUE, BooleanValue(elementType->opaque()));
     obj->initReservedSlot(JS_DESCR_SLOT_ARRAY_ELEM_TYPE, ObjectValue(*elementType));
     obj->initReservedSlot(JS_DESCR_SLOT_ARRAY_LENGTH, Int32Value(length));
 
     RootedValue elementTypeVal(cx, ObjectValue(*elementType));
-    if (!JSObject::defineProperty(cx, obj, cx->names().elementType, elementTypeVal,
-                                  nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
+    if (!DefineProperty(cx, obj, cx->names().elementType, elementTypeVal,
+                        nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
     {
         return nullptr;
     }
 
     RootedValue lengthValue(cx, NumberValue(length));
-    if (!JSObject::defineProperty(cx, obj, cx->names().length, lengthValue,
-                                  nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
+    if (!DefineProperty(cx, obj, cx->names().length, lengthValue,
+                        nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
     {
         return nullptr;
     }
 
     if (!CreateUserSizeAndAlignmentProperties(cx, obj))
         return nullptr;
 
     // All arrays with the same element type have the same prototype. This
@@ -789,17 +789,17 @@ StructMetaTypeDescr::create(JSContext *c
         if (!JSID_IS_ATOM(id) || JSID_TO_ATOM(id)->isIndex(&unused)) {
             RootedValue idValue(cx, IdToValue(id));
             ReportCannotConvertTo(cx, idValue, "StructType field name");
             return nullptr;
         }
 
         // Load the value for the current field from the `fields` object.
         // The value should be a type descriptor.
-        if (!JSObject::getGeneric(cx, fields, fields, id, &fieldTypeVal))
+        if (!GetProperty(cx, fields, fields, id, &fieldTypeVal))
             return nullptr;
         fieldType = ToObjectIf<TypeDescr>(fieldTypeVal);
         if (!fieldType) {
             ReportCannotConvertTo(cx, fieldTypeVal, "StructType field specifier");
             return nullptr;
         }
 
         // Collect field name and type object
@@ -809,20 +809,21 @@ StructMetaTypeDescr::create(JSContext *c
             return nullptr;
         }
         if (!fieldTypeObjs.append(ObjectValue(*fieldType))) {
             js_ReportOutOfMemory(cx);
             return nullptr;
         }
 
         // userFieldTypes[id] = typeObj
-        if (!JSObject::defineGeneric(cx, userFieldTypes, id,
-                                     fieldTypeObjs[i], nullptr, nullptr,
-                                     JSPROP_READONLY | JSPROP_PERMANENT))
+        if (!DefineProperty(cx, userFieldTypes, id, fieldTypeObjs[i], nullptr, nullptr,
+                            JSPROP_READONLY | JSPROP_PERMANENT))
+        {
             return nullptr;
+        }
 
         // Append "f:Type" to the string repr
         if (i > 0 && !stringBuffer.append(", ")) {
             js_ReportOutOfMemory(cx);
             return nullptr;
         }
         if (!stringBuffer.append(JSID_TO_ATOM(id))) {
             js_ReportOutOfMemory(cx);
@@ -848,20 +849,21 @@ StructMetaTypeDescr::create(JSContext *c
         MOZ_ASSERT(offset.value() >= 0);
         if (!fieldOffsets.append(Int32Value(offset.value()))) {
             js_ReportOutOfMemory(cx);
             return nullptr;
         }
 
         // userFieldOffsets[id] = offset
         RootedValue offsetValue(cx, Int32Value(offset.value()));
-        if (!JSObject::defineGeneric(cx, userFieldOffsets, id,
-                                     offsetValue, nullptr, nullptr,
-                                     JSPROP_READONLY | JSPROP_PERMANENT))
+        if (!DefineProperty(cx, userFieldOffsets, id, offsetValue, nullptr, nullptr,
+                            JSPROP_READONLY | JSPROP_PERMANENT))
+        {
             return nullptr;
+        }
 
         // Add space for this field to the total struct size.
         sizeSoFar = offset + fieldType->size();
         if (!sizeSoFar.isValid()) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
                                  JSMSG_TYPEDOBJECT_TOO_BIG);
             return nullptr;
         }
@@ -940,29 +942,29 @@ StructMetaTypeDescr::create(JSContext *c
                                               TenuredObject);
         if (!fieldOffsetsVec)
             return nullptr;
         descr->initReservedSlot(JS_DESCR_SLOT_STRUCT_FIELD_OFFSETS,
                                      ObjectValue(*fieldOffsetsVec));
     }
 
     // Create data properties fieldOffsets and fieldTypes
-    if (!JSObject::freeze(cx, userFieldOffsets))
+    if (!FreezeObject(cx, userFieldOffsets))
         return nullptr;
-    if (!JSObject::freeze(cx, userFieldTypes))
+    if (!FreezeObject(cx, userFieldTypes))
         return nullptr;
     RootedValue userFieldOffsetsValue(cx, ObjectValue(*userFieldOffsets));
-    if (!JSObject::defineProperty(cx, descr, cx->names().fieldOffsets, userFieldOffsetsValue,
-                                  nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
+    if (!DefineProperty(cx, descr, cx->names().fieldOffsets, userFieldOffsetsValue,
+                        nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
     {
         return nullptr;
     }
     RootedValue userFieldTypesValue(cx, ObjectValue(*userFieldTypes));
-    if (!JSObject::defineProperty(cx, descr, cx->names().fieldTypes, userFieldTypesValue,
-                                  nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
+    if (!DefineProperty(cx, descr, cx->names().fieldTypes, userFieldTypesValue,
+                        nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
     {
         return nullptr;
     }
 
     if (!CreateUserSizeAndAlignmentProperties(cx, descr))
         return nullptr;
 
     Rooted<TypedProto*> prototypeObj(cx);
@@ -1160,21 +1162,18 @@ DefineSimpleTypeDescr(JSContext *cx,
     // not being user accessible, but we still create one for consistency.
     Rooted<TypedProto*> proto(cx);
     proto = NewObjectWithProto<TypedProto>(cx, objProto, nullptr, TenuredObject);
     if (!proto)
         return false;
     descr->initReservedSlot(JS_DESCR_SLOT_TYPROTO, ObjectValue(*proto));
 
     RootedValue descrValue(cx, ObjectValue(*descr));
-    if (!JSObject::defineProperty(cx, module, className,
-                                  descrValue, nullptr, nullptr, 0))
-    {
+    if (!DefineProperty(cx, module, className, descrValue, nullptr, nullptr, 0))
         return false;
-    }
 
     if (!CreateTraceList(cx, descr))
         return false;
 
     return true;
 }
 
 ///////////////////////////////////////////////////////////////////////////
@@ -1209,18 +1208,18 @@ DefineMetaTypeDescr(JSContext *cx,
         return nullptr;
     RootedObject protoProto(cx);
     protoProto = NewObjectWithProto<PlainObject>(cx, objProto,
                                                  global, SingletonObject);
     if (!protoProto)
         return nullptr;
 
     RootedValue protoProtoValue(cx, ObjectValue(*protoProto));
-    if (!JSObject::defineProperty(cx, proto, cx->names().prototype, protoProtoValue,
-                                  nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
+    if (!DefineProperty(cx, proto, cx->names().prototype, protoProtoValue,
+                        nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
     {
         return nullptr;
     }
 
     // Create ctor itself
 
     const int constructorLength = 2;
     RootedFunction ctor(cx);
@@ -1283,45 +1282,42 @@ GlobalObject::initTypedObjectModule(JSCo
 
     RootedObject arrayType(cx);
     arrayType = DefineMetaTypeDescr<ArrayMetaTypeDescr>(
         cx, "ArrayType", global, module, TypedObjectModuleObject::ArrayTypePrototype);
     if (!arrayType)
         return false;
 
     RootedValue arrayTypeValue(cx, ObjectValue(*arrayType));
-    if (!JSObject::defineProperty(cx, module, cx->names().ArrayType, arrayTypeValue,
-                                  nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
+    if (!DefineProperty(cx, module, cx->names().ArrayType, arrayTypeValue,
+                        nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
     {
         return false;
     }
 
     // StructType.
 
     RootedObject structType(cx);
     structType = DefineMetaTypeDescr<StructMetaTypeDescr>(
         cx, "StructType", global, module, TypedObjectModuleObject::StructTypePrototype);
     if (!structType)
         return false;
 
     RootedValue structTypeValue(cx, ObjectValue(*structType));
-    if (!JSObject::defineProperty(cx, module, cx->names().StructType, structTypeValue,
-                                  nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
+    if (!DefineProperty(cx, module, cx->names().StructType, structTypeValue,
+                        nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
     {
         return false;
     }
 
     // Everything is setup, install module on the global object:
     RootedValue moduleValue(cx, ObjectValue(*module));
     global->setConstructor(JSProto_TypedObject, moduleValue);
-    if (!JSObject::defineProperty(cx, global, cx->names().TypedObject, moduleValue,
-                                  nullptr, nullptr, 0))
-    {
+    if (!DefineProperty(cx, global, cx->names().TypedObject, moduleValue, nullptr, nullptr, 0))
         return false;
-    }
 
     return module;
 }
 
 JSObject *
 js_InitTypedObjectModuleObject(JSContext *cx, HandleObject obj)
 {
     MOZ_ASSERT(obj->is<GlobalObject>());
@@ -1713,17 +1709,17 @@ TypedObject::obj_lookupGeneric(JSContext
 
     RootedObject proto(cx, obj->getProto());
     if (!proto) {
         objp.set(nullptr);
         propp.set(nullptr);
         return true;
     }
 
-    return JSObject::lookupGeneric(cx, proto, id, objp, propp);
+    return LookupProperty(cx, proto, id, objp, propp);
 }
 
 bool
 TypedObject::obj_lookupProperty(JSContext *cx,
                                 HandleObject obj,
                                 HandlePropertyName name,
                                 MutableHandleObject objp,
                                 MutableHandleShape propp)
@@ -1839,17 +1835,17 @@ TypedObject::obj_getGeneric(JSContext *c
     }
 
     RootedObject proto(cx, obj->getProto());
     if (!proto) {
         vp.setUndefined();
         return true;
     }
 
-    return JSObject::getGeneric(cx, proto, receiver, id, vp);
+    return GetProperty(cx, proto, receiver, id, vp);
 }
 
 bool
 TypedObject::obj_getProperty(JSContext *cx, HandleObject obj, HandleObject receiver,
                               HandlePropertyName name, MutableHandleValue vp)
 {
     RootedId id(cx, NameToId(name));
     return obj_getGeneric(cx, obj, receiver, id, vp);
@@ -1875,17 +1871,17 @@ TypedObject::obj_getElement(JSContext *c
     }
 
     RootedObject proto(cx, obj->getProto());
     if (!proto) {
         vp.setUndefined();
         return true;
     }
 
-    return JSObject::getElement(cx, proto, receiver, index, vp);
+    return GetElement(cx, proto, receiver, index, vp);
 }
 
 /*static*/ bool
 TypedObject::obj_getArrayElement(JSContext *cx,
                                 Handle<TypedObject*> typedObj,
                                 Handle<TypeDescr*> typeDescr,
                                 uint32_t index,
                                 MutableHandleValue vp)
@@ -2029,17 +2025,17 @@ TypedObject::obj_getGenericAttributes(JS
     }
 
     RootedObject proto(cx, obj->getProto());
     if (!proto) {
         *attrsp = 0;
         return true;
     }
 
-    return JSObject::getGenericAttributes(cx, proto, id, attrsp);
+    return GetPropertyAttributes(cx, proto, id, attrsp);
 }
 
 static bool
 IsOwnId(JSContext *cx, HandleObject obj, HandleId id)
 {
     uint32_t index;
     Rooted<TypedObject *> typedObj(cx, &obj->as<TypedObject>());
     switch (typedObj->typeDescr().kind()) {
@@ -2068,32 +2064,32 @@ TypedObject::obj_setGenericAttributes(JS
         return ReportPropertyError(cx, JSMSG_CANT_REDEFINE_PROP, id);
 
     RootedObject proto(cx, obj->getProto());
     if (!proto) {
         *attrsp = 0;
         return true;
     }
 
-    return JSObject::setGenericAttributes(cx, proto, id, attrsp);
+    return SetPropertyAttributes(cx, proto, id, attrsp);
 }
 
 bool
 TypedObject::obj_deleteGeneric(JSContext *cx, HandleObject obj, HandleId id, bool *succeeded)
 {
     if (IsOwnId(cx, obj, id))
         return ReportPropertyError(cx, JSMSG_CANT_DELETE, id);
 
     RootedObject proto(cx, obj->getProto());
     if (!proto) {
         *succeeded = false;
         return true;
     }
 
-    return JSObject::deleteGeneric(cx, proto, id, succeeded);
+    return DeleteProperty(cx, proto, id, succeeded);
 }
 
 bool
 TypedObject::obj_enumerate(JSContext *cx, HandleObject obj, AutoIdVector &properties)
 {
     MOZ_ASSERT(obj->is<TypedObject>());
     Rooted<TypedObject *> typedObj(cx, &obj->as<TypedObject>());
     Rooted<TypeDescr *> descr(cx, &typedObj->typeDescr());
--- a/js/src/builtin/Utilities.js
+++ b/js/src/builtin/Utilities.js
@@ -38,17 +38,20 @@ var std_WeakMap = WeakMap;
 // StopIteration is a bare constructor without properties or methods.
 var std_StopIteration = StopIteration;
 
 
 /********** List specification type **********/
 
 
 /* Spec: ECMAScript Language Specification, 5.1 edition, 8.8 */
-function List() {}
+function List() {
+    this.length = 0;
+}
+
 {
   let ListProto = std_Object_create(null);
   ListProto.indexOf = std_Array_indexOf;
   ListProto.join = std_Array_join;
   ListProto.push = std_Array_push;
   ListProto.slice = std_Array_slice;
   ListProto.sort = std_Array_sort;
   MakeConstructible(List, ListProto);
@@ -98,12 +101,55 @@ function ToLength(v) {
 
     if (v <= 0)
         return 0;
 
     // Math.pow(2, 53) - 1 = 0x1fffffffffffff
     return std_Math_min(v, 0x1fffffffffffff);
 }
 
-// Spec: ECMAScript Draft, 6th edition Oct 14, 2014, 7.2.4.
+/* Spec: ECMAScript Draft, 6th edition Oct 14, 2014, 7.2.4 */
 function SameValueZero(x, y) {
     return x === y || (x !== x && y !== y);
 }
+
+/* Spec: ECMAScript Draft, 6th edition Dec 24, 2014, 7.3.8 */
+function GetMethod(O, P) {
+    // Step 1.
+    assert(IsPropertyKey(P), "Invalid property key");
+
+    // Steps 2-3.
+    var func = ToObject(O)[P];
+
+    // Step 4.
+    if (func === undefined || func === null)
+        return undefined;
+
+    // Step 5.
+    if (!IsCallable(func))
+        ThrowError(JSMSG_NOT_FUNCTION, typeof func);
+
+    // Step 6.
+    return func;
+}
+
+/* Spec: ECMAScript Draft, 6th edition Dec 24, 2014, 7.2.7 */
+function IsPropertyKey(argument) {
+    var type = typeof argument;
+    return type === "string" || type === "symbol";
+}
+
+/* Spec: ECMAScript Draft, 6th edition Dec 24, 2014, 7.4.1 */
+function GetIterator(obj, method) {
+    // Steps 1-2.
+    if (arguments.length === 1)
+        method = GetMethod(obj, std_iterator);
+
+    // Steps 3-4.
+    var iterator = callFunction(method, obj);
+
+    // Step 5.
+    if (!IsObject(iterator))
+        ThrowError(JSMSG_NOT_ITERABLE, ToString(iterator));
+
+    // Step 6.
+    return iterator;
+}
--- a/js/src/builtin/WeakSetObject.cpp
+++ b/js/src/builtin/WeakSetObject.cpp
@@ -89,17 +89,17 @@ WeakSetObject::construct(JSContext *cx, 
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION, "WeakSet");
         return false;
     }
 
     if (!args.get(0).isNullOrUndefined()) {
         RootedObject map(cx, &obj->getReservedSlot(WEAKSET_MAP_SLOT).toObject());
 
         RootedValue adderVal(cx);
-        if (!JSObject::getProperty(cx, obj, obj, cx->names().add, &adderVal))
+        if (!GetProperty(cx, obj, obj, cx->names().add, &adderVal))
             return false;
 
         if (!IsCallable(adderVal))
             return ReportIsNotFunction(cx, adderVal);
 
         JSFunction *adder;
         bool isOriginalAdder = IsFunctionObject(adderVal, &adder) &&
                                IsSelfHostedFunctionWithName(adder, cx->names().WeakSet_add);
--- a/js/src/doc/Debugger/Debugger.Script.md
+++ b/js/src/doc/Debugger/Debugger.Script.md
@@ -198,16 +198,49 @@ methods of other kinds of objects.
     * the `for` statement head has two entry points at offsets 5 and 20 (for
       the initialization, which is performed only once, and the loop test,
       which is performed at the start of each iteration);
 
     * the third line has no code;
 
     * and the fourth line begins at offset 10.
 
+`getAllColumnOffsets()`:
+:   Return an array describing the relationship between bytecode instruction
+    offsets and source code positions in this script. Unlike getAllOffsets(),
+    which returns all offsets that are entry points for each line,
+    getAllColumnOffsets() returns all offsets that are entry points for each
+    (line, column) pair.
+
+    The elements of the array are objects, each of which describes a single
+    entry point, and contains the following properties:
+
+    * lineNumber: the line number for which offset is an entry point
+
+    * columnNumber: the column number for which offset is an entry point
+
+    * offset: the bytecode instruction offset of the entry point
+
+    For example, suppose we have a script for the following source code:
+
+    ```language-js
+    a=[]
+    for (i=1; i < 10; i++)
+        // It's hip to be square.
+        a[i] = i*i;
+    ```
+
+    Calling `getAllColumnOffsets()` on that code might yield an array like this:
+
+    ```language-js
+    [{ lineNumber: 0, columnNumber: 0, offset: 0 },
+     { lineNumber: 1, columnNumber: 5, offset: 5 },
+     { lineNumber: 1, columnNumber: 10, offset: 20 },
+     { lineNumber: 3, columnNumber: 4, offset: 10 }]
+
 <code>getLineOffsets(<i>line</i>)</code>
 :   Return an array of bytecode instruction offsets representing the entry
     points to source line <i>line</i>. If the script contains no executable
     code at that line, the array returned is empty.
 
 <code>getOffsetLine(<i>offset</i>)</code>
 :   Return the source code line responsible for the bytecode at
     <i>offset</i> in this script.
@@ -264,9 +297,11 @@ methods of other kinds of objects.
     same location(s) as <i>handler</i>, they remain in place.
 
 <code>clearAllBreakpoints([<i>offset</i>])</code>
 :   Remove all breakpoints set in this script. If <i>offset</i> is present,
     remove all breakpoints set at that offset in this script; if
     <i>offset</i> is not a valid bytecode offset in this script, throw an
     error.
 
-
+<code>isInCatchScope([<i>offset</i>])</code>
+:   This is `true` if this offset falls within the scope of a try block, and
+    `false` otherwise.
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -2273,22 +2273,22 @@ IteratorResultShape(ExclusiveContext *cx
     RootedPlainObject obj(cx);
     gc::AllocKind kind = GuessObjectGCKind(2);
     obj = NewBuiltinClassInstance<PlainObject>(cx, kind);
     if (!obj)
         return false;
 
     Rooted<jsid> value_id(cx, AtomToId(cx->names().value));
     Rooted<jsid> done_id(cx, AtomToId(cx->names().done));
-    if (!DefineNativeProperty(cx, obj, value_id, UndefinedHandleValue, nullptr, nullptr,
+    if (!NativeDefineProperty(cx, obj, value_id, UndefinedHandleValue, nullptr, nullptr,
                               JSPROP_ENUMERATE))
     {
         return false;
     }
-    if (!DefineNativeProperty(cx, obj, done_id, UndefinedHandleValue, nullptr, nullptr,
+    if (!NativeDefineProperty(cx, obj, done_id, UndefinedHandleValue, nullptr, nullptr,
                               JSPROP_ENUMERATE))
     {
         return false;
     }
 
     ObjectBox *objbox = bce->parser->newObjectBox(obj);
     if (!objbox)
         return false;
@@ -4236,17 +4236,17 @@ ParseNode::getConstantValue(ExclusiveCon
         for (; pn; idx++, pn = pn->pn_next) {
             if (!pn->getConstantValue(cx, allowObjects, &value))
                 return false;
             if (value.isMagic(JS_GENERIC_MAGIC)) {
                 vp.setMagic(JS_GENERIC_MAGIC);
                 return true;
             }
             id = INT_TO_JSID(idx);
-            if (!JSObject::defineGeneric(cx, obj, id, value, nullptr, nullptr, JSPROP_ENUMERATE))
+            if (!DefineProperty(cx, obj, id, value, nullptr, nullptr, JSPROP_ENUMERATE))
                 return false;
         }
         MOZ_ASSERT(idx == count);
 
         types::FixArrayType(cx, obj);
         vp.setObject(*obj);
         return true;
       }
@@ -4282,36 +4282,32 @@ ParseNode::getConstantValue(ExclusiveCon
             } else {
                 MOZ_ASSERT(pnid->isKind(PNK_NAME) || pnid->isKind(PNK_STRING));
                 MOZ_ASSERT(pnid->pn_atom != cx->names().proto);
                 idvalue = StringValue(pnid->pn_atom);
             }
 
             uint32_t index;
             if (IsDefinitelyIndex(idvalue, &index)) {
-                if (!JSObject::defineElement(cx, obj, index, value, nullptr, nullptr,
-                                             JSPROP_ENUMERATE))
-                {
+                if (!DefineElement(cx, obj, index, value, nullptr, nullptr, JSPROP_ENUMERATE))
                     return false;
-                }
 
                 continue;
             }
 
             JSAtom *name = ToAtom<CanGC>(cx, idvalue);
             if (!name)
                 return false;
 
             if (name->isIndex(&index)) {
-                if (!JSObject::defineElement(cx, obj, index, value,
-                                             nullptr, nullptr, JSPROP_ENUMERATE))
+                if (!DefineElement(cx, obj, index, value, nullptr, nullptr, JSPROP_ENUMERATE))
                     return false;
             } else {
-                if (!JSObject::defineProperty(cx, obj, name->asPropertyName(), value,
-                                              nullptr, nullptr, JSPROP_ENUMERATE))
+                if (!DefineProperty(cx, obj, name->asPropertyName(), value,
+                                    nullptr, nullptr, JSPROP_ENUMERATE))
                 {
                     return false;
                 }
             }
         }
 
         types::FixObjectType(cx, obj);
         vp.setObject(*obj);
@@ -6621,17 +6617,17 @@ EmitObject(ExclusiveContext *cx, Bytecod
             jsatomid index;
             if (!bce->makeAtomIndex(key->pn_atom, &index))
                 return false;
 
             if (obj) {
                 MOZ_ASSERT(!obj->inDictionaryMode());
                 Rooted<jsid> id(cx, AtomToId(key->pn_atom));
                 RootedValue undefinedValue(cx, UndefinedValue());
-                if (!DefineNativeProperty(cx, obj, id, undefinedValue, nullptr, nullptr,
+                if (!NativeDefineProperty(cx, obj, id, undefinedValue, nullptr, nullptr,
                                           JSPROP_ENUMERATE))
                 {
                     return false;
                 }
                 if (obj->inDictionaryMode())
                     obj = nullptr;
             }
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug1102187.js
@@ -0,0 +1,49 @@
+
+function minmax() {
+    // The test cases for minmax with two operands.
+    // Test integer type.
+    var pair_min = Math.min(1, 2);
+    assertEq(pair_min, 1);
+    var pair_max = Math.max(1, 2);
+    assertEq(pair_max, 2);
+
+    // Test double type.
+    pair_min = Math.min(1.2, 2.3);
+    assertEq(pair_min, 1.2);
+    pair_max = Math.max(1.2, 2.3);
+    assertEq(pair_max, 2.3);
+
+    // Test float type.
+    var expt_min = Math.fround(1.2);
+    var expt_max = Math.fround(2.3);
+    pair_min = Math.min(Math.fround(1.2), Math.fround(2.3));
+    assertEq(pair_min, expt_min);
+    pair_max = Math.max(Math.fround(1.2), Math.fround(2.3));
+    assertEq(pair_max, expt_max);
+
+    // The test cases for minmax with more than two operands.
+    // Test integer type.
+    pair_min = Math.min(1, 3, 2, 5, 4);
+    assertEq(pair_min, 1);
+    pair_max = Math.max(1, 3, 2, 5, 4);
+    assertEq(pair_max, 5);
+
+    // Test double type.
+    pair_min = Math.min(1.1, 3.3, 2.2, 5.5, 4.4);
+    assertEq(pair_min, 1.1);
+    pair_max = Math.max(1.1, 3.3, 2.2, 5.5, 4.4);
+    assertEq(pair_max, 5.5);
+
+    // Test float type.
+    expt_min = Math.fround(1.1);
+    expt_max = Math.fround(5.5);
+    pair_min = Math.min(Math.fround(1.1), Math.fround(3.3), Math.fround(2.2),
+                        Math.fround(5.5), Math.fround(4.4));
+    assertEq(pair_min, expt_min);
+    pair_max = Math.max(Math.fround(1.1), Math.fround(3.3), Math.fround(2.2),
+                        Math.fround(5.5), Math.fround(4.4));
+    assertEq(pair_max, expt_max);
+}
+
+minmax();
+minmax();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/recover-lambdas-bug1118911.js
@@ -0,0 +1,9 @@
+
+function test() {
+  function f()
+    k.apply(this, arguments);
+  if (undefined >> undefined !== 0) {}
+  for (var [ v , c ]  = 0 in this.tracemonkey) {  }
+}
+try { test(); } catch(exc1) {}
+try { test(); } catch(exc1) {}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/simd-bug1121299.js
@@ -0,0 +1,28 @@
+setJitCompilerOption("baseline.warmup.trigger", 10);
+setJitCompilerOption("ion.warmup.trigger", 30);
+
+function test_1(i) {
+  if (i >= 40)
+    return;
+  var a = SIMD.float32x4(1.1, 2.2, 3.3, 4.6);
+  SIMD.int32x4.fromFloat32x4(a);
+  test_1(i + 1);
+}
+test_1(0);
+
+
+var float32x4 = SIMD.float32x4;
+function test_2() {
+    var Array = float32x4.array(3);
+    var array = new Array([
+        float32x4(1, 2, 3, 4),
+        float32x4(5, 6, 7, 8),
+        float32x4(9, 10, 11, 12)
+    ]);
+    if (typeof reportCompare === "function")
+        reportCompare(true, true);
+}
+test_2();
+evaluate("test_2(); test_2();", {
+    compileAndGo: true
+});
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -3363,17 +3363,17 @@ EffectlesslyLookupProperty(JSContext *cx
         checkObj = GetDOMProxyProto(obj);
         if (!checkObj)
             return true;
     } else if (!obj->isNative()) {
         return true;
     }
 
     if (checkObj->hasIdempotentProtoChain()) {
-        if (!JSObject::lookupProperty(cx, checkObj, name, holder, shape))
+        if (!LookupProperty(cx, checkObj, name, holder, shape))
             return false;
     } else if (checkObj->isNative()) {
         shape.set(checkObj->as<NativeObject>().lookup(cx, NameToId(name)));
         if (shape)
             holder.set(checkObj);
     }
     return true;
 }
@@ -6922,17 +6922,17 @@ ComputeGetPropResult(JSContext *cx, Base
         }
     } else {
         // Handle when val is an object.
         RootedObject obj(cx, ToObjectFromStack(cx, val));
         if (!obj)
             return false;
 
         RootedId id(cx, NameToId(name));
-        if (!JSObject::getGeneric(cx, obj, obj, id, res))
+        if (!GetProperty(cx, obj, obj, id, res))
             return false;
 
 #if JS_HAS_NO_SUCH_METHOD
         // Handle objects with __noSuchMethod__.
         if (op == JSOP_CALLPROP && MOZ_UNLIKELY(res.isUndefined()) && val.isObject()) {
             if (!OnUnknownMethod(cx, obj, IdToValue(id), res))
                 return false;
         }
@@ -8291,17 +8291,17 @@ DoSetPropFallback(JSContext *cx, Baselin
         !TryAttachSetAccessorPropStub(cx, script, pc, stub, obj, oldShape, name, id,
                                       rhs, &attached, &isTemporarilyUnoptimizable))
     {
         return false;
     }
 
     if (op == JSOP_INITPROP) {
         MOZ_ASSERT(obj->is<PlainObject>());
-        if (!DefineNativeProperty(cx, obj.as<PlainObject>(), id, rhs,
+        if (!NativeDefineProperty(cx, obj.as<PlainObject>(), id, rhs,
                                   nullptr, nullptr, JSPROP_ENUMERATE))
         {
             return false;
         }
     } else if (op == JSOP_SETNAME ||
                op == JSOP_STRICTSETNAME ||
                op == JSOP_SETGNAME ||
                op == JSOP_STRICTSETGNAME)
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -2260,26 +2260,27 @@ jit::SetEnterJitData(JSContext *cx, Ente
         data.numActualArgs = args.length();
         data.maxArgc = Max(args.length(), numFormals) + 1;
         data.scopeChain = nullptr;
         data.calleeToken = CalleeToToken(&args.callee().as<JSFunction>(), data.constructing);
 
         if (data.numActualArgs >= numFormals) {
             data.maxArgv = args.base() + 1;
         } else {
+            MOZ_ASSERT(vals.empty());
+            if (!vals.reserve(Max(args.length() + 1, numFormals + 1)))
+                return false;
+
+            // Append |this| and any provided arguments.
+            for (size_t i = 1; i < args.length() + 2; ++i)
+                vals.infallibleAppend(args.base()[i]);
+
             // Pad missing arguments with |undefined|.
-            for (size_t i = 1; i < args.length() + 2; i++) {
-                if (!vals.append(args.base()[i]))
-                    return false;
-            }
-
-            while (vals.length() < numFormals + 1) {
-                if (!vals.append(UndefinedValue()))
-                    return false;
-            }
+            while (vals.length() < numFormals + 1)
+                vals.infallibleAppend(UndefinedValue());
 
             MOZ_ASSERT(vals.length() >= numFormals + 1);
             data.maxArgv = vals.begin();
         }
     } else {
         data.constructing = false;
         data.numActualArgs = 0;
         data.maxArgc = 1;
@@ -2503,20 +2504,26 @@ jit::StopAllOffThreadCompilations(JSComp
 {
     if (!comp->jitCompartment())
         return;
     CancelOffThreadIonCompile(comp, nullptr);
     FinishAllOffThreadCompilations(comp);
 }
 
 void
-jit::InvalidateAll(FreeOp *fop, Zone *zone)
+jit::StopAllOffThreadCompilations(Zone *zone)
 {
     for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next())
         StopAllOffThreadCompilations(comp);
+}
+
+void
+jit::InvalidateAll(FreeOp *fop, Zone *zone)
+{
+    StopAllOffThreadCompilations(zone);
 
     for (JitActivationIterator iter(fop->runtime()); !iter.done(); ++iter) {
         if (iter->compartment()->zone() == zone) {
             JitSpew(JitSpew_IonInvalidate, "Invalidating all frames for GC");
             InvalidateActivation(fop, iter, true);
         }
     }
 }
--- a/js/src/jit/Ion.h
+++ b/js/src/jit/Ion.h
@@ -139,16 +139,17 @@ class CodeGenerator;
 
 bool OptimizeMIR(MIRGenerator *mir);
 LIRGraph *GenerateLIR(MIRGenerator *mir);
 CodeGenerator *GenerateCode(MIRGenerator *mir, LIRGraph *lir);
 CodeGenerator *CompileBackEnd(MIRGenerator *mir);
 
 void AttachFinishedCompilations(JSContext *cx);
 void FinishOffThreadBuilder(JSContext *cx, IonBuilder *builder);
+void StopAllOffThreadCompilations(Zone *zone);
 void StopAllOffThreadCompilations(JSCompartment *comp);
 
 uint8_t *LazyLinkTopActivation(JSContext *cx);
 
 static inline bool
 IsIonEnabled(JSContext *cx)
 {
 #ifdef JS_CODEGEN_NONE
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -310,18 +310,17 @@ IonBuilder::getPolyCallTargets(types::Te
         // Don't optimize if the callee is not callable or constructable per
         // the manner it is being invoked, so that CallKnown does not have to
         // handle these cases (they will always throw).
         if (constructing ? !obj->isConstructor() : !obj->isCallable()) {
             targets.clear();
             return true;
         }
 
-        DebugOnly<bool> appendOk = targets.append(obj);
-        MOZ_ASSERT(appendOk);
+        targets.infallibleAppend(obj);
     }
 
     // For now, only inline "singleton" lambda calls
     if (*gotLambda && targets.length() > 1)
         targets.clear();
 
     return true;
 }
@@ -4650,17 +4649,17 @@ IonBuilder::makeInliningDecision(JSObjec
     // TI calls ObjectStateChange to trigger invalidation of the caller.
     types::TypeObjectKey *targetType = types::TypeObjectKey::get(target);
     targetType->watchStateChangeForInlinedCall(constraints());
 
     return InliningDecision_Inline;
 }
 
 bool
-IonBuilder::selectInliningTargets(ObjectVector &targets, CallInfo &callInfo, BoolVector &choiceSet,
+IonBuilder::selectInliningTargets(const ObjectVector &targets, CallInfo &callInfo, BoolVector &choiceSet,
                                   uint32_t *numInlineable)
 {
     *numInlineable = 0;
     uint32_t totalSize = 0;
 
     // For each target, ask whether it may be inlined.
     if (!choiceSet.reserve(targets.length()))
         return false;
@@ -4835,17 +4834,17 @@ IonBuilder::inlineSingleCall(CallInfo &c
         return inlineNativeCall(callInfo, target);
 
     if (!inlineScriptedCall(callInfo, target))
         return InliningStatus_Error;
     return InliningStatus_Inlined;
 }
 
 IonBuilder::InliningStatus
-IonBuilder::inlineCallsite(ObjectVector &targets, ObjectVector &originals,
+IonBuilder::inlineCallsite(const ObjectVector &targets, ObjectVector &originals,
                            bool lambda, CallInfo &callInfo)
 {
     if (targets.empty())
         return InliningStatus_NotInlined;
 
     // Is the function provided by an MGetPropertyCache?
     // If so, the cache may be movable to a fallback path, with a dispatch
     // instruction guarding on the incoming TypeObject.
@@ -5019,17 +5018,17 @@ IonBuilder::inlineTypeObjectFallback(Cal
 
     // inlineGenericFallback() set the return block as |current|.
     preCallBlock->end(MGoto::New(alloc(), current));
     *fallbackTarget = prepBlock;
     return true;
 }
 
 bool
-IonBuilder::inlineCalls(CallInfo &callInfo, ObjectVector &targets,
+IonBuilder::inlineCalls(CallInfo &callInfo, const ObjectVector &targets,
                         ObjectVector &originals, BoolVector &choiceSet,
                         MGetPropertyCache *maybeCache)
 {
     // Only handle polymorphic inlining.
     MOZ_ASSERT(IsIonInlinablePC(pc));
     MOZ_ASSERT(choiceSet.length() == targets.length());
     MOZ_ASSERT_IF(!maybeCache, targets.length() >= 2);
     MOZ_ASSERT_IF(maybeCache, targets.length() >= 1);
@@ -5590,22 +5589,20 @@ IonBuilder::jsop_funapplyarguments(uint3
 
     CallInfo callInfo(alloc(), false);
 
     // Vp
     MDefinition *vp = current->pop();
     vp->setImplicitlyUsedUnchecked();
 
     // Arguments
-    MDefinitionVector args(alloc());
     if (inliningDepth_) {
-        if (!args.appendAll(inlineCallInfo_->argv()))
+        if (!callInfo.setArgs(inlineCallInfo_->argv()))
             return false;
     }
-    callInfo.setArgs(&args);
 
     // This
     MDefinition *argThis = current->pop();
     callInfo.setThis(argThis);
 
     // Pop function parameter.
     MDefinition *argFunc = current->pop();
     callInfo.setFun(argFunc);
@@ -5658,31 +5655,32 @@ IonBuilder::jsop_call(uint32_t argc, boo
             return false;
     }
     MOZ_ASSERT_IF(gotLambda, originals.length() <= 1);
 
     // If any call targets need to be cloned, look for existing clones to use.
     // Keep track of the originals as we need to case on them for poly inline.
     bool hasClones = false;
     ObjectVector targets(alloc());
+    if (!targets.reserve(originals.length()))
+        return false;
     for (uint32_t i = 0; i < originals.length(); i++) {
         JSObject *obj = originals[i];
         if (obj->is<JSFunction>()) {
             JSFunction *fun = &obj->as<JSFunction>();
             if (fun->hasScript() && fun->nonLazyScript()->shouldCloneAtCallsite()) {
                 if (JSFunction *clone = ExistingCloneFunctionAtCallsite(compartment->callsiteClones(),
                                                                         fun, script(), pc))
                 {
                     obj = clone;
                     hasClones = true;
                 }
             }
         }
-        if (!targets.append(obj))
-            return false;
+        targets.infallibleAppend(obj);
     }
 
     CallInfo callInfo(alloc(), constructing);
     if (!callInfo.init(current, argc))
         return false;
 
     // Try inlining
     InliningStatus status = inlineCallsite(targets, originals, gotLambda, callInfo);
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -703,17 +703,17 @@ class IonBuilder
         InliningDecision_WarmUpCountTooLow
     };
 
     static InliningDecision DontInline(JSScript *targetScript, const char *reason);
 
     // Oracles.
     InliningDecision canInlineTarget(JSFunction *target, CallInfo &callInfo);
     InliningDecision makeInliningDecision(JSObject *target, CallInfo &callInfo);
-    bool selectInliningTargets(ObjectVector &targets, CallInfo &callInfo,
+    bool selectInliningTargets(const ObjectVector &targets, CallInfo &callInfo,
                                BoolVector &choiceSet, uint32_t *numInlineable);
 
     // Native inlining helpers.
     // The typeset for the return value of our function.  These are
     // the types it's been observed returning in the past.
     types::TemporaryTypeSet *getInlineReturnTypeSet();
     // The known MIR type of getInlineReturnTypeSet.
     MIRType getInlineReturnType();
@@ -811,19 +811,19 @@ class IonBuilder
     // Main inlining functions
     InliningStatus inlineNativeCall(CallInfo &callInfo, JSFunction *target);
     InliningStatus inlineNativeGetter(CallInfo &callInfo, JSFunction *target);
     InliningStatus inlineNonFunctionCall(CallInfo &callInfo, JSObject *target);
     bool inlineScriptedCall(CallInfo &callInfo, JSFunction *target);
     InliningStatus inlineSingleCall(CallInfo &callInfo, JSObject *target);
 
     // Call functions
-    InliningStatus inlineCallsite(ObjectVector &targets, ObjectVector &originals,
+    InliningStatus inlineCallsite(const ObjectVector &targets, ObjectVector &originals,
                                   bool lambda, CallInfo &callInfo);