Merge m-c to b-i
authorPhil Ringnalda <philringnalda@gmail.com>
Fri, 18 Sep 2015 21:54:17 -0700
changeset 295978 07711f754650f049206413f2c7a3f4adc6cc0ab6
parent 295977 7f2c1a5e677134c39500640fe4d366b9ba44e4d8 (current diff)
parent 295963 4313752f69956ae248bd4e7ff3913c8dd4252698 (diff)
child 295979 a5b1224e416a42797c9932ec03e13ae2ddb26c75
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone43.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 b-i
browser/devtools/debugger/content/stores/event-listeners.js
browser/devtools/debugger/content/stores/index.js
browser/devtools/shared/D3_LICENSE
browser/devtools/shared/content/react-dev.js
browser/devtools/shared/content/react.js
browser/devtools/shared/create-dispatcher.js
browser/devtools/shared/d3.js
browser/devtools/shared/fluxify/bindActionCreators.js
browser/devtools/shared/fluxify/dispatcher.js
browser/devtools/shared/fluxify/logMiddleware.js
browser/devtools/shared/fluxify/moz.build
browser/devtools/shared/fluxify/test/unit/head.js
browser/devtools/shared/fluxify/test/unit/stores-for-testing.js
browser/devtools/shared/fluxify/test/unit/test_dispatcher.js
browser/devtools/shared/fluxify/test/unit/test_middlewares.js
browser/devtools/shared/fluxify/test/unit/xpcshell.ini
browser/devtools/shared/fluxify/thunkMiddleware.js
browser/devtools/shared/fluxify/waitUntilService.js
browser/devtools/webaudioeditor/lib/DAGRE_D3_LICENSE
browser/devtools/webaudioeditor/lib/dagre-d3.js
image/test/reftest/ico/ico-bmp-corrupted/invalid_ico_height.ico
image/test/reftest/ico/ico-bmp-corrupted/invalid_ico_width.ico
mobile/android/themes/core/images/spinning_throbber.svg
security/manager/ssl/tests/unit/tlsserver/cert9.db
security/manager/ssl/tests/unit/tlsserver/expired-ee.der
security/manager/ssl/tests/unit/tlsserver/generate_certs.sh
security/manager/ssl/tests/unit/tlsserver/inadequatekeyusage-ee.der
security/manager/ssl/tests/unit/tlsserver/key4.db
security/manager/ssl/tests/unit/tlsserver/other-test-ca.der
security/manager/ssl/tests/unit/tlsserver/pkcs11.txt
security/manager/ssl/tests/unit/tlsserver/test-ca.der
security/manager/ssl/tests/unit/tlsserver/unknown-issuer.der
security/manager/ssl/tests/unit/tlsserver/v1Cert.der
testing/mozharness/mozharness/mozilla/testing/gaia_test.py
testing/taskcluster/tasks/builds/b2g_nexus4_eng.yml
testing/taskcluster/tasks/builds/b2g_nexus4_user.yml
testing/web-platform/mozilla/meta/service-workers/service-worker/unregister-then-register.https.html.ini
--- a/b2g/components/ActivityChannel.jsm
+++ b/b2g/components/ActivityChannel.jsm
@@ -7,16 +7,20 @@
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 
 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
                                    "@mozilla.org/childprocessmessagemanager;1",
                                    "nsIMessageSender");
 
+XPCOMUtils.defineLazyServiceGetter(this, "contentSecManager",
+                                   "@mozilla.org/contentsecuritymanager;1",
+                                   "nsIContentSecurityManager");
+
 this.EXPORTED_SYMBOLS = ["ActivityChannel"];
 
 this.ActivityChannel = function(aURI, aLoadInfo, aName, aDetails) {
   this._activityName = aName;
   this._activityDetails = aDetails;
   this.originalURI = aURI;
   this.URI = aURI;
   this.loadInfo = aLoadInfo;
@@ -46,13 +50,15 @@ this.ActivityChannel.prototype = {
 
   asyncOpen: function(aListener, aContext) {
     cpmm.sendAsyncMessage(this._activityName, this._activityDetails);
     // Let the listener cleanup.
     aListener.onStopRequest(this, aContext, Cr.NS_OK);
   },
 
   asyncOpen2: function(aListener) {
-    this.asyncOpen(aListener, null);
+    // throws an error if security checks fail
+    var outListener = contentSecManager.performSecurityCheck(this, aListener);
+    this.asyncOpen(outListener, null);
   },
 
   QueryInterface2: XPCOMUtils.generateQI([Ci.nsIChannel])
 }
--- a/b2g/config/nexus-5-l/config.json
+++ b/b2g/config/nexus-5-l/config.json
@@ -2,17 +2,17 @@
     "config_version": 2,
     "tooltool_manifest": "releng-nexus5.tt",
     "mock_target": "mozilla-centos6-x86_64",
     "mock_packages": ["ccache", "make", "bison", "flex", "gcc", "g++", "mpfr", "zlib-devel", "ncurses-devel", "zip", "autoconf213", "glibc-static", "perl-Digest-SHA", "wget", "alsa-lib", "atk", "cairo", "dbus-glib", "fontconfig", "freetype", "glib2", "gtk2", "libXRender", "libXt", "pango", "mozilla-python27-mercurial", "openssh-clients", "nss-devel", "glibc-devel.i686", "libstdc++.i686", "zlib-devel.i686", "ncurses-devel.i686", "libX11-devel.i686", "mesa-libGL-devel.i686", "mesa-libGL-devel", "libX11-devel", "git", "libxml2"],
     "mock_files": [
         ["/home/cltbld/.ssh", "/home/mock_mozilla/.ssh"],
         ["/builds/crash-stats-api.token", "/builds/crash-stats-api.token"]
     ],
-    "build_targets": [],
+    "build_targets": ["", "blobfree"],
     "upload_files": [
         "{objdir}/dist/b2g-*.crashreporter-symbols.zip",
         "{objdir}/dist/b2g-*.tar.gz",
         "{workdir}/sources.xml"
     ],
     "public_upload_files": [
         "{objdir}/dist/b2g-*.crashreporter-symbols.zip",
         "{objdir}/dist/b2g-*.tar.gz",
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1874,17 +1874,17 @@ pref("media.gmp-provider.enabled", true)
 
 pref("browser.apps.URL", "https://marketplace.firefox.com/discovery/");
 
 #ifdef NIGHTLY_BUILD
 pref("browser.polaris.enabled", false);
 pref("privacy.trackingprotection.ui.enabled", false);
 #endif
 pref("privacy.trackingprotection.introCount", 0);
-pref("privacy.trackingprotection.introURL", "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/tracking-protection-pbm");
+pref("privacy.trackingprotection.introURL", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/tracking-protection/start/");
 
 #ifndef RELEASE_BUILD
 // At the moment, autostart.2 is used, while autostart.1 is unused.
 // We leave it here set to false to reset users' defaults and allow
 // us to change everybody to true in the future, when desired.
 pref("browser.tabs.remote.autostart.1", false);
 pref("browser.tabs.remote.autostart.2", true);
 #endif
--- a/browser/components/loop/content/js/conversation.js
+++ b/browser/components/loop/content/js/conversation.js
@@ -129,16 +129,19 @@ loop.conversation = (function(mozL10n) {
     var sdkDriver = new loop.OTSdkDriver({
       isDesktop: true,
       useDataChannels: useDataChannels,
       dispatcher: dispatcher,
       sdk: OT,
       mozLoop: navigator.mozLoop
     });
 
+    // expose for functional tests
+    loop.conversation._sdkDriver = sdkDriver;
+
     // Create the stores.
     var conversationAppStore = new loop.store.ConversationAppStore({
       dispatcher: dispatcher,
       mozLoop: navigator.mozLoop
     });
     var conversationStore = new loop.store.ConversationStore(dispatcher, {
       client: client,
       isDesktop: true,
--- a/browser/components/loop/content/js/conversation.jsx
+++ b/browser/components/loop/content/js/conversation.jsx
@@ -129,16 +129,19 @@ loop.conversation = (function(mozL10n) {
     var sdkDriver = new loop.OTSdkDriver({
       isDesktop: true,
       useDataChannels: useDataChannels,
       dispatcher: dispatcher,
       sdk: OT,
       mozLoop: navigator.mozLoop
     });
 
+    // expose for functional tests
+    loop.conversation._sdkDriver = sdkDriver;
+
     // Create the stores.
     var conversationAppStore = new loop.store.ConversationAppStore({
       dispatcher: dispatcher,
       mozLoop: navigator.mozLoop
     });
     var conversationStore = new loop.store.ConversationStore(dispatcher, {
       client: client,
       isDesktop: true,
--- a/browser/components/loop/content/shared/css/conversation.css
+++ b/browser/components/loop/content/shared/css/conversation.css
@@ -452,25 +452,45 @@ html[dir="rtl"] .room-failure > .setting
 }
 
 .conversation-window-dropdown > li {
   padding: .2rem;
   font-size: 1rem;
   white-space: nowrap;
 }
 
+.screen-share-menu.dropdown-menu,
 .settings-menu.dropdown-menu {
   left: auto;
   bottom: 3.1rem;
+}
+
+.screen-share-menu.dropdown-menu {
+  /*offset dropdown menu to be above menu button*/
+  right: 40px;
+}
+
+.settings-menu.dropdown-menu {
+  /*offset dropdown menu to be above menu button*/
   right: 14px;
 }
 
+html[dir="rtl"] .screen-share-menu.dropdown-menu,
 html[dir="rtl"] .settings-menu.dropdown-menu {
+  right: auto;
+}
+
+html[dir="rtl"] .screen-share-menu.dropdown-menu {
+  /*offset dropdown menu to be above menu button*/
+  left: 40px;
+}
+
+html[dir="rtl"] .settings-menu.dropdown-menu {
+  /*offset dropdown menu to be above menu button*/
   left: 14px;
-  right: auto;
 }
 
 .settings-menu.dropdown-menu.menu-below {
   top: 11.5rem;
   bottom: auto;
 }
 
 /* Expired call url page */
--- a/browser/components/loop/content/shared/js/views.js
+++ b/browser/components/loop/content/shared/js/views.js
@@ -145,35 +145,35 @@ loop.shared.views = (function(_, mozL10n
         "btn": true,
         "btn-screen-share": true,
         "transparent-button": true,
         "menu-showing": this.state.showMenu,
         "active": isActive,
         "disabled": this.props.state === SCREEN_SHARE_STATES.PENDING
       });
       var dropdownMenuClasses = cx({
-        "native-dropdown-menu": true,
-        "conversation-window-dropdown": true,
-        "hide": !this.state.showMenu,
-        "visually-hidden": true
+        "screen-share-menu": true,
+        "dropdown-menu": true,
+        "hide": !this.state.showMenu
       });
       var windowSharingClasses = cx({
+        "dropdown-menu-item": true,
         "disabled": this.state.windowSharingDisabled
       });
 
       return (
         React.createElement("div", null, 
           React.createElement("button", {className: screenShareClasses, 
                   onClick: this.handleClick, 
                   ref: "menu-button", 
                   title: this._getTitle()}, 
             isActive ? null : React.createElement("span", {className: "chevron"})
           ), 
           React.createElement("ul", {className: dropdownMenuClasses, ref: "menu"}, 
-            React.createElement("li", {onClick: this._handleShareTabs}, 
+            React.createElement("li", {className: "dropdown-menu-item", onClick: this._handleShareTabs}, 
               mozL10n.get("share_tabs_button_title2")
             ), 
             React.createElement("li", {className: windowSharingClasses, onClick: this._handleShareWindows}, 
               mozL10n.get("share_windows_button_title")
             )
           )
         )
       );
--- a/browser/components/loop/content/shared/js/views.jsx
+++ b/browser/components/loop/content/shared/js/views.jsx
@@ -145,35 +145,35 @@ loop.shared.views = (function(_, mozL10n
         "btn": true,
         "btn-screen-share": true,
         "transparent-button": true,
         "menu-showing": this.state.showMenu,
         "active": isActive,
         "disabled": this.props.state === SCREEN_SHARE_STATES.PENDING
       });
       var dropdownMenuClasses = cx({
-        "native-dropdown-menu": true,
-        "conversation-window-dropdown": true,
-        "hide": !this.state.showMenu,
-        "visually-hidden": true
+        "screen-share-menu": true,
+        "dropdown-menu": true,
+        "hide": !this.state.showMenu
       });
       var windowSharingClasses = cx({
+        "dropdown-menu-item": true,
         "disabled": this.state.windowSharingDisabled
       });
 
       return (
         <div>
           <button className={screenShareClasses}
                   onClick={this.handleClick}
                   ref="menu-button"
                   title={this._getTitle()}>
             {isActive ? null : <span className="chevron"/>}
           </button>
           <ul className={dropdownMenuClasses} ref="menu">
-            <li onClick={this._handleShareTabs}>
+            <li className="dropdown-menu-item" onClick={this._handleShareTabs}>
               {mozL10n.get("share_tabs_button_title2")}
             </li>
             <li className={windowSharingClasses} onClick={this._handleShareWindows}>
               {mozL10n.get("share_windows_button_title")}
             </li>
           </ul>
         </div>
       );
--- a/browser/components/loop/test/shared/views_test.js
+++ b/browser/components/loop/test/shared/views_test.js
@@ -164,79 +164,79 @@ describe("loop.shared.views", function()
         var comp = TestUtils.renderIntoDocument(
           React.createElement(sharedViews.ScreenShareControlButton, {
             dispatcher: dispatcher,
             visible: true,
             state: SCREEN_SHARE_STATES.INACTIVE
           }));
 
         TestUtils.Simulate.click(comp.getDOMNode().querySelector(
-          ".conversation-window-dropdown > li"));
+          ".screen-share-menu > li"));
 
         sinon.assert.calledOnce(dispatcher.dispatch);
         sinon.assert.calledWithExactly(dispatcher.dispatch,
           new sharedActions.StartScreenShare({ type: "browser" }));
       });
 
     it("should dispatch a 'window' StartScreenShare action on option click",
       function() {
         var comp = TestUtils.renderIntoDocument(
           React.createElement(sharedViews.ScreenShareControlButton, {
             dispatcher: dispatcher,
             visible: true,
             state: SCREEN_SHARE_STATES.INACTIVE
           }));
 
         TestUtils.Simulate.click(comp.getDOMNode().querySelector(
-          ".conversation-window-dropdown > li:last-child"));
+          ".screen-share-menu > li:last-child"));
 
         sinon.assert.calledOnce(dispatcher.dispatch);
         sinon.assert.calledWithExactly(dispatcher.dispatch,
           new sharedActions.StartScreenShare({ type: "window" }));
       });
 
     it("should have the 'window' option enabled", function() {
       var comp = TestUtils.renderIntoDocument(
         React.createElement(sharedViews.ScreenShareControlButton, {
           dispatcher: dispatcher,
           visible: true,
           state: SCREEN_SHARE_STATES.INACTIVE
         }));
 
-      var node = comp.getDOMNode().querySelector(".conversation-window-dropdown > li:last-child");
+      var node = comp.getDOMNode().querySelector(".screen-share-menu > li:last-child");
       expect(node.classList.contains("disabled")).eql(false);
     });
 
     it("should disable the 'window' option on Windows XP", function() {
       OS = "win";
       OSVersion = { major: 5, minor: 1 };
 
       var comp = TestUtils.renderIntoDocument(
         React.createElement(sharedViews.ScreenShareControlButton, {
           dispatcher: dispatcher,
           visible: true,
           state: SCREEN_SHARE_STATES.INACTIVE
         }));
 
-      var node = comp.getDOMNode().querySelector(".conversation-window-dropdown > li:last-child");
+      var node = comp.getDOMNode().querySelector(".screen-share-menu > li:last-child");
       expect(node.classList.contains("disabled")).eql(true);
     });
 
     it("should disable the 'window' option on OSX 10.6", function() {
       OS = "mac";
       OSVersion = { major: 10, minor: 6 };
 
       var comp = TestUtils.renderIntoDocument(
         React.createElement(sharedViews.ScreenShareControlButton, {
           dispatcher: dispatcher,
           visible: true,
           state: SCREEN_SHARE_STATES.INACTIVE
         }));
 
-      var node = comp.getDOMNode().querySelector(".conversation-window-dropdown > li:last-child");
+      var node = comp.getDOMNode().querySelector(".screen-share-menu > li:last-child");
       expect(node.classList.contains("disabled")).eql(true);
     });
 
     it("should dispatch a EndScreenShare action on click when the state is active",
       function() {
         var comp = TestUtils.renderIntoDocument(
           React.createElement(sharedViews.ScreenShareControlButton, {
             dispatcher: dispatcher,
--- a/browser/components/search/content/search.xml
+++ b/browser/components/search/content/search.xml
@@ -330,28 +330,34 @@
 
           this.openSuggestionsPanel();
         ]]></body>
       </method>
 
       <method name="handleSearchCommand">
         <parameter name="aEvent"/>
         <parameter name="aEngine"/>
+        <parameter name="aForceNewTab"/>
         <body><![CDATA[
           var textBox = this._textbox;
           var textValue = textBox.value;
 
           var where = "current";
 
           // Open ctrl/cmd clicks on one-off buttons in a new background tab.
           if (aEvent && aEvent.originalTarget.getAttribute("anonid") == "search-go-button") {
             if (aEvent.button == 2)
               return;
             where = whereToOpenLink(aEvent, false, true);
           }
+          else if (aForceNewTab) {
+            where = "tab";
+            if (Services.prefs.getBoolPref("browser.tabs.loadInBackground"))
+              where += "-background";
+          }
           else {
             var newTabPref = Services.prefs.getBoolPref("browser.search.openintab");
             if (((aEvent instanceof KeyboardEvent) && aEvent.altKey) ^ newTabPref)
               where = "tab";
             if ((aEvent instanceof MouseEvent) &&
                 (aEvent.button == 1 || aEvent.getModifierState("Accel"))) {
               where = "tab-background";
             }
@@ -375,16 +381,18 @@
                 source = "oneoff";
               } else if (target.classList.contains("search-panel-header") ||
                          target.parentNode.classList.contains("search-panel-header")) {
                 source = "header";
               }
             } else if (aEvent instanceof XULCommandEvent) {
               if (target.getAttribute("anonid") == "paste-and-search") {
                 source = "paste";
+              } else if (target.getAttribute("anonid") == "search-one-offs-context-open-in-new-tab") {
+                source = "oneoff-context";
               }
             }
 
             if (!aEngine) {
               aEngine = this.currentEngine;
             }
             BrowserSearch.recordOneoffSearchInTelemetry(aEngine, source, type, where);
           }
@@ -1002,17 +1010,17 @@
 
     </handlers>
   </binding>
 
   <binding id="browser-search-autocomplete-result-popup" extends="chrome://browser/content/urlbarBindings.xml#browser-autocomplete-result-popup">
     <resources>
       <stylesheet src="chrome://browser/skin/searchbar.css"/>
     </resources>
-    <content ignorekeys="true" level="top" consumeoutsideclicks="never">
+    <content ignorekeys="true" level="top" consumeoutsideclicks="never" context="_child">
       <xul:hbox anonid="searchbar-engine" xbl:inherits="showonlysettings"
                 class="search-panel-header search-panel-current-engine">
         <xul:image class="searchbar-engine-image" xbl:inherits="src"/>
         <xul:label anonid="searchbar-engine-name" flex="1" crop="end"
                    role="presentation"/>
       </xul:hbox>
       <xul:tree anonid="tree" flex="1"
                 class="autocomplete-tree plain search-panel-tree"
@@ -1044,23 +1052,34 @@
       <xul:description anonid="search-panel-one-offs"
                        role="group"
                        class="search-panel-one-offs"/>
       <xul:vbox anonid="add-engines"/>
       <xul:button anonid="search-settings"
                   oncommand="showSettings();"
                   class="search-setting-button search-panel-header"
                   label="&changeSearchSettings.button;"/>
+      <xul:menupopup anonid="search-one-offs-context-menu">
+        <xul:menuitem anonid="search-one-offs-context-open-in-new-tab"
+                      label="&searchInNewTab.label;"
+                      accesskey="&searchInNewTab.accesskey;"/>
+        <xul:menuitem anonid="search-one-offs-context-set-default"
+                      label="&searchSetAsDefault.label;"
+                      accesskey="&searchSetAsDefault.accesskey;"/>
+      </xul:menupopup>
     </content>
     <implementation>
       <!-- Popup rollup is triggered by native events before the mousedown event
            reaches the DOM. The will be set to true by the popuphiding event and
            false after the mousedown event has been triggered to detect what
            caused rollup. -->
       <field name="_isHiding">false</field>
+      <!-- When a context menu is opened on a one-off button, this is set to the
+           engine of that button for use with the context menu actions. -->
+      <field name="_contextEngine">null</field>
       <field name="_bundle">null</field>
       <property name="bundle" readonly="true">
         <getter>
           <![CDATA[
             if (!this._bundle) {
               const kBundleURI = "chrome://browser/locale/search.properties";
               this._bundle = Services.strings.createBundle(kBundleURI);
             }
@@ -1096,22 +1115,51 @@
         <body><![CDATA[
           BrowserUITelemetry.countSearchSettingsEvent("searchbar");
           openPreferences("paneSearch");
           // If the preference tab was already selected, the panel doesn't
           // close itself automatically.
           BrowserSearch.searchBar._textbox.closePopup();
         ]]></body>
       </method>
+
+      <constructor><![CDATA[
+        // Prevent popup events from the context menu from reaching the autocomplete
+        // binding (or other listeners).
+        let menu = document.getAnonymousElementByAttribute(this, "anonid", "search-one-offs-context-menu");
+        let listener = aEvent => aEvent.stopPropagation();
+        menu.addEventListener("popupshowing", listener);
+        menu.addEventListener("popuphiding", listener);
+        menu.addEventListener("popupshown", aEvent => {
+          this._ignoreMouseEvents = true;
+          aEvent.stopPropagation();
+        });
+        menu.addEventListener("popuphidden", aEvent => {
+          this._ignoreMouseEvents = false;
+          aEvent.stopPropagation();
+        });
+      ]]></constructor>
     </implementation>
     <handlers>
       <handler event="popuphidden"><![CDATA[
         Services.tm.mainThread.dispatch(function() {
           document.getElementById("searchbar").textbox.selectedButton = null;
         }, Ci.nsIThread.DISPATCH_NORMAL);
+        this._contextEngine = null;
+      ]]></handler>
+
+      <handler event="contextmenu"><![CDATA[
+        let target = event.originalTarget;
+        // Prevent the context menu from appearing except on the one off buttons.
+        if (!target.classList.contains("searchbar-engine-one-off-item") ||
+            target.classList.contains("dummy")) {
+          event.preventDefault();
+          return;
+        }
+        this._contextEngine = target.engine;
       ]]></handler>
 
       <handler event="popupshowing"><![CDATA[
         // First handle deciding if we are showing the reduced version of the
         // popup containing only the preferences button. We do this if the
         // glass icon has been clicked if the text field is empty.
         let searchbar = document.getElementById("searchbar");
         let tree = document.getAnonymousElementByAttribute(this, "anonid",
@@ -1316,46 +1364,62 @@
         event.preventDefault();
       ]]></handler>
 
       <handler event="mouseover"><![CDATA[
         let target = event.originalTarget;
         if (target.localName != "button")
           return;
 
+        // Ignore mouse events when the context menu is open.
+         if (this._ignoreMouseEvents)
+           return;
+
         if ((target.classList.contains("searchbar-engine-one-off-item") &&
              !target.classList.contains("dummy")) ||
             target.classList.contains("addengine-item") ||
             target.classList.contains("search-setting-button")) {
           let textbox = document.getElementById("searchbar").textbox;
           textbox.selectedButton = target;
           textbox.selectionFromMouseOver = true;
         }
       ]]></handler>
 
       <handler event="mouseout"><![CDATA[
         let target = event.originalTarget;
         if (target.localName != "button")
           return;
 
+        // Don't deselect the current button if the context menu is open.
+        if (this._ignoreMouseEvents)
+          return;
+
         let textbox = document.getElementById("searchbar").textbox;
         if (textbox.selectedButton == target)
           textbox.selectedButton = null;
       ]]></handler>
 
       <handler event="click"><![CDATA[
         if (event.button == 2)
           return; // ignore right clicks.
 
         let button = event.originalTarget;
         let engine = button.engine || button.parentNode.engine;
 
         if (!engine)
           return;
 
+        // For some reason, if the context menu had been opened prior to the
+        // click, the suggestions popup won't be closed after loading the search
+        // in the current tab - so we hide it manually. Some focusing magic
+        // that happens when a search is loaded ensures that the popup is opened
+        // again if it needs to be, so we don't need to worry about which cases
+        // require manual hiding.
+        this.hidePopup();
+
         let searchbar = document.getElementById("searchbar");
         searchbar.handleSearchCommand(event, engine);
       ]]></handler>
 
       <handler event="command"><![CDATA[
         let target = event.originalTarget;
         if (target.classList.contains("addengine-item")) {
           // On success, hide and reshow the panel to show the new engine.
@@ -1368,16 +1432,39 @@
               Components.utils.reportError("Error adding search engine: " + errorCode);
             }
           }
           Services.search.addEngine(target.getAttribute("uri"),
                                     Ci.nsISearchEngine.DATA_XML,
                                     target.getAttribute("image"), false,
                                     installCallback);
         }
+        let anonid = target.getAttribute("anonid");
+        if (anonid == "search-one-offs-context-open-in-new-tab") {
+          let searchbar = document.getElementById("searchbar");
+          searchbar.handleSearchCommand(event, this._contextEngine, true);
+        }
+        if (anonid == "search-one-offs-context-set-default") {
+          let currentEngine = Services.search.currentEngine;
+
+          // Make the target button of the context menu reflect the current
+          // search engine first. Doing this as opposed to rebuilding all the
+          // one-off buttons avoids flicker.
+          let button = document.getElementById("searchbar-engine-one-off-item-" +
+            this._contextEngine.name.replace(/ /g, '-'));
+          button.id = "searchbar-engine-one-off-item-" + currentEngine.name.replace(/ /g, '-');
+          let uri = "chrome://browser/skin/search-engine-placeholder.png";
+          if (currentEngine.iconURI)
+            uri = PlacesUtils.getImageURLForResolution(window, currentEngine.iconURI.spec);
+          button.setAttribute("image", uri);
+          button.setAttribute("tooltiptext", currentEngine.name);
+          button.engine = currentEngine;
+
+          Services.search.currentEngine = this._contextEngine;
+        }
       ]]></handler>
 
       <handler event="popuphiding"><![CDATA[
         this._isHiding = true;
         setTimeout(() => {
           this._isHiding = false;
         }, 0);
       ]]></handler>
rename from browser/devtools/debugger/content/stores/event-listeners.js
rename to browser/devtools/debugger/content/actions/event-listeners.js
--- a/browser/devtools/debugger/content/stores/event-listeners.js
+++ b/browser/devtools/debugger/content/actions/event-listeners.js
@@ -1,50 +1,22 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-const constants = require('../constants');
-const promise = require('promise');
-const { rdpInvoke, asPaused } = require('../utils');
+const constants = require("../constants");
+const { rdpInvoke, asPaused } = require("../utils");
 const { reportException } = require("devtools/toolkit/DevToolsUtils");
 
 const FETCH_EVENT_LISTENERS_DELAY = 200; // ms
 
-const initialState = {
-  activeEventNames: [],
-  listeners: [],
-  fetchingListeners: false,
-};
-
-function update(state = initialState, action, emitChange) {
-  switch(action.type) {
-  case constants.UPDATE_EVENT_BREAKPOINTS:
-    state.activeEventNames = action.eventNames;
-    emitChange('activeEventNames', state.activeEventNames);
-    break;
-  case constants.FETCH_EVENT_LISTENERS:
-    if (action.status === "begin") {
-      state.fetchingListeners = true;
-    }
-    else if (action.status === "done") {
-      state.fetchingListeners = false;
-      state.listeners = action.listeners;
-      emitChange('listeners', state.listeners);
-    }
-    break;
-  }
-
-  return state;
-};
-
 function fetchEventListeners() {
   return (dispatch, getState) => {
-    // Make sure we're not sending a batch of closely repeated requests.
+    // Make sure we"re not sending a batch of closely repeated requests.
     // This can easily happen whenever new sources are fetched.
     setNamedTimeout("event-listeners-fetch", FETCH_EVENT_LISTENERS_DELAY, () => {
       // In case there is still a request of listeners going on (it
       // takes several RDP round trips right now), make sure we wait
       // on a currently running request
       if (getState().eventListeners.fetchingListeners) {
         dispatch({
           type: services.WAIT_UNTIL,
@@ -76,31 +48,31 @@ function fetchEventListeners() {
     });
   };
 }
 
 const _getListeners = Task.async(function*() {
   const response = yield rdpInvoke(gThreadClient, gThreadClient.eventListeners);
 
   // Make sure all the listeners are sorted by the event type, since
-  // they're not guaranteed to be clustered together.
+  // they"re not guaranteed to be clustered together.
   response.listeners.sort((a, b) => a.type > b.type ? 1 : -1);
 
   // Add all the listeners in the debugger view event linsteners container.
   let fetchedDefinitions = new Map();
   let listeners = [];
   for (let listener of response.listeners) {
     let definitionSite;
     if (fetchedDefinitions.has(listener.function.actor)) {
       definitionSite = fetchedDefinitions.get(listener.function.actor);
     } else if (listener.function.class == "Function") {
       definitionSite = yield _getDefinitionSite(listener.function);
       if (!definitionSite) {
-        // We don't know where this listener comes from so don't show it in
-        // the UI as breaking on it doesn't work (bug 942899).
+        // We don"t know where this listener comes from so don"t show it in
+        // the UI as breaking on it doesn"t work (bug 942899).
         continue;
       }
 
       fetchedDefinitions.set(listener.function.actor, definitionSite);
     }
     listener.function.url = definitionSite;
     listeners.push(listener);
   }
@@ -136,12 +108,9 @@ function updateEventBreakpoints(eventNam
           type: constants.UPDATE_EVENT_BREAKPOINTS,
           eventNames: eventNames
         });
       });
     });
   }
 }
 
-module.exports = {
-  update: update,
-  actions: { updateEventBreakpoints, fetchEventListeners }
-}
+module.exports = { updateEventBreakpoints, fetchEventListeners };
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/content/reducers/event-listeners.js
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const constants = require('../constants');
+
+const FETCH_EVENT_LISTENERS_DELAY = 200; // ms
+
+const initialState = {
+  activeEventNames: [],
+  listeners: [],
+  fetchingListeners: false,
+};
+
+function update(state = initialState, action, emit) {
+  switch(action.type) {
+  case constants.UPDATE_EVENT_BREAKPOINTS:
+    state.activeEventNames = action.eventNames;
+    emit("@redux:activeEventNames", state.activeEventNames);
+    break;
+  case constants.FETCH_EVENT_LISTENERS:
+    if (action.status === "begin") {
+      state.fetchingListeners = true;
+    }
+    else if (action.status === "done") {
+      state.fetchingListeners = false;
+      state.listeners = action.listeners;
+      emit("@redux:listeners", state.listeners);
+    }
+    break;
+  }
+
+  return state;
+}
+
+module.exports = update;
rename from browser/devtools/debugger/content/stores/index.js
rename to browser/devtools/debugger/content/reducers/index.js
--- a/browser/devtools/debugger/content/stores/index.js
+++ b/browser/devtools/debugger/content/reducers/index.js
@@ -1,8 +1,8 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const eventListeners = require('./event-listeners');
 
-module.exports = { eventListeners };
+exports.eventListeners = eventListeners;
--- a/browser/devtools/debugger/content/views/event-listeners-view.js
+++ b/browser/devtools/debugger/content/views/event-listeners-view.js
@@ -1,33 +1,32 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-const actions = require('../stores/event-listeners').actions;
-const bindActionCreators = require('devtools/shared/fluxify/bindActionCreators');
+const actions = require('../actions/event-listeners');
+const { bindActionCreators } = require('devtools/shared/vendor/redux');
 
 /**
  * Functions handling the event listeners UI.
  */
-function EventListenersView(dispatcher, DebuggerController) {
+function EventListenersView(store, DebuggerController) {
   dumpn("EventListenersView was instantiated");
 
-  this.actions = bindActionCreators(actions, dispatcher.dispatch);
-  this.getState = () => dispatcher.getState().eventListeners;
-
-  this.Breakpoints = DebuggerController.Breakpoints;
-
-  dispatcher.onChange({
-    "eventListeners": { "listeners": this.renderListeners }
-  }, this);
+  this.actions = bindActionCreators(actions, store.dispatch);
+  this.getState = () => store.getState().eventListeners;
 
   this._onCheck = this._onCheck.bind(this);
   this._onClick = this._onClick.bind(this);
+  this._onListeners = this._onListeners.bind(this);
+
+  this.Breakpoints = DebuggerController.Breakpoints;
+  this.controller = DebuggerController;
+  this.controller.on("@redux:listeners", this._onListeners);
 }
 
 EventListenersView.prototype = Heritage.extend(WidgetMethods, {
   /**
    * Initialization function, called when the debugger is started.
    */
   initialize: function() {
     dumpn("Initializing the EventListenersView");
@@ -48,18 +47,20 @@ EventListenersView.prototype = Heritage.
   },
 
   /**
    * Destruction function, called when the debugger is closed.
    */
   destroy: function() {
     dumpn("Destroying the EventListenersView");
 
+    this.controller.off("@redux:listeners", this._onListeners);
     this.widget.removeEventListener("check", this._onCheck, false);
     this.widget.removeEventListener("click", this._onClick, false);
+    this.controller = this.Breakpoints = null;
   },
 
   renderListeners: function(listeners) {
     listeners.forEach(listener => {
       this.addListener(listener, { staged: true });
     });
 
     // Flushes all the prepared events into the event listeners container.
@@ -280,15 +281,22 @@ EventListenersView.prototype = Heritage.
     // when retrieving the target's item, to ignore the checkbox.
     let eventItem = this.getItemForElement(target, { noSiblings: true });
     if (eventItem) {
       let newState = eventItem.attachment.checkboxState ^= 1;
       this.callMethod("checkItem", eventItem.target, newState);
     }
   },
 
+  /**
+   * Called when listeners change.
+   */
+  _onListeners: function(_, listeners) {
+    this.renderListeners(listeners);
+  },
+
   _eventCheckboxTooltip: "",
   _onSelectorString: "",
   _inSourceString: "",
   _inNativeCodeString: ""
 });
 
 module.exports = EventListenersView;
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -1275,17 +1275,17 @@ SourceScripts.prototype = {
 
     // If there are any stored breakpoints for this source, display them again,
     // both in the editor and the breakpoints pane.
     DebuggerController.Breakpoints.updatePaneBreakpoints();
     DebuggerController.Breakpoints.updateEditorBreakpoints();
 
     // Make sure the events listeners are up to date.
     if (DebuggerView.instrumentsPaneTab == "events-tab") {
-      dispatcher.dispatch(actions.fetchEventListeners());
+      store.dispatch(actions.fetchEventListeners());
     }
 
     // Signal that a new source has been added.
     window.emit(EVENTS.NEW_SOURCE);
   },
 
   /**
    * Callback for the debugger's active thread getSources() method.
@@ -2050,16 +2050,17 @@ var Prefs = new ViewHelpers.Prefs("devto
   autoBlackBox: ["Bool", "debugger.auto-black-box"],
   promiseDebuggerEnabled: ["Bool", "debugger.promise"]
 });
 
 /**
  * Convenient way of emitting events from the panel window.
  */
 EventEmitter.decorate(this);
+EventEmitter.decorate(DebuggerController);
 
 /**
  * Preliminary setup for the DebuggerController object.
  */
 DebuggerController.initialize();
 DebuggerController.Parser = new Parser();
 DebuggerController.Workers = new Workers();
 DebuggerController.ThreadState = new ThreadState();
--- a/browser/devtools/debugger/debugger-view.js
+++ b/browser/devtools/debugger/debugger-view.js
@@ -30,26 +30,29 @@ const SEARCH_VARIABLE_FLAG = "*";
 const SEARCH_AUTOFILL = [SEARCH_GLOBAL_FLAG, SEARCH_FUNCTION_FLAG, SEARCH_TOKEN_FLAG];
 const EDITOR_VARIABLE_HOVER_DELAY = 750; // ms
 const EDITOR_VARIABLE_POPUP_POSITION = "topcenter bottomleft";
 const TOOLBAR_ORDER_POPUP_POSITION = "topcenter bottomleft";
 const RESIZE_REFRESH_RATE = 50; // ms
 const PROMISE_DEBUGGER_URL =
   "chrome://browser/content/devtools/promisedebugger/promise-debugger.xhtml";
 
-const createDispatcher = require('devtools/shared/create-dispatcher')();
-const stores = require('./content/stores/index');
-const dispatcher = createDispatcher(stores);
-const waitUntilService = require('devtools/shared/fluxify/waitUntilService');
+const debuggerControllerEmit = DebuggerController.emit.bind(DebuggerController);
+const createStore = require("devtools/shared/redux/create-store")();
+const { combineEmittingReducers } = require("devtools/shared/redux/reducers");
+const reducers = require("./content/reducers/index");
+const store = createStore(combineEmittingReducers(reducers, debuggerControllerEmit));
+const { NAME: WAIT_UNTIL_NAME } = require("devtools/shared/redux/middleware/wait-service");
+
 const services = {
-  WAIT_UNTIL: waitUntilService.name
+  WAIT_UNTIL: WAIT_UNTIL_NAME
 };
 
 const EventListenersView = require('./content/views/event-listeners-view');
-const actions = require('./content/stores/event-listeners').actions;
+const actions = require('./content/actions/event-listeners');
 
 /**
  * Object defining the debugger view components.
  */
 var DebuggerView = {
   /**
    * Initializes the debugger view.
    *
@@ -621,17 +624,17 @@ var DebuggerView = {
     }, 0);
   },
 
   /**
    * Handles a tab selection event on the instruments pane.
    */
   _onInstrumentsPaneTabSelect: function() {
     if (this._instrumentsPane.selectedTab.id == "events-tab") {
-      dispatcher.dispatch(actions.fetchEventListeners());
+      store.dispatch(actions.fetchEventListeners());
     }
   },
 
   /**
    * Handles a host change event issued by the parent toolbox.
    *
    * @param string aType
    *        The host type, either "bottom", "side" or "window".
@@ -923,9 +926,9 @@ ResultsPanelContainer.prototype = Herita
 
   _anchor: null,
   _panel: null,
   position: RESULTS_PANEL_POPUP_POSITION,
   left: 0,
   top: 0
 });
 
-DebuggerView.EventListeners = new EventListenersView(dispatcher, DebuggerController);
+DebuggerView.EventListeners = new EventListenersView(store, DebuggerController);
--- a/browser/devtools/debugger/moz.build
+++ b/browser/devtools/debugger/moz.build
@@ -13,14 +13,18 @@ EXTRA_JS_MODULES.devtools.debugger.conte
     'content/globalActions.js',
     'content/utils.js'
 ]
 
 EXTRA_JS_MODULES.devtools.debugger.content.views += [
     'content/views/event-listeners-view.js'
 ]
 
-EXTRA_JS_MODULES.devtools.debugger.content.stores += [
-    'content/stores/event-listeners.js',
-    'content/stores/index.js'
+EXTRA_JS_MODULES.devtools.debugger.content.reducers += [
+    'content/reducers/event-listeners.js',
+    'content/reducers/index.js'
+]
+
+EXTRA_JS_MODULES.devtools.debugger.content.actions += [
+    'content/actions/event-listeners.js',
 ]
 
 BROWSER_CHROME_MANIFESTS += ['test/mochitest/browser.ini']
--- a/browser/devtools/debugger/test/mochitest/browser_dbg_break-on-dom-02.js
+++ b/browser/devtools/debugger/test/mochitest/browser_dbg_break-on-dom-02.js
@@ -8,30 +8,30 @@
 
 const TAB_URL = EXAMPLE_URL + "doc_event-listeners-02.html";
 
 function test() {
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     let gDebugger = aPanel.panelWin;
     let gView = gDebugger.DebuggerView;
     let gEvents = gView.EventListeners;
-    let gDispatcher = gDebugger.dispatcher;
+    let gStore = gDebugger.store;
     let constants = gDebugger.require('./content/constants');
 
     Task.spawn(function*() {
       yield waitForSourceShown(aPanel, ".html");
       yield testFetchOnFocus();
       yield testFetchOnReloadWhenFocused();
       yield testFetchOnReloadWhenNotFocused();
       yield closeDebuggerAndFinish(aPanel);
     });
 
     function testFetchOnFocus() {
       return Task.spawn(function*() {
-        let fetched = afterDispatch(gDispatcher, constants.FETCH_EVENT_LISTENERS);
+        let fetched = afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
 
         gView.toggleInstrumentsPane({ visible: true, animated: false }, 1);
         is(gView.instrumentsPaneHidden, false,
           "The instruments pane should be visible now.");
         is(gView.instrumentsPaneTab, "events-tab",
           "The events tab should be selected.");
 
         yield fetched;
@@ -40,17 +40,17 @@ function test() {
           "Event listeners were fetched when the events tab was selected");
         is(gEvents.itemCount, 4,
           "There should be 4 events displayed in the view.");
       });
     }
 
     function testFetchOnReloadWhenFocused() {
       return Task.spawn(function*() {
-        let fetched = afterDispatch(gDispatcher, constants.FETCH_EVENT_LISTENERS);
+        let fetched = afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
 
         let reloading = once(gDebugger.gTarget, "will-navigate");
         let reloaded = waitForSourcesAfterReload();
         gDebugger.DebuggerController._target.activeTab.reload();
 
         yield reloading;
 
         is(gEvents.itemCount, 0,
@@ -71,17 +71,17 @@ function test() {
           "There should be 4 events displayed in the view after reloading.");
         ok(true,
           "Event listeners were added back after the target finished navigating.");
       });
     }
 
     function testFetchOnReloadWhenNotFocused() {
       return Task.spawn(function*() {
-        gDispatcher.dispatch({
+        gStore.dispatch({
           type: gDebugger.services.WAIT_UNTIL,
           predicate: action => {
             return (action.type === constants.FETCH_EVENT_LISTENERS ||
                     action.type === constants.UPDATE_EVENT_BREAKPOINTS);
           },
           run: (dispatch, getState, action) => {
             if(action.type === constants.FETCH_EVENT_LISTENERS) {
               ok(false, "Shouldn't have fetched any event listeners.");
--- a/browser/devtools/debugger/test/mochitest/browser_dbg_break-on-dom-03.js
+++ b/browser/devtools/debugger/test/mochitest/browser_dbg_break-on-dom-03.js
@@ -7,23 +7,23 @@
 
 const TAB_URL = EXAMPLE_URL + "doc_event-listeners-02.html";
 
 function test() {
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     let gDebugger = aPanel.panelWin;
     let gView = gDebugger.DebuggerView;
     let gEvents = gView.EventListeners;
-    let gDispatcher = gDebugger.dispatcher;
+    let gStore = gDebugger.store;
     let constants = gDebugger.require('./content/constants');
 
     Task.spawn(function*() {
       yield waitForSourceShown(aPanel, ".html");
 
-      let fetched = afterDispatch(gDispatcher, constants.FETCH_EVENT_LISTENERS);
+      let fetched = afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
       gView.toggleInstrumentsPane({ visible: true, animated: false }, 1);
       yield fetched;
 
       is(gEvents.widget._parent.querySelectorAll(".side-menu-widget-group").length, 3,
         "There should be 3 groups shown in the view.");
       is(gEvents.widget._parent.querySelectorAll(".side-menu-widget-group-checkbox").length, 3,
         "There should be a checkbox for each group shown in the view.");
 
--- a/browser/devtools/debugger/test/mochitest/browser_dbg_break-on-dom-04.js
+++ b/browser/devtools/debugger/test/mochitest/browser_dbg_break-on-dom-04.js
@@ -9,50 +9,50 @@
 const TAB_URL = EXAMPLE_URL + "doc_event-listeners-02.html";
 
 function test() {
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     let gDebugger = aPanel.panelWin;
     let gView = gDebugger.DebuggerView;
     let gController = gDebugger.DebuggerController
     let gEvents = gView.EventListeners;
-    let gDispatcher = gDebugger.dispatcher;
-    let getState = gDispatcher.getState;
+    let gStore = gDebugger.store;
+    let getState = gStore.getState;
     let constants = gDebugger.require('./content/constants');
 
     Task.spawn(function*() {
       yield waitForSourceShown(aPanel, ".html");
 
-      let fetched = afterDispatch(gDispatcher, constants.FETCH_EVENT_LISTENERS);
+      let fetched = afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
       gView.toggleInstrumentsPane({ visible: true, animated: false }, 1);
       yield fetched;
 
       testEventItem(0, false);
       testEventItem(1, false);
       testEventItem(2, false);
       testEventItem(3, false);
       testEventGroup("interactionEvents", false);
       testEventGroup("keyboardEvents", false);
       testEventGroup("mouseEvents", false);
       testEventArrays("change,click,keydown,keyup", "");
 
-      let updated = afterDispatch(gDispatcher, constants.UPDATE_EVENT_BREAKPOINTS);
+      let updated = afterDispatch(gStore, constants.UPDATE_EVENT_BREAKPOINTS);
       EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(0), gDebugger);
       yield updated;
 
       testEventItem(0, true);
       testEventItem(1, false);
       testEventItem(2, false);
       testEventItem(3, false);
       testEventGroup("interactionEvents", false);
       testEventGroup("keyboardEvents", false);
       testEventGroup("mouseEvents", false);
       testEventArrays("change,click,keydown,keyup", "change");
 
-      updated = afterDispatch(gDispatcher, constants.UPDATE_EVENT_BREAKPOINTS);
+      updated = afterDispatch(gStore, constants.UPDATE_EVENT_BREAKPOINTS);
       EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(0), gDebugger);
       yield updated;
 
       testEventItem(0, false);
       testEventItem(1, false);
       testEventItem(2, false);
       testEventItem(3, false);
       testEventGroup("interactionEvents", false);
--- a/browser/devtools/debugger/test/mochitest/browser_dbg_break-on-dom-05.js
+++ b/browser/devtools/debugger/test/mochitest/browser_dbg_break-on-dom-05.js
@@ -10,76 +10,76 @@
 const TAB_URL = EXAMPLE_URL + "doc_event-listeners-02.html";
 
 function test() {
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     let gDebugger = aPanel.panelWin;
     let gView = gDebugger.DebuggerView;
     let gController = gDebugger.DebuggerController
     let gEvents = gView.EventListeners;
-    let gDispatcher = gDebugger.dispatcher;
-    let getState = gDispatcher.getState;
+    let gStore = gDebugger.store;
+    let getState = gStore.getState;
     let constants = gDebugger.require('./content/constants');
 
     Task.spawn(function*() {
       yield waitForSourceShown(aPanel, ".html");
 
-      let fetched = afterDispatch(gDispatcher, constants.FETCH_EVENT_LISTENERS);
+      let fetched = afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
       gView.toggleInstrumentsPane({ visible: true, animated: false }, 1);
       yield fetched;
 
       testEventItem(0, false);
       testEventItem(1, false);
       testEventItem(2, false);
       testEventItem(3, false);
       testEventGroup("interactionEvents", false);
       testEventGroup("keyboardEvents", false);
       testEventGroup("mouseEvents", false);
       testEventArrays("change,click,keydown,keyup", "");
 
-      let updated = afterDispatch(gDispatcher, constants.UPDATE_EVENT_BREAKPOINTS);
+      let updated = afterDispatch(gStore, constants.UPDATE_EVENT_BREAKPOINTS);
       EventUtils.sendMouseEvent({ type: "click" }, getGroupCheckboxNode("interactionEvents"), gDebugger);
       yield updated;
 
       testEventItem(0, true);
       testEventItem(1, false);
       testEventItem(2, false);
       testEventItem(3, false);
       testEventGroup("interactionEvents", true);
       testEventGroup("keyboardEvents", false);
       testEventGroup("mouseEvents", false);
       testEventArrays("change,click,keydown,keyup", "change");
 
-      updated = afterDispatch(gDispatcher, constants.UPDATE_EVENT_BREAKPOINTS);
+      updated = afterDispatch(gStore, constants.UPDATE_EVENT_BREAKPOINTS);
       EventUtils.sendMouseEvent({ type: "click" }, getGroupCheckboxNode("interactionEvents"), gDebugger);
       yield updated;
 
       testEventItem(0, false);
       testEventItem(1, false);
       testEventItem(2, false);
       testEventItem(3, false);
       testEventGroup("interactionEvents", false);
       testEventGroup("keyboardEvents", false);
       testEventGroup("mouseEvents", false);
       testEventArrays("change,click,keydown,keyup", "");
 
-      updated = afterDispatch(gDispatcher, constants.UPDATE_EVENT_BREAKPOINTS);
+      updated = afterDispatch(gStore, constants.UPDATE_EVENT_BREAKPOINTS);
       EventUtils.sendMouseEvent({ type: "click" }, getGroupCheckboxNode("keyboardEvents"), gDebugger);
       yield updated;
 
       testEventItem(0, false);
       testEventItem(1, false);
       testEventItem(2, true);
       testEventItem(3, true);
       testEventGroup("interactionEvents", false);
       testEventGroup("keyboardEvents", true);
       testEventGroup("mouseEvents", false);
       testEventArrays("change,click,keydown,keyup", "keydown,keyup");
 
-      updated = afterDispatch(gDispatcher, constants.UPDATE_EVENT_BREAKPOINTS);
+      updated = afterDispatch(gStore, constants.UPDATE_EVENT_BREAKPOINTS);
       EventUtils.sendMouseEvent({ type: "click" }, getGroupCheckboxNode("keyboardEvents"), gDebugger);
       yield updated;
 
       testEventItem(0, false);
       testEventItem(1, false);
       testEventItem(2, false);
       testEventItem(3, false);
       testEventGroup("interactionEvents", false);
--- a/browser/devtools/debugger/test/mochitest/browser_dbg_break-on-dom-06.js
+++ b/browser/devtools/debugger/test/mochitest/browser_dbg_break-on-dom-06.js
@@ -10,80 +10,80 @@ const TAB_URL = EXAMPLE_URL + "doc_event
 
 function test() {
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     let gDebugger = aPanel.panelWin;
     let gView = gDebugger.DebuggerView;
     let gController = gDebugger.DebuggerController
     let gEvents = gView.EventListeners;
     let gBreakpoints = gController.Breakpoints;
-    let gDispatcher = gDebugger.dispatcher;
-    let getState = gDispatcher.getState;
+    let gStore = gDebugger.store;
+    let getState = gStore.getState;
     let constants = gDebugger.require('./content/constants');
 
     Task.spawn(function*() {
       yield waitForSourceShown(aPanel, ".html");
 
-      let fetched = afterDispatch(gDispatcher, constants.FETCH_EVENT_LISTENERS);
+      let fetched = afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
       gView.toggleInstrumentsPane({ visible: true, animated: false }, 1);
       yield fetched;
 
       testEventItem(0, false);
       testEventItem(1, false);
       testEventItem(2, false);
       testEventItem(3, false);
       testEventGroup("interactionEvents", false);
       testEventGroup("keyboardEvents", false);
       testEventGroup("mouseEvents", false);
       testEventArrays("change,click,keydown,keyup", "");
 
-      let updated = afterDispatch(gDispatcher, constants.UPDATE_EVENT_BREAKPOINTS);
+      let updated = afterDispatch(gStore, constants.UPDATE_EVENT_BREAKPOINTS);
       EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(0), gDebugger);
       EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(1), gDebugger);
       EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(2), gDebugger);
       yield updated;
 
       testEventItem(0, true);
       testEventItem(1, true);
       testEventItem(2, true);
       testEventItem(3, false);
       testEventGroup("interactionEvents", false);
       testEventGroup("keyboardEvents", false);
       testEventGroup("mouseEvents", false);
       testEventArrays("change,click,keydown,keyup", "change,click,keydown");
 
       reload(aPanel);
-      yield afterDispatch(gDispatcher, constants.FETCH_EVENT_LISTENERS);
+      yield afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
 
       testEventItem(0, true);
       testEventItem(1, true);
       testEventItem(2, true);
       testEventItem(3, false);
       testEventGroup("interactionEvents", false);
       testEventGroup("keyboardEvents", false);
       testEventGroup("mouseEvents", false);
       testEventArrays("change,click,keydown,keyup", "change,click,keydown");
 
-      updated = afterDispatch(gDispatcher, constants.UPDATE_EVENT_BREAKPOINTS);
+      updated = afterDispatch(gStore, constants.UPDATE_EVENT_BREAKPOINTS);
       EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(0), gDebugger);
       EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(1), gDebugger);
       EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(2), gDebugger);
       yield updated;
 
       testEventItem(0, false);
       testEventItem(1, false);
       testEventItem(2, false);
       testEventItem(3, false);
       testEventGroup("interactionEvents", false);
       testEventGroup("keyboardEvents", false);
       testEventGroup("mouseEvents", false);
       testEventArrays("change,click,keydown,keyup", "");
 
       reload(aPanel);
-      yield afterDispatch(gDispatcher, constants.FETCH_EVENT_LISTENERS);
+      yield afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
 
       testEventItem(0, false);
       testEventItem(1, false);
       testEventItem(2, false);
       testEventItem(3, false);
       testEventGroup("interactionEvents", false);
       testEventGroup("keyboardEvents", false);
       testEventGroup("mouseEvents", false);
--- a/browser/devtools/debugger/test/mochitest/browser_dbg_break-on-dom-08.js
+++ b/browser/devtools/debugger/test/mochitest/browser_dbg_break-on-dom-08.js
@@ -8,35 +8,35 @@
 const TAB_URL = EXAMPLE_URL + "doc_event-listeners-02.html";
 
 function test() {
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     let gTab = aTab;
     let gDebugger = aPanel.panelWin;
     let gView = gDebugger.DebuggerView;
     let gEvents = gView.EventListeners;
-    let gDispatcher = gDebugger.dispatcher;
-    let getState = gDispatcher.getState;
+    let gStore = gDebugger.store;
+    let getState = gStore.getState;
     let constants = gDebugger.require('./content/constants');
 
     Task.spawn(function*() {
       yield waitForSourceShown(aPanel, ".html");
       yield callInTab(gTab, "addBodyClickEventListener");
 
-      let fetched = afterDispatch(gDispatcher, constants.FETCH_EVENT_LISTENERS);
+      let fetched = afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
       gView.toggleInstrumentsPane({ visible: true, animated: false }, 1);
       yield fetched;
       yield ensureThreadClientState(aPanel, "attached");
 
       is(gView.instrumentsPaneHidden, false,
         "The instruments pane should be visible.");
       is(gView.instrumentsPaneTab, "events-tab",
         "The events tab should be selected.");
 
-      let updated = afterDispatch(gDispatcher, constants.UPDATE_EVENT_BREAKPOINTS);
+      let updated = afterDispatch(gStore, constants.UPDATE_EVENT_BREAKPOINTS);
       EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(1), gDebugger);
       yield updated;
       yield ensureThreadClientState(aPanel, "attached");
 
       let paused = waitForCaretAndScopes(aPanel, 48);
       generateMouseClickInTab(gTab, "content.document.body");
       yield paused;
       yield ensureThreadClientState(aPanel, "paused");
--- a/browser/devtools/debugger/test/mochitest/browser_dbg_event-listeners-04.js
+++ b/browser/devtools/debugger/test/mochitest/browser_dbg_event-listeners-04.js
@@ -26,21 +26,21 @@ add_task(function* () {
   info("Attaching an event handler via add-on sdk content scripts.");
   let worker = sdkTab.attach({
     contentScript: "document.body.addEventListener('click', e => alert(e))",
     onError: ok.bind(this, false)
   });
 
   let [,, panel, win] = yield initDebugger(tab);
   let gDebugger = panel.panelWin;
-  let gDispatcher = gDebugger.dispatcher;
+  let gStore = gDebugger.store;
   let constants = gDebugger.require('./content/constants');
-  let eventListeners = gDebugger.require('./content/stores/event-listeners');
-  let fetched = afterDispatch(gDispatcher, constants.FETCH_EVENT_LISTENERS);
+  let actions = gDebugger.require('./content/actions/event-listeners');
+  let fetched = afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
 
   info("Scheduling event listener fetch.");
-  gDispatcher.dispatch(eventListeners.actions.fetchEventListeners());
+  gStore.dispatch(actions.fetchEventListeners());
 
   info("Waiting for updated event listeners to arrive.");
   yield fetched;
 
   ok(true, "The listener update did not hang.");
 });
--- a/browser/devtools/debugger/test/mochitest/head.js
+++ b/browser/devtools/debugger/test/mochitest/head.js
@@ -1187,20 +1187,20 @@ function setBreakpoint(sourceClient, loc
   return rdpInvoke(sourceClient, sourceClient.setBreakpoint, location);
 }
 
 function source(sourceClient) {
   info("Getting source.\n");
   return rdpInvoke(sourceClient, sourceClient.source);
 }
 
-function afterDispatch(dispatcher, type) {
+function afterDispatch(store, type) {
   info("Waiting on dispatch: " + type);
   return new Promise(resolve => {
-    dispatcher.dispatch({
+    store.dispatch({
       // Normally we would use `services.WAIT_UNTIL`, but use the
       // internal name here so tests aren't forced to always pass it
       // in
       type: "@@service/waitUntil",
       predicate: action => (
         action.type === type &&
         action.status ? action.status === "done" : true
       ),
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -1,13 +1,15 @@
 # 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/.
 
 browser.jar:
+    content/browser/devtools/d3.js                                     (shared/vendor/d3.js)
+    content/browser/devtools/dagre-d3.js                               (shared/vendor/dagre-d3.js)
     content/browser/devtools/widgets.css                               (shared/widgets/widgets.css)
     content/browser/devtools/widgets/VariablesView.xul                 (shared/widgets/VariablesView.xul)
     content/browser/devtools/markup-view.xhtml                         (markupview/markup-view.xhtml)
     content/browser/devtools/markup-view.css                           (markupview/markup-view.css)
     content/browser/devtools/projecteditor.xul                               (projecteditor/chrome/content/projecteditor.xul)
     content/browser/devtools/readdir.js                                (projecteditor/lib/helpers/readdir.js)
     content/browser/devtools/projecteditor-loader.xul                        (projecteditor/chrome/content/projecteditor-loader.xul)
     content/browser/devtools/projecteditor-test.xul                          (projecteditor/chrome/content/projecteditor-test.xul)
@@ -79,19 +81,17 @@ browser.jar:
     content/browser/devtools/debugger/filter-view.js                   (debugger/views/filter-view.js)
     content/browser/devtools/debugger/utils.js                         (debugger/utils.js)
     content/browser/devtools/shadereditor.xul                          (shadereditor/shadereditor.xul)
     content/browser/devtools/shadereditor.js                           (shadereditor/shadereditor.js)
     content/browser/devtools/canvasdebugger.xul                        (canvasdebugger/canvasdebugger.xul)
     content/browser/devtools/canvasdebugger.js                         (canvasdebugger/canvasdebugger.js)
     content/browser/devtools/canvasdebugger/snapshotslist.js           (canvasdebugger/snapshotslist.js)
     content/browser/devtools/canvasdebugger/callslist.js               (canvasdebugger/callslist.js)
-    content/browser/devtools/d3.js                                     (shared/d3.js)
     content/browser/devtools/webaudioeditor.xul                        (webaudioeditor/webaudioeditor.xul)
-    content/browser/devtools/dagre-d3.js                               (webaudioeditor/lib/dagre-d3.js)
     content/browser/devtools/webaudioeditor/includes.js                (webaudioeditor/includes.js)
     content/browser/devtools/webaudioeditor/models.js                  (webaudioeditor/models.js)
     content/browser/devtools/webaudioeditor/controller.js              (webaudioeditor/controller.js)
     content/browser/devtools/webaudioeditor/views/utils.js             (webaudioeditor/views/utils.js)
     content/browser/devtools/webaudioeditor/views/context.js           (webaudioeditor/views/context.js)
     content/browser/devtools/webaudioeditor/views/inspector.js         (webaudioeditor/views/inspector.js)
     content/browser/devtools/webaudioeditor/views/properties.js        (webaudioeditor/views/properties.js)
     content/browser/devtools/webaudioeditor/views/automation.js        (webaudioeditor/views/automation.js)
--- a/browser/devtools/shared/browser-loader.js
+++ b/browser/devtools/shared/browser-loader.js
@@ -18,16 +18,17 @@ catch(e) {
   // will be loaded if this is true, and that file doesn't get built
   // into the release version of Firefox, so this will only work with
   // dev environments.
   appConstants = {
     DEBUG_JS_MODULES: true
   };
 }
 
+const VENDOR_CONTENT_URL = "resource:///modules/devtools/shared/vendor";
 
 /*
  * Create a loader to be used in a browser environment. This evaluates
  * modules in their own environment, but sets window (the normal
  * global object) as the sandbox prototype, so when a variable is not
  * defined it checks `window` before throwing an error. This makes all
  * browser APIs available to modules by default, like a normal browser
  * environment, but modules are still evaluated in their own scope.
@@ -50,30 +51,31 @@ catch(e) {
  *         - require: a function to require modules with
  */
 function BrowserLoader(baseURI, window) {
   const loaderOptions = devtools.require('@loader/options');
 
   let dynamicPaths = {};
   if (appConstants.DEBUG_JS_MODULES) {
     // Load in the dev version of React
-    dynamicPaths["devtools/shared/content/react"] =
-      "resource:///modules/devtools/shared/content/react-dev.js";
+    dynamicPaths["devtools/shared/vendor/react"] =
+      "resource:///modules/devtools/vendor/react-dev.js";
   }
 
   const opts = {
     id: "browser-loader",
     sharedGlobal: true,
     sandboxPrototype: window,
     paths: Object.assign({}, loaderOptions.paths, dynamicPaths),
     invisibleToDebugger: loaderOptions.invisibleToDebugger,
     require: (id, require) => {
       const uri = require.resolve(id);
+
       if (!uri.startsWith(baseURI) &&
-          !uri.startsWith("resource:///modules/devtools/shared/content")) {
+          !uri.startsWith(VENDOR_CONTENT_URL)) {
         return devtools.require(uri);
       }
       return require(uri);
     }
   };
 
   // The main.js file does not have to actually exist. It just
   // represents the base environment, so requires will be relative to
deleted file mode 100644
--- a/browser/devtools/shared/fluxify/bindActionCreators.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-"use strict";
-
-function bindActionCreator(actionCreator, dispatch) {
-  return (...args) => dispatch(actionCreator(...args));
-}
-
-/**
- * Wraps action creator functions into a function that automatically
- * dispatches the created action with `dispatch`. Normally action
- * creators simply return actions, but wrapped functions will
- * automatically dispatch.
- *
- * @param {object} actionCreators
- *        An object of action creator functions (names as keys)
- * @param {function} dispatch
- */
-function bindActionCreators(actionCreators, dispatch) {
-  let actions = {};
-  for (let k of Object.keys(actionCreators)) {
-    actions[k] = bindActionCreator(actionCreators[k], dispatch);
-  }
-  return actions;
-}
-
-module.exports = bindActionCreators;
deleted file mode 100644
--- a/browser/devtools/shared/fluxify/dispatcher.js
+++ /dev/null
@@ -1,247 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-"use strict";
-
-const { entries, compose } = require("devtools/toolkit/DevToolsUtils");
-
-/**
- * A store creator that creates a dispatch function that runs the
- * provided middlewares before actually dispatching. This allows
- * simple middleware to augment the kinds of actions that can
- * be dispatched. This would be used like this:
- * `createDispatcher = applyMiddleware([fooMiddleware, ...])(createDispatcher)`
- *
- * Middlewares are simple functions that are provided `dispatch` and
- * `getState` functions. They create functions that accept actions and
- * can re-dispatch them in any way they want. A common scenario is
- * asynchronously dispatching multiple actions. Here is a full
- * middleware:
- *
- * function thunkMiddleware({ dispatch, getState }) {
- *  return next => action => {
- *    return typeof action === 'function' ?
- *      action(dispatch, getState) :
- *      next(action);
- *  }
- * }
- *
- * `next` is essentially a `dispatch` function, but it calls the next
- * middelware in the chain (or the real `dispatch` function). Using
- * this middleware, you can return a function that gives you a
- * dispatch function:
- *
- * function actionCreator(timeout) {
- *   return (dispatch, getState) => {
- *     dispatch({ type: TIMEOUT, status: "start" });
- *     setTimeout(() => dispatch({ type: TIMEOUT, status: "end" }),
- *                timeout);
- *   }
- * }
- *
- */
-function applyMiddleware(...middlewares) {
-  return next => stores => {
-    const dispatcher = next(stores);
-    let dispatch = dispatcher.dispatch;
-
-    const api = {
-      getState: dispatcher.getState,
-      dispatch: action => dispatch(action)
-    };
-    const chain = middlewares.map(middleware => middleware(api));
-    dispatch = compose(...chain)(dispatcher.dispatch);
-
-    return Object.assign({}, dispatcher, { dispatch: dispatch });
-  }
-}
-
-/*
- * The heart of the system. This creates a dispatcher which is the
- * interface between views and stores. Views can use a dispatcher
- * instance to dispatch actions, which know nothing about the stores.
- * Actions are broadcasted to all registered stores, and stores can
- * handle the action and update their state. The dispatcher gives
- * stores an `emitChange` function, which signifies that a piece of
- * state has changed. The dispatcher will notify all views that are
- * listening to that piece of state, registered with `onChange`.
- *
- * Views generally are stateless, pure components (eventually React
- * components). They simply take state and render it.
- *
- * Stores make up the entire app state, and are all grouped together
- * into a single app state atom, returned by the dispatcher's
- * `getState` function. The shape of the app state is determined by
- * the `stores` object passed in to the dispatcher, so if
- * `{ foo: fooStore }` was passed to `createDispatcher` the app state
- * would be `{ foo: fooState }`
- *
- * Actions are just JavaScript object with a `type` property and any
- * other fields pertinent to the action. Action creators should
- * generally be used to create actions, which are just functions that
- * return the action object. Additionally, the `bindActionCreators`
- * module provides a function for automatically binding action
- * creators to a dispatch function, so calling them automatically
- * dispatches. For example:
- *
- * ```js
- * // Manually dispatch
- * dispatcher.dispatch({ type: constants.ADD_ITEM, item: item });
- * // Using an action creator
- * dispatcher.dispatch(addItem(item));
- *
- * // Using an action creator bound to dispatch
- * actions = bindActionCreators({ addItem: addItem });
- * actions.addItem(item);
- * ```
- *
- * Our system expects stores to exist as an `update` function. You
- * should define an update function in a module, and optionally
- * any action creators that are useful to go along with it. Here is
- * an example store file:
- *
- * ```js
- * const initialState = { items: [] };
- * function update(state = initialState, action, emitChange) {
- *   if (action.type === constants.ADD_ITEM) {
- *     state.items.push(action.item);
- *     emitChange("items", state.items);
- *   }
- *
- *   return state;
- * }
- *
- * function addItem(item) {
- *   return {
- *     type: constants.ADD_ITEM,
- *     item: item
- *   };
- * }
- *
- * module.exports = {
- *   update: update,
- *   actions: { addItem }
- * }
- * ```
- *
- * Lastly, "constants" are simple strings that specify action names.
- * Usually the entire set of available action types are specified in
- * a `constants.js` file, so they are available globally. Use
- * variables that are the same name as the string, for example
- * `const ADD_ITEM = "ADD_ITEM"`.
- *
- * This entire system was inspired by Redux, which hopefully we will
- * eventually use once things get cleaned up enough. You should read
- * its docs, and keep in mind that it calls stores "reducers" and the
- * dispatcher instance is called a single store.
- * http://rackt.github.io/redux/
- */
-function createDispatcher(stores) {
-  const state = {};
-  const listeners = {};
-  let enqueuedChanges = [];
-  let isDispatching = false;
-
-  // Validate the stores to make sure they have the right shape,
-  // and accumulate the initial state
-  entries(stores).forEach(([name, store]) => {
-    if (!store || typeof store.update !== "function") {
-      throw new Error("Error creating dispatcher: store \"" + name +
-                      "\" does not have an `update` function");
-    }
-
-    state[name] = store.update(undefined, {});
-  });
-
-  function getState() {
-    return state;
-  }
-
-  function emitChange(storeName, dataName, payload) {
-    enqueuedChanges.push([storeName, dataName, payload]);
-  }
-
-  function onChange(paths, view) {
-    entries(paths).forEach(([storeName, data]) => {
-      if (!stores[storeName]) {
-        throw new Error("Error adding onChange handler to store: store " +
-                        "\"" + storeName + "\" does not exist");
-      }
-
-      if (!listeners[storeName]) {
-        listeners[storeName] = [];
-      }
-
-      if (typeof data == 'function') {
-        listeners[storeName].push(data.bind(view));
-      }
-      else {
-        entries(data).forEach(([watchedName, handler]) => {
-          listeners[storeName].push((payload, dataName, storeName) => {
-            if (dataName === watchedName) {
-              handler.call(view, payload, dataName, storeName);
-            }
-          });
-        });
-      }
-    });
-  }
-
-  /**
-   * Flush any enqueued state changes from the dispatch cycle. Listeners
-   * are not immediately notified of changes, only after dispatching
-   * is completed, to ensure that all state is consistent (in the case
-   * of multiple stores changes at once).
-   */
-  function flushChanges() {
-    enqueuedChanges.forEach(([storeName, dataName, payload]) => {
-      if (listeners[storeName]) {
-        listeners[storeName].forEach(listener => {
-          listener(payload, dataName, storeName);
-        });
-      }
-    });
-
-    enqueuedChanges = [];
-  }
-
-  function dispatch(action) {
-    if (isDispatching) {
-      throw new Error('Cannot dispatch in the middle of a dispatch');
-    }
-    if (!action.type) {
-      throw new Error(
-        'action type is null, ' +
-        'did you make a typo when publishing this action? ' +
-        JSON.stringify(action, null, 2)
-      );
-    }
-
-    isDispatching = true;
-    try {
-      entries(stores).forEach(([name, store]) => {
-        state[name] = store.update(
-          state[name],
-          action,
-          emitChange.bind(null, name)
-        );
-      });
-    }
-    finally {
-      isDispatching = false;
-    }
-
-    flushChanges();
-  }
-
-  return {
-    getState,
-    dispatch,
-    onChange
-  };
-}
-
-module.exports = {
-  createDispatcher: createDispatcher,
-  applyMiddleware: applyMiddleware
-};
deleted file mode 100644
--- a/browser/devtools/shared/fluxify/test/unit/head.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-const Cr = Components.results;
-const CC = Components.Constructor;
-
-const { require } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
-const createDispatcher = require('devtools/shared/create-dispatcher')({ log: true });
-const waitUntilService = require('devtools/shared/fluxify/waitUntilService');
-const services = {
-  WAIT_UNTIL: waitUntilService.name
-};
-
-const Services = require("Services");
-const { waitForTick, waitForTime } = require("devtools/toolkit/DevToolsUtils");
-
-var loadSubScript = Cc[
-  '@mozilla.org/moz/jssubscript-loader;1'
-].getService(Ci.mozIJSSubScriptLoader).loadSubScript;
-
-function getFileUrl(name, allowMissing=false) {
-  let file = do_get_file(name, allowMissing);
-  return Services.io.newFileURI(file).spec;
-}
deleted file mode 100644
--- a/browser/devtools/shared/fluxify/test/unit/stores-for-testing.js
+++ /dev/null
@@ -1,48 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-// This file should be loaded with `loadSubScript` because it contains
-// stateful stores that need a new instance per test.
-
-const NumberStore = {
-  update: (state = 1, action, emitChange) => {
-    switch(action.type) {
-    case constants.ADD_NUMBER: {
-      const newState = state + action.value;
-      emitChange('number', newState);
-      return newState;
-    }
-    case constants.DOUBLE_NUMBER: {
-      const newState = state * 2;
-      emitChange('number', newState);
-      return newState;
-    }}
-
-    return state;
-  },
-
-  constants: {
-    ADD_NUMBER: 'ADD_NUMBER',
-    DOUBLE_NUMBER: 'DOUBLE_NUMBER'
-  }
-};
-
-const itemsInitialState = {
-  list: []
-};
-const ItemStore = {
-  update: (state = itemsInitialState, action, emitChange) => {
-    switch(action.type) {
-    case constants.ADD_ITEM:
-      state.list.push(action.item);
-      emitChange('list', state.list);
-      return state;
-    }
-
-    return state;
-  },
-
-  constants: {
-    ADD_ITEM: 'ADD_ITEM'
-  }
-}
deleted file mode 100644
--- a/browser/devtools/shared/fluxify/test/unit/test_dispatcher.js
+++ /dev/null
@@ -1,101 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-loadSubScript(getFileUrl("stores-for-testing.js"));
-const constants = Object.assign(
-  {},
-  NumberStore.constants,
-  ItemStore.constants
-);
-const stores = { number: NumberStore,
-                 items: ItemStore };
-
-function addNumber(num) {
-  return {
-    type: constants.ADD_NUMBER,
-    value: num
-  }
-}
-
-function addItem(item) {
-  return {
-    type: constants.ADD_ITEM,
-    item: item
-  };
-}
-
-// Tests
-
-function run_test() {
-  testInitialValue();
-  testDispatch();
-  testEmitChange();
-  run_next_test();
-}
-
-function testInitialValue() {
-  do_print("Testing initial value");
-  const dispatcher = createDispatcher(stores);
-  equal(dispatcher.getState().number, 1);
-}
-
-function testDispatch() {
-  do_print("Testing dispatch");
-
-  const dispatcher = createDispatcher(stores);
-  dispatcher.dispatch(addNumber(5));
-  equal(dispatcher.getState().number, 6);
-
-  dispatcher.dispatch(addNumber(2));
-  equal(dispatcher.getState().number, 8);
-
-  // It should ignore unknown action types
-  dispatcher.dispatch({ type: "FOO" });
-  equal(dispatcher.getState().number, 8);
-}
-
-function testEmitChange() {
-  do_print("Testing change emittters");
-  const dispatcher = createDispatcher(stores);
-  let listenerRan = false;
-
-  const numberView = {
-    x: 3,
-    renderNumber: function(num) {
-      ok(this.x, 3, "listener ran in context of view");
-      ok(num, 10);
-      listenerRan = true;
-    }
-  }
-
-  // Views can listen to changes in state by specifying which part of
-  // the state to listen to and giving a handler function. The
-  // function will be run with the view as `this`.
-  dispatcher.onChange({
-    "number": numberView.renderNumber
-  }, numberView);
-
-  dispatcher.dispatch(addNumber(9));
-  ok(listenerRan, "number listener actually ran");
-  listenerRan = false;
-
-  const itemsView = {
-    renderList: function(items) {
-      ok(items.length, 1);
-      ok(items[0].name = "james");
-      listenerRan = true;
-    }
-  }
-
-  // You can listen to deeper sections of the state by nesting objects
-  // to specify the path to that state. You can do this 1 level deep;
-  // you cannot arbitrarily nest state listeners.
-  dispatcher.onChange({
-    "items": {
-      "list": itemsView.renderList
-    }
-  }, itemsView);
-
-  dispatcher.dispatch(addItem({ name: "james" }));
-  ok(listenerRan, "items listener actually ran");
-}
deleted file mode 100644
--- a/browser/devtools/shared/fluxify/test/unit/test_middlewares.js
+++ /dev/null
@@ -1,114 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-loadSubScript(getFileUrl('stores-for-testing.js'));
-const constants = NumberStore.constants;
-const stores = { number: NumberStore };
-
-function run_test() {
-  run_next_test();
-}
-
-add_task(function* testThunkDispatch() {
-  do_print("Testing thunk dispatch");
-  // The thunk middleware allows you to return a function from an
-  // action creator which takes `dispatch` and `getState` functions as
-  // arguments. This allows the action creator to fire multiple
-  // actions manually with `dispatch`, possibly asynchronously.
-
-  function addNumberLater(num) {
-    return dispatch => {
-      // Just do it in the next tick, no need to wait too long
-      waitForTick().then(() => {
-        dispatch({
-          type: constants.ADD_NUMBER,
-          value: num
-        });
-      });
-    };
-  }
-
-  function addNumber(num) {
-    return (dispatch, getState) => {
-      dispatch({
-        type: constants.ADD_NUMBER,
-        value: getState().number > 10 ? (num * 2) : num
-      });
-    };
-  }
-
-  const dispatcher = createDispatcher(stores);
-  equal(dispatcher.getState().number, 1);
-  dispatcher.dispatch(addNumberLater(5));
-  equal(dispatcher.getState().number, 1, "state should not have changed");
-  yield waitForTick();
-  equal(dispatcher.getState().number, 6, "state should have changed");
-
-  dispatcher.dispatch(addNumber(5));
-  equal(dispatcher.getState().number, 11);
-  dispatcher.dispatch(addNumber(2));
-  // 2 * 2 should have actually been added because the state is
-  // greater than 10 (the action creator changes the value based on
-  // the current state)
-  equal(dispatcher.getState().number, 15);
-});
-
-add_task(function* testWaitUntilService() {
-  do_print("Testing waitUntil service");
-  // The waitUntil service allows you to queue functions to be run at a
-  // later time, depending on a predicate. As actions comes through
-  // the system, you predicate will be called with each action. Once
-  // your predicate returns true, the queued function will be run and
-  // removed from the pending queue.
-
-  function addWhenDoubled(num) {
-    return {
-      type: services.WAIT_UNTIL,
-      predicate: action => action.type === constants.DOUBLE_NUMBER,
-      run: (dispatch, getState, action) => {
-        ok(action.type, constants.DOUBLE_NUMBER);
-        ok(getState(), 10);
-
-        dispatch({
-          type: constants.ADD_NUMBER,
-          value: 2
-        });
-      }
-    };
-  }
-
-  function addWhenGreaterThan(threshold, num) {
-    return (dispatch, getState) => {
-      dispatch({
-        type: services.WAIT_UNTIL,
-        predicate: () => getState().number > threshold,
-        run: () => {
-          dispatch({
-            type: constants.ADD_NUMBER,
-            value: num
-          });
-        }
-      });
-    }
-  }
-
-  const dispatcher = createDispatcher(stores);
-
-  // Add a pending action that adds 2 after the number is doubled
-  equal(dispatcher.getState().number, 1);
-  dispatcher.dispatch(addWhenDoubled(2));
-  equal(dispatcher.getState().number, 1);
-  dispatcher.dispatch({ type: constants.DOUBLE_NUMBER });
-  // Note how the pending function we added ran synchronously. It
-  // should have added 2 after doubling 1, so 1 * 2 + 2 = 4
-  equal(dispatcher.getState().number, 4);
-
-  // Add a pending action that adds 5 once the number is greater than 10
-  dispatcher.dispatch(addWhenGreaterThan(10, 5));
-  equal(dispatcher.getState().number, 4);
-  dispatcher.dispatch({ type: constants.ADD_NUMBER, value: 10 });
-  // Again, the pending function we added ran synchronously. It should
-  // have added 5 more after 10 was added, since the number was
-  // greater than 10.
-  equal(dispatcher.getState().number, 19);
-});
deleted file mode 100644
--- a/browser/devtools/shared/fluxify/test/unit/xpcshell.ini
+++ /dev/null
@@ -1,12 +0,0 @@
-[DEFAULT]
-tags = devtools
-head = head.js
-tail =
-firefox-appdir = browser
-skip-if = toolkit == 'android' || toolkit == 'gonk'
-
-support-files =
-  stores-for-testing.js
-
-[test_dispatcher.js]
-[test_middlewares.js]
\ No newline at end of file
--- a/browser/devtools/shared/moz.build
+++ b/browser/devtools/shared/moz.build
@@ -2,16 +2,21 @@
 # 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/.
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
 XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
 
+DIRS += [
+    'redux',
+    'vendor',
+]
+
 EXTRA_JS_MODULES.devtools += [
     'AppCacheUtils.jsm',
     'Curl.jsm',
     'DeveloperToolbar.jsm',
     'DOMHelpers.jsm',
     'Jsbeautify.jsm',
     'Parser.jsm',
     'SplitView.jsm',
@@ -27,52 +32,40 @@ EXTRA_JS_MODULES.devtools += [
     'widgets/VariablesView.jsm',
     'widgets/VariablesViewController.jsm',
     'widgets/ViewHelpers.jsm',
 ]
 
 EXTRA_JS_MODULES.devtools.shared += [
     'autocomplete-popup.js',
     'browser-loader.js',
-    'create-dispatcher.js',
     'devices.js',
     'doorhanger.js',
     'frame-script-utils.js',
     'getjson.js',
     'inplace-editor.js',
     'node-attribute-parser.js',
     'observable-object.js',
     'options-view.js',
     'poller.js',
     'source-utils.js',
     'telemetry.js',
     'theme-switching.js',
     'theme.js',
     'undo.js'
 ]
 
-EXTRA_JS_MODULES.devtools.shared.content += [
-    'content/react.js'
-]
-
-if CONFIG['DEBUG_JS_MODULES']:
-    EXTRA_JS_MODULES.devtools.shared.content += [
-        'content/react-dev.js'
-    ]
-
 EXTRA_JS_MODULES.devtools.shared.widgets += [
     'widgets/BarGraphWidget.js',
     'widgets/CubicBezierPresets.js',
     'widgets/CubicBezierWidget.js',
     'widgets/FastListWidget.js',
     'widgets/FilterWidget.js',
     'widgets/FlameGraph.js',
     'widgets/Graphs.js',
     'widgets/LineGraphWidget.js',
     'widgets/MdnDocsWidget.js',
     'widgets/MountainGraphWidget.js',
     'widgets/Spectrum.js',
     'widgets/TableWidget.js',
     'widgets/Tooltip.js',
     'widgets/TreeWidget.js',
 ]
-
-DIRS += ['fluxify']
rename from browser/devtools/shared/create-dispatcher.js
rename to browser/devtools/shared/redux/create-store.js
--- a/browser/devtools/shared/create-dispatcher.js
+++ b/browser/devtools/shared/redux/create-store.js
@@ -1,31 +1,35 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-const fluxify = require('./fluxify/dispatcher');
-const thunkMiddleware = require('./fluxify/thunkMiddleware');
-const logMiddleware = require('./fluxify/logMiddleware');
-const waitUntilService = require('./fluxify/waitUntilService')
-const { compose } = require('devtools/toolkit/DevToolsUtils');
+const { createStore, applyMiddleware } = require("devtools/shared/vendor/redux");
+const { thunk } = require("./middleware/thunk");
+const { waitUntilService } = require("./middleware/wait-service");
+const { log } = require("./middleware/log");
 
 /**
  * This creates a dispatcher with all the standard middleware in place
  * that all code requires. It can also be optionally configured in
  * various ways, such as logging and recording.
  *
  * @param {object} opts - boolean configuration flags
  *        - log: log all dispatched actions to console
+ *        - middleware: array of middleware to be included in the redux store
  */
 module.exports = (opts={}) => {
   const middleware = [
-    thunkMiddleware,
-    waitUntilService.service
+    thunk,
+    waitUntilService
   ];
 
   if (opts.log) {
-    middleware.push(logMiddleware);
+    middleware.push(log);
   }
 
-  return fluxify.applyMiddleware(...middleware)(fluxify.createDispatcher);
+  if (opts.middleware) {
+    opts.middleware.forEach(fn => middleware.push(fn));
+  }
+
+  return applyMiddleware(...middleware)(createStore);
 }
rename from browser/devtools/shared/fluxify/logMiddleware.js
rename to browser/devtools/shared/redux/middleware/log.js
--- a/browser/devtools/shared/fluxify/logMiddleware.js
+++ b/browser/devtools/shared/redux/middleware/log.js
@@ -2,16 +2,16 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 /**
  * A middleware that logs all actions coming through the system
  * to the console.
  */
-function logMiddleware({ dispatch, getState }) {
+function log({ dispatch, getState }) {
   return next => action => {
-    console.log('[DISPATCH]', JSON.stringify(action));
+    console.log("[DISPATCH]", JSON.stringify(action));
     next(action);
   }
 }
 
-module.exports = logMiddleware;
+exports.log = log;
rename from browser/devtools/shared/fluxify/thunkMiddleware.js
rename to browser/devtools/shared/redux/middleware/thunk.js
--- a/browser/devtools/shared/fluxify/thunkMiddleware.js
+++ b/browser/devtools/shared/redux/middleware/thunk.js
@@ -4,17 +4,16 @@
 "use strict";
 
 /**
  * A middleware that allows thunks (functions) to be dispatched.
  * If it's a thunk, it is called with `dispatch` and `getState`,
  * allowing the action to create multiple actions (most likely
  * asynchronously).
  */
-function thunkMiddleware({ dispatch, getState }) {
+function thunk({ dispatch, getState }) {
   return next => action => {
     return typeof action === "function"
       ? action(dispatch, getState)
       : next(action);
   }
 }
-
-module.exports = thunkMiddleware;
+exports.thunk = thunk;
rename from browser/devtools/shared/fluxify/waitUntilService.js
rename to browser/devtools/shared/redux/middleware/wait-service.js
--- a/browser/devtools/shared/fluxify/waitUntilService.js
+++ b/browser/devtools/shared/redux/middleware/wait-service.js
@@ -1,35 +1,35 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-const NAME = "@@service/waitUntil";
-
 /**
  * A middleware which acts like a service, because it is stateful
  * and "long-running" in the background. It provides the ability
  * for actions to install a function to be run once when a specific
  * condition is met by an action coming through the system. Think of
  * it as a thunk that blocks until the condition is met. Example:
  *
  * ```js
- * const services = { WAIT_UNTIL: require('waitUntilService').name };
+ * const services = { WAIT_UNTIL: require('wait-service').NAME };
  *
  * { type: services.WAIT_UNTIL,
  *   predicate: action => action.type === constants.ADD_ITEM,
  *   run: (dispatch, getState, action) => {
  *     // Do anything here. You only need to accept the arguments
  *     // if you need them. `action` is the action that satisfied
  *     // the predicate.
  *   }
  * }
  * ```
  */
+const NAME = exports.NAME = "@@service/waitUntil";
+
 function waitUntilService({ dispatch, getState }) {
   let pending = [];
 
   function checkPending(action) {
     let readyRequests = [];
     let stillPending = [];
 
     // Find the pending requests whose predicates are satisfied with
@@ -57,13 +57,9 @@ function waitUntilService({ dispatch, ge
       pending.push(action);
     }
     else {
       next(action);
       checkPending(action);
     }
   }
 }
-
-module.exports = {
-  service: waitUntilService,
-  name: NAME
-};
+exports.waitUntilService = waitUntilService;
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/redux/moz.build
@@ -0,0 +1,16 @@
+# -*- 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/.
+
+EXTRA_JS_MODULES.devtools.shared.redux += [
+    'create-store.js',
+    'reducers.js',
+]
+
+EXTRA_JS_MODULES.devtools.shared.redux.middleware += [
+    'middleware/log.js',
+    'middleware/thunk.js',
+    'middleware/wait-service.js',
+]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/redux/reducers.js
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { combineReducers } = require("devtools/shared/vendor/redux");
+
+/**
+ * Function that takes a hash of reducers, like `combineReducers`,
+ * and an `emit` function and returns a function to be used as a reducer
+ * for a Redux store. This allows all reducers defined here to receive
+ * a third argument, the `emit` function, for event-based subscriptions
+ * from within reducers.
+ *
+ * @param {Object} reducers
+ * @param {Function} emit
+ * @return {Function}
+ */
+function combineEmittingReducers (reducers, emit) {
+  // Wrap each reducer with a wrapper function that calls
+  // the reducer with a third argument, an `emit` function.
+  // Use this rather than a new custom top level reducer that would ultimately
+  // have to replicate redux's `combineReducers` so we only pass in correct state,
+  // the error checking, and other edge cases.
+  function wrapReduce (newReducers, key) {
+    newReducers[key] = (state, action) => reducers[key](state, action, emit);
+    return newReducers;
+  }
+
+  return combineReducers(Object.keys(reducers).reduce(wrapReduce, Object.create(null)));
+}
+
+exports.combineEmittingReducers = combineEmittingReducers;
rename from browser/devtools/shared/D3_LICENSE
rename to browser/devtools/shared/vendor/D3_LICENSE
rename from browser/devtools/webaudioeditor/lib/DAGRE_D3_LICENSE
rename to browser/devtools/shared/vendor/DAGRE_D3_LICENSE
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/vendor/REDUX_LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Dan Abramov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
rename from browser/devtools/shared/d3.js
rename to browser/devtools/shared/vendor/d3.js
rename from browser/devtools/webaudioeditor/lib/dagre-d3.js
rename to browser/devtools/shared/vendor/dagre-d3.js
rename from browser/devtools/shared/fluxify/moz.build
rename to browser/devtools/shared/vendor/moz.build
--- a/browser/devtools/shared/fluxify/moz.build
+++ b/browser/devtools/shared/vendor/moz.build
@@ -1,15 +1,15 @@
 # -*- 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/.
 
-EXTRA_JS_MODULES.devtools.shared.fluxify += [
-    'bindActionCreators.js',
-    'dispatcher.js',
-    'logMiddleware.js',
-    'thunkMiddleware.js',
-    'waitUntilService.js'
+EXTRA_JS_MODULES.devtools.shared.vendor += [
+    'react.js',
+    'redux.js',
 ]
 
-XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
\ No newline at end of file
+if CONFIG['DEBUG_JS_MODULES']:
+    EXTRA_JS_MODULES.devtools.shared.vendor += [
+        'content/react-dev.js'
+    ]
rename from browser/devtools/shared/content/react-dev.js
rename to browser/devtools/shared/vendor/react-dev.js
rename from browser/devtools/shared/content/react.js
rename to browser/devtools/shared/vendor/react.js
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/vendor/redux.js
@@ -0,0 +1,610 @@
+(function webpackUniversalModuleDefinition(root, factory) {
+	if(typeof exports === 'object' && typeof module === 'object')
+		module.exports = factory();
+	else if(typeof define === 'function' && define.amd)
+		define([], factory);
+	else if(typeof exports === 'object')
+		exports["Redux"] = factory();
+	else
+		root["Redux"] = factory();
+})(this, function() {
+return /******/ (function(modules) { // webpackBootstrap
+/******/ 	// The module cache
+/******/ 	var installedModules = {};
+
+/******/ 	// The require function
+/******/ 	function __webpack_require__(moduleId) {
+
+/******/ 		// Check if module is in cache
+/******/ 		if(installedModules[moduleId])
+/******/ 			return installedModules[moduleId].exports;
+
+/******/ 		// Create a new module (and put it into the cache)
+/******/ 		var module = installedModules[moduleId] = {
+/******/ 			exports: {},
+/******/ 			id: moduleId,
+/******/ 			loaded: false
+/******/ 		};
+
+/******/ 		// Execute the module function
+/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+
+/******/ 		// Flag the module as loaded
+/******/ 		module.loaded = true;
+
+/******/ 		// Return the exports of the module
+/******/ 		return module.exports;
+/******/ 	}
+
+
+/******/ 	// expose the modules object (__webpack_modules__)
+/******/ 	__webpack_require__.m = modules;
+
+/******/ 	// expose the module cache
+/******/ 	__webpack_require__.c = installedModules;
+
+/******/ 	// __webpack_public_path__
+/******/ 	__webpack_require__.p = "";
+
+/******/ 	// Load entry module and return exports
+/******/ 	return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	exports.__esModule = true;
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+	var _createStore = __webpack_require__(1);
+
+	var _createStore2 = _interopRequireDefault(_createStore);
+
+	var _utilsCombineReducers = __webpack_require__(7);
+
+	var _utilsCombineReducers2 = _interopRequireDefault(_utilsCombineReducers);
+
+	var _utilsBindActionCreators = __webpack_require__(6);
+
+	var _utilsBindActionCreators2 = _interopRequireDefault(_utilsBindActionCreators);
+
+	var _utilsApplyMiddleware = __webpack_require__(5);
+
+	var _utilsApplyMiddleware2 = _interopRequireDefault(_utilsApplyMiddleware);
+
+	var _utilsCompose = __webpack_require__(2);
+
+	var _utilsCompose2 = _interopRequireDefault(_utilsCompose);
+
+	exports.createStore = _createStore2['default'];
+	exports.combineReducers = _utilsCombineReducers2['default'];
+	exports.bindActionCreators = _utilsBindActionCreators2['default'];
+	exports.applyMiddleware = _utilsApplyMiddleware2['default'];
+	exports.compose = _utilsCompose2['default'];
+
+/***/ },
+/* 1 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	exports.__esModule = true;
+	exports['default'] = createStore;
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+	var _utilsIsPlainObject = __webpack_require__(3);
+
+	var _utilsIsPlainObject2 = _interopRequireDefault(_utilsIsPlainObject);
+
+	/**
+	 * These are private action types reserved by Redux.
+	 * For any unknown actions, you must return the current state.
+	 * If the current state is undefined, you must return the initial state.
+	 * Do not reference these action types directly in your code.
+	 */
+	var ActionTypes = {
+	  INIT: '@@redux/INIT'
+	};
+
+	exports.ActionTypes = ActionTypes;
+	/**
+	 * Creates a Redux store that holds the state tree.
+	 * The only way to change the data in the store is to call `dispatch()` on it.
+	 *
+	 * There should only be a single store in your app. To specify how different
+	 * parts of the state tree respond to actions, you may combine several reducers
+	 * into a single reducer function by using `combineReducers`.
+	 *
+	 * @param {Function} reducer A function that returns the next state tree, given
+	 * the current state tree and the action to handle.
+	 *
+	 * @param {any} [initialState] The initial state. You may optionally specify it
+	 * to hydrate the state from the server in universal apps, or to restore a
+	 * previously serialized user session.
+	 * If you use `combineReducers` to produce the root reducer function, this must be
+	 * an object with the same shape as `combineReducers` keys.
+	 *
+	 * @returns {Store} A Redux store that lets you read the state, dispatch actions
+	 * and subscribe to changes.
+	 */
+
+	function createStore(reducer, initialState) {
+	  if (typeof reducer !== 'function') {
+	    throw new Error('Expected the reducer to be a function.');
+	  }
+
+	  var currentReducer = reducer;
+	  var currentState = initialState;
+	  var listeners = [];
+	  var isDispatching = false;
+
+	  /**
+	   * Reads the state tree managed by the store.
+	   *
+	   * @returns {any} The current state tree of your application.
+	   */
+	  function getState() {
+	    return currentState;
+	  }
+
+	  /**
+	   * Adds a change listener. It will be called any time an action is dispatched,
+	   * and some part of the state tree may potentially have changed. You may then
+	   * call `getState()` to read the current state tree inside the callback.
+	   *
+	   * @param {Function} listener A callback to be invoked on every dispatch.
+	   * @returns {Function} A function to remove this change listener.
+	   */
+	  function subscribe(listener) {
+	    listeners.push(listener);
+
+	    return function unsubscribe() {
+	      var index = listeners.indexOf(listener);
+	      listeners.splice(index, 1);
+	    };
+	  }
+
+	  /**
+	   * Dispatches an action. It is the only way to trigger a state change.
+	   *
+	   * The `reducer` function, used to create the store, will be called with the
+	   * current state tree and the given `action`. Its return value will
+	   * be considered the **next** state of the tree, and the change listeners
+	   * will be notified.
+	   *
+	   * The base implementation only supports plain object actions. If you want to
+	   * dispatch a Promise, an Observable, a thunk, or something else, you need to
+	   * wrap your store creating function into the corresponding middleware. For
+	   * example, see the documentation for the `redux-thunk` package. Even the
+	   * middleware will eventually dispatch plain object actions using this method.
+	   *
+	   * @param {Object} action A plain object representing “what changed”. It is
+	   * a good idea to keep actions serializable so you can record and replay user
+	   * sessions, or use the time travelling `redux-devtools`.
+	   *
+	   * @returns {Object} For convenience, the same action object you dispatched.
+	   *
+	   * Note that, if you use a custom middleware, it may wrap `dispatch()` to
+	   * return something else (for example, a Promise you can await).
+	   */
+	  function dispatch(action) {
+	    if (!_utilsIsPlainObject2['default'](action)) {
+	      throw new Error('Actions must be plain objects. Use custom middleware for async actions.');
+	    }
+
+	    if (isDispatching) {
+	      throw new Error('Reducers may not dispatch actions.');
+	    }
+
+	    try {
+	      isDispatching = true;
+	      currentState = currentReducer(currentState, action);
+	    } finally {
+	      isDispatching = false;
+	    }
+
+	    listeners.slice().forEach(function (listener) {
+	      return listener();
+	    });
+	    return action;
+	  }
+
+	  /**
+	   * Replaces the reducer currently used by the store to calculate the state.
+	   *
+	   * You might need this if your app implements code splitting and you want to
+	   * load some of the reducers dynamically. You might also need this if you
+	   * implement a hot reloading mechanism for Redux.
+	   *
+	   * @param {Function} nextReducer The reducer for the store to use instead.
+	   * @returns {void}
+	   */
+	  function replaceReducer(nextReducer) {
+	    currentReducer = nextReducer;
+	    dispatch({ type: ActionTypes.INIT });
+	  }
+
+	  // When a store is created, an "INIT" action is dispatched so that every
+	  // reducer returns their initial state. This effectively populates
+	  // the initial state tree.
+	  dispatch({ type: ActionTypes.INIT });
+
+	  return {
+	    dispatch: dispatch,
+	    subscribe: subscribe,
+	    getState: getState,
+	    replaceReducer: replaceReducer
+	  };
+	}
+
+/***/ },
+/* 2 */
+/***/ function(module, exports) {
+
+	/**
+	 * Composes single-argument functions from right to left.
+	 *
+	 * @param {...Function} funcs The functions to compose.
+	 * @returns {Function} A function obtained by composing functions from right to
+	 * left. For example, compose(f, g, h) is identical to x => h(g(f(x))).
+	 */
+	"use strict";
+
+	exports.__esModule = true;
+	exports["default"] = compose;
+
+	function compose() {
+	  for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) {
+	    funcs[_key] = arguments[_key];
+	  }
+
+	  return function (arg) {
+	    return funcs.reduceRight(function (composed, f) {
+	      return f(composed);
+	    }, arg);
+	  };
+	}
+
+	module.exports = exports["default"];
+
+/***/ },
+/* 3 */
+/***/ function(module, exports) {
+
+	'use strict';
+
+	exports.__esModule = true;
+	exports['default'] = isPlainObject;
+	var fnToString = function fnToString(fn) {
+	  return Function.prototype.toString.call(fn);
+	};
+
+	/**
+	 * @param {any} obj The object to inspect.
+	 * @returns {boolean} True if the argument appears to be a plain object.
+	 */
+
+	function isPlainObject(obj) {
+	  if (!obj || typeof obj !== 'object') {
+	    return false;
+	  }
+
+	  var proto = typeof obj.constructor === 'function' ? Object.getPrototypeOf(obj) : Object.prototype;
+
+	  if (proto === null) {
+	    return true;
+	  }
+
+	  var constructor = proto.constructor;
+
+	  return typeof constructor === 'function' && constructor instanceof constructor && fnToString(constructor) === fnToString(Object);
+	}
+
+	module.exports = exports['default'];
+
+/***/ },
+/* 4 */
+/***/ function(module, exports) {
+
+	/**
+	 * Applies a function to every key-value pair inside an object.
+	 *
+	 * @param {Object} obj The source object.
+	 * @param {Function} fn The mapper function that receives the value and the key.
+	 * @returns {Object} A new object that contains the mapped values for the keys.
+	 */
+	"use strict";
+
+	exports.__esModule = true;
+	exports["default"] = mapValues;
+
+	function mapValues(obj, fn) {
+	  return Object.keys(obj).reduce(function (result, key) {
+	    result[key] = fn(obj[key], key);
+	    return result;
+	  }, {});
+	}
+
+	module.exports = exports["default"];
+
+/***/ },
+/* 5 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	exports.__esModule = true;
+
+	var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+	exports['default'] = applyMiddleware;
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+	var _compose = __webpack_require__(2);
+
+	var _compose2 = _interopRequireDefault(_compose);
+
+	/**
+	 * Creates a store enhancer that applies middleware to the dispatch method
+	 * of the Redux store. This is handy for a variety of tasks, such as expressing
+	 * asynchronous actions in a concise manner, or logging every action payload.
+	 *
+	 * See `redux-thunk` package as an example of the Redux middleware.
+	 *
+	 * Because middleware is potentially asynchronous, this should be the first
+	 * store enhancer in the composition chain.
+	 *
+	 * Note that each middleware will be given the `dispatch` and `getState` functions
+	 * as named arguments.
+	 *
+	 * @param {...Function} middlewares The middleware chain to be applied.
+	 * @returns {Function} A store enhancer applying the middleware.
+	 */
+
+	function applyMiddleware() {
+	  for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
+	    middlewares[_key] = arguments[_key];
+	  }
+
+	  return function (next) {
+	    return function (reducer, initialState) {
+	      var store = next(reducer, initialState);
+	      var _dispatch = store.dispatch;
+	      var chain = [];
+
+	      var middlewareAPI = {
+	        getState: store.getState,
+	        dispatch: function dispatch(action) {
+	          return _dispatch(action);
+	        }
+	      };
+	      chain = middlewares.map(function (middleware) {
+	        return middleware(middlewareAPI);
+	      });
+	      _dispatch = _compose2['default'].apply(undefined, chain)(store.dispatch);
+
+	      return _extends({}, store, {
+	        dispatch: _dispatch
+	      });
+	    };
+	  };
+	}
+
+	module.exports = exports['default'];
+
+/***/ },
+/* 6 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	exports.__esModule = true;
+	exports['default'] = bindActionCreators;
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+	var _utilsMapValues = __webpack_require__(4);
+
+	var _utilsMapValues2 = _interopRequireDefault(_utilsMapValues);
+
+	function bindActionCreator(actionCreator, dispatch) {
+	  return function () {
+	    return dispatch(actionCreator.apply(undefined, arguments));
+	  };
+	}
+
+	/**
+	 * Turns an object whose values are action creators, into an object with the
+	 * same keys, but with every function wrapped into a `dispatch` call so they
+	 * may be invoked directly. This is just a convenience method, as you can call
+	 * `store.dispatch(MyActionCreators.doSomething())` yourself just fine.
+	 *
+	 * For convenience, you can also pass a single function as the first argument,
+	 * and get a function in return.
+	 *
+	 * @param {Function|Object} actionCreators An object whose values are action
+	 * creator functions. One handy way to obtain it is to use ES6 `import * as`
+	 * syntax. You may also pass a single function.
+	 *
+	 * @param {Function} dispatch The `dispatch` function available on your Redux
+	 * store.
+	 *
+	 * @returns {Function|Object} The object mimicking the original object, but with
+	 * every action creator wrapped into the `dispatch` call. If you passed a
+	 * function as `actionCreators`, the return value will also be a single
+	 * function.
+	 */
+
+	function bindActionCreators(actionCreators, dispatch) {
+	  if (typeof actionCreators === 'function') {
+	    return bindActionCreator(actionCreators, dispatch);
+	  }
+
+	  if (typeof actionCreators !== 'object' || actionCreators == null) {
+	    // eslint-disable-line no-eq-null
+	    throw new Error('bindActionCreators expected an object or a function, instead received ' + typeof actionCreators + '. ' + 'Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?');
+	  }
+
+	  return _utilsMapValues2['default'](actionCreators, function (actionCreator) {
+	    return bindActionCreator(actionCreator, dispatch);
+	  });
+	}
+
+	module.exports = exports['default'];
+
+/***/ },
+/* 7 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	exports.__esModule = true;
+	exports['default'] = combineReducers;
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+	var _createStore = __webpack_require__(1);
+
+	var _utilsIsPlainObject = __webpack_require__(3);
+
+	var _utilsIsPlainObject2 = _interopRequireDefault(_utilsIsPlainObject);
+
+	var _utilsMapValues = __webpack_require__(4);
+
+	var _utilsMapValues2 = _interopRequireDefault(_utilsMapValues);
+
+	var _utilsPick = __webpack_require__(8);
+
+	var _utilsPick2 = _interopRequireDefault(_utilsPick);
+
+	/* eslint-disable no-console */
+
+	function getErrorMessage(key, action) {
+	  var actionType = action && action.type;
+	  var actionName = actionType && '"' + actionType.toString() + '"' || 'an action';
+
+	  return 'Reducer "' + key + '" returned undefined handling ' + actionName + '. ' + 'To ignore an action, you must explicitly return the previous state.';
+	}
+
+	function verifyStateShape(initialState, currentState) {
+	  var reducerKeys = Object.keys(currentState);
+
+	  if (reducerKeys.length === 0) {
+	    console.error('Store does not have a valid reducer. Make sure the argument passed ' + 'to combineReducers is an object whose values are reducers.');
+	    return;
+	  }
+
+	  if (!_utilsIsPlainObject2['default'](initialState)) {
+	    console.error('initialState has unexpected type of "' + ({}).toString.call(initialState).match(/\s([a-z|A-Z]+)/)[1] + '". Expected initialState to be an object with the following ' + ('keys: "' + reducerKeys.join('", "') + '"'));
+	    return;
+	  }
+
+	  var unexpectedKeys = Object.keys(initialState).filter(function (key) {
+	    return reducerKeys.indexOf(key) < 0;
+	  });
+
+	  if (unexpectedKeys.length > 0) {
+	    console.error('Unexpected ' + (unexpectedKeys.length > 1 ? 'keys' : 'key') + ' ' + ('"' + unexpectedKeys.join('", "') + '" in initialState will be ignored. ') + ('Expected to find one of the known reducer keys instead: "' + reducerKeys.join('", "') + '"'));
+	  }
+	}
+
+	/**
+	 * Turns an object whose values are different reducer functions, into a single
+	 * reducer function. It will call every child reducer, and gather their results
+	 * into a single state object, whose keys correspond to the keys of the passed
+	 * reducer functions.
+	 *
+	 * @param {Object} reducers An object whose values correspond to different
+	 * reducer functions that need to be combined into one. One handy way to obtain
+	 * it is to use ES6 `import * as reducers` syntax. The reducers may never return
+	 * undefined for any action. Instead, they should return their initial state
+	 * if the state passed to them was undefined, and the current state for any
+	 * unrecognized action.
+	 *
+	 * @returns {Function} A reducer function that invokes every reducer inside the
+	 * passed object, and builds a state object with the same shape.
+	 */
+
+	function combineReducers(reducers) {
+	  var finalReducers = _utilsPick2['default'](reducers, function (val) {
+	    return typeof val === 'function';
+	  });
+
+	  Object.keys(finalReducers).forEach(function (key) {
+	    var reducer = finalReducers[key];
+	    if (typeof reducer(undefined, { type: _createStore.ActionTypes.INIT }) === 'undefined') {
+	      throw new Error('Reducer "' + key + '" returned undefined during initialization. ' + 'If the state passed to the reducer is undefined, you must ' + 'explicitly return the initial state. The initial state may ' + 'not be undefined.');
+	    }
+
+	    var type = Math.random().toString(36).substring(7).split('').join('.');
+	    if (typeof reducer(undefined, { type: type }) === 'undefined') {
+	      throw new Error('Reducer "' + key + '" returned undefined when probed with a random type. ' + ('Don\'t try to handle ' + _createStore.ActionTypes.INIT + ' or other actions in "redux/*" ') + 'namespace. They are considered private. Instead, you must return the ' + 'current state for any unknown actions, unless it is undefined, ' + 'in which case you must return the initial state, regardless of the ' + 'action type. The initial state may not be undefined.');
+	    }
+	  });
+
+	  var defaultState = _utilsMapValues2['default'](finalReducers, function () {
+	    return undefined;
+	  });
+	  var stateShapeVerified;
+
+	  return function combination(state, action) {
+	    if (state === undefined) state = defaultState;
+
+	    var finalState = _utilsMapValues2['default'](finalReducers, function (reducer, key) {
+	      var newState = reducer(state[key], action);
+	      if (typeof newState === 'undefined') {
+	        throw new Error(getErrorMessage(key, action));
+	      }
+	      return newState;
+	    });
+
+	    if (true) {
+	      if (!stateShapeVerified) {
+	        verifyStateShape(state, finalState);
+	        stateShapeVerified = true;
+	      }
+	    }
+
+	    return finalState;
+	  };
+	}
+
+	module.exports = exports['default'];
+
+/***/ },
+/* 8 */
+/***/ function(module, exports) {
+
+	/**
+	 * Picks key-value pairs from an object where values satisfy a predicate.
+	 *
+	 * @param {Object} obj The object to pick from.
+	 * @param {Function} fn The predicate the values must satisfy to be copied.
+	 * @returns {Object} The object with the values that satisfied the predicate.
+	 */
+	"use strict";
+
+	exports.__esModule = true;
+	exports["default"] = pick;
+
+	function pick(obj, fn) {
+	  return Object.keys(obj).reduce(function (result, key) {
+	    if (fn(obj[key])) {
+	      result[key] = obj[key];
+	    }
+	    return result;
+	  }, {});
+	}
+
+	module.exports = exports["default"];
+
+/***/ }
+/******/ ])
+});
+;
\ No newline at end of file
--- a/browser/devtools/webaudioeditor/webaudioeditor.xul
+++ b/browser/devtools/webaudioeditor/webaudioeditor.xul
@@ -13,17 +13,17 @@
 ]>
 
 <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"/>
 
   <script type="application/javascript" src="chrome://browser/content/devtools/d3.js"/>
-  <script type="application/javascript" src="dagre-d3.js"/>
+  <script type="application/javascript" src="chrome://browser/content/devtools/dagre-d3.js"/>
   <script type="application/javascript" src="webaudioeditor/includes.js"/>
   <script type="application/javascript" src="webaudioeditor/models.js"/>
   <script type="application/javascript" src="webaudioeditor/controller.js"/>
   <script type="application/javascript" src="webaudioeditor/views/utils.js"/>
   <script type="application/javascript" src="webaudioeditor/views/context.js"/>
   <script type="application/javascript" src="webaudioeditor/views/inspector.js"/>
   <script type="application/javascript" src="webaudioeditor/views/properties.js"/>
   <script type="application/javascript" src="webaudioeditor/views/automation.js"/>
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -466,16 +466,21 @@ These should match what Safari and other
      searchFor.label and searchWith.label. This string will be used instead of
      them when the user has not typed any keyword. -->
 <!ENTITY searchWithHeader.label       "Search with:">
 <!-- LOCALIZATION NOTE (changeSearchSettings.button):
      This string won't wrap, so if the translated string is longer,
      consider translating it as if it said only "Search Settings". -->
 <!ENTITY changeSearchSettings.button  "Change Search Settings">
 
+<!ENTITY searchInNewTab.label         "Search in New Tab">
+<!ENTITY searchInNewTab.accesskey     "T">
+<!ENTITY searchSetAsDefault.label     "Set As Default Search Engine">
+<!ENTITY searchSetAsDefault.accesskey "D">
+
 <!ENTITY tabView.commandkey           "e">
 
 <!ENTITY openLinkCmdInTab.label       "Open Link in New Tab">
 <!ENTITY openLinkCmdInTab.accesskey   "T">
 <!ENTITY openLinkCmd.label            "Open Link in New Window">
 <!ENTITY openLinkCmd.accesskey        "W">
 <!ENTITY openLinkInPrivateWindowCmd.label "Open Link in New Private Window">
 <!ENTITY openLinkInPrivateWindowCmd.accesskey "P">
--- a/browser/themes/shared/urlbarSearchSuggestionsNotification.inc.css
+++ b/browser/themes/shared/urlbarSearchSuggestionsNotification.inc.css
@@ -1,10 +1,11 @@
 #PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] {
   border-bottom: 1px solid hsla(210, 4%, 10%, 0.14);
+  color: -moz-FieldText;
   background-color: hsla(210, 4%, 10%, 0.07);
   padding: 6px 0;
   -moz-padding-start: 44px;
   -moz-padding-end: 6px;
   background-image: url("chrome://browser/skin/info.svg");
   background-clip: padding-box;
   background-position: 20px center;
   background-repeat: no-repeat;
--- a/dom/base/Attr.h
+++ b/dom/base/Attr.h
@@ -93,18 +93,16 @@ public:
   bool Specified() const;
 
   // XPCOM GetNamespaceURI() is OK
   // XPCOM GetPrefix() is OK
   // XPCOM GetLocalName() is OK
 
   Element* GetOwnerElement(ErrorResult& aRv);
 
-  bool IsNSAware() const { return mNsAware; }
-
 protected:
   virtual Element* GetNameSpaceElement() override
   {
     return GetElement();
   }
 
   static bool sInitialized;
 
--- a/dom/base/nsDOMAttributeMap.cpp
+++ b/dom/base/nsDOMAttributeMap.cpp
@@ -10,17 +10,16 @@
 
 #include "nsDOMAttributeMap.h"
 
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/dom/Attr.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/NamedNodeMapBinding.h"
 #include "mozilla/dom/NodeInfoInlines.h"
-#include "mozilla/Telemetry.h"
 #include "nsAttrName.h"
 #include "nsContentUtils.h"
 #include "nsError.h"
 #include "nsIContentInlines.h"
 #include "nsIDocument.h"
 #include "nsNameSpaceManager.h"
 #include "nsNodeInfoManager.h"
 #include "nsUnicharUtils.h"
@@ -267,31 +266,16 @@ nsDOMAttributeMap::SetNamedItemNS(nsIDOM
 
 already_AddRefed<Attr>
 nsDOMAttributeMap::SetNamedItemInternal(Attr& aAttr,
                                         bool aWithNS,
                                         ErrorResult& aError)
 {
   NS_ENSURE_TRUE(mContent, nullptr);
 
-  if (!aAttr.IsNSAware() &&
-      !mContent->IsHTMLElement() &&
-      aAttr.OwnerDoc()->IsHTMLDocument()) {
-    // Check whether we have a non-lowercase name, and if so log some telemetry.
-    // We check whether the attr's document is HTML _before_ the adopt we do
-    // below, because we're trying to figure out whether we could lowercase the
-    // attr name at creation time.  We restrict this to the !IsNSAware() case,
-    // because we only care about Attr nodes created via createAttribute.
-    nsIAtom* nameAtom = aAttr.NodeInfo()->NameAtom();
-    if (nsContentUtils::StringContainsASCIIUpper(nsDependentAtomString(nameAtom))) {
-        Telemetry::Accumulate(Telemetry::NONLOWERCASE_NONHTML_ATTR_NODE_SET,
-                              true);
-      }
-  }
-
   // XXX should check same-origin between mContent and aAttr however
   // nsContentUtils::CheckSameOrigin can't deal with attributenodes yet
 
   // Check that attribute is not owned by somebody else
   nsDOMAttributeMap* owner = aAttr.GetMap();
   if (owner) {
     if (owner != this) {
       aError.Throw(NS_ERROR_DOM_INUSE_ATTRIBUTE_ERR);
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -5727,18 +5727,25 @@ nsIDocument::CreateAttribute(const nsASt
   }
 
   nsresult res = nsContentUtils::CheckQName(aName, false);
   if (NS_FAILED(res)) {
     rv.Throw(res);
     return nullptr;
   }
 
+  nsAutoString name;
+  if (IsHTMLDocument()) {
+    nsContentUtils::ASCIIToLower(aName, name);
+  } else {
+    name = aName;
+  }
+
   nsRefPtr<mozilla::dom::NodeInfo> nodeInfo;
-  res = mNodeInfoManager->GetNodeInfo(aName, nullptr, kNameSpaceID_None,
+  res = mNodeInfoManager->GetNodeInfo(name, nullptr, kNameSpaceID_None,
                                       nsIDOMNode::ATTRIBUTE_NODE,
                                       getter_AddRefs(nodeInfo));
   if (NS_FAILED(res)) {
     rv.Throw(res);
     return nullptr;
   }
 
   nsRefPtr<Attr> attribute = new Attr(nullptr, nodeInfo.forget(),
--- a/dom/base/nsXMLHttpRequest.cpp
+++ b/dom/base/nsXMLHttpRequest.cpp
@@ -3493,17 +3493,17 @@ nsXMLHttpRequest::OnProgress(nsIRequest 
     if (lengthComputable) {
       int64_t headerSize = aProgressMax - mUploadTotal;
       loaded -= headerSize;
     }
     mUploadLengthComputable = lengthComputable;
     mUploadTransferred = loaded;
     mProgressSinceLastProgressEvent = true;
 
-    MaybeDispatchProgressEvents(false);
+    MaybeDispatchProgressEvents((mUploadTransferred == mUploadTotal));
   } else {
     mLoadLengthComputable = lengthComputable;
     mLoadTotal = lengthComputable ? aProgressMax : 0;
     mLoadTransferred = aProgress;
     // Don't dispatch progress events here. OnDataAvailable will take care
     // of that.
   }
 
--- a/dom/base/test/chrome/test_bug682305.html
+++ b/dom/base/test/chrome/test_bug682305.html
@@ -31,16 +31,18 @@ var Cc = Components.classes;
 var Ci = Components.interfaces;
 var Cr = Components.results;
 var Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 var SimpleURI = Cc["@mozilla.org/network/simple-uri;1"];
 var ios = Cc["@mozilla.org/network/io-service;1"]
             .getService(Ci.nsIIOService);
+var contentSecManager = Cc["@mozilla.org/contentsecuritymanager;1"]
+                          .getService(Ci.nsIContentSecurityManager);
 
 var PROTOCOL_SCHEME = "jsproto";
 
 
 function CustomChannel(uri, loadInfo) {
 	this.URI = this.originalURI = uri;
   this.loadInfo = loadInfo;
 }
@@ -65,22 +67,32 @@ CustomChannel.prototype = {
     } catch(e) {}
     try {
       listener.onDataAvailable(this, context, stream, 0, stream.available());
     } catch(e) {}
     try {
       listener.onStopRequest(this, context, Cr.NS_OK);
     } catch(e) {}
   },
+  asyncOpen2: function(listener) {
+    // throws an error if security checks fail
+    var outListener = contentSecManager.performSecurityCheck(this, listener);
+    return this.asyncOpen(outListener, null);
+  },
   open: function() {
     let data = "bar";
     let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
     stream.setData(data, data.length);
     return stream;
   },
+  open2: function() {
+    // throws an error if security checks fail
+    contentSecManager.performSecurityCheck(this, null);
+    return this.open();
+  },
   isPending: function() {
     return false;
   },
   cancel: function() {
     throw Cr.NS_ERROR_NOT_IMPLEMENTED;
   },
   suspend: function() {
     throw Cr.NS_ERROR_NOT_IMPLEMENTED;
--- a/dom/base/test/test_bug469304.html
+++ b/dom/base/test/test_bug469304.html
@@ -30,17 +30,17 @@ function testGetAttribute() {
   is(document.body.getAttribute('aa'), "UPPERCASE", "Wrong value (1)");
   is(document.body.getAttribute('AA'), "UPPERCASE", "Wrong value (2)");
 
   var s = "";
   for (var i = 0; i < document.body.attributes.length; ++i) {
     s += document.body.attributes[i].nodeName + "=" +
          document.body.attributes[i].nodeValue;
   }
-  is(s, "AA=UPPERCASE", "Wrong attribute!");
+  is(s, "aa=UPPERCASE", "Wrong attribute!");
 
   is(document.body.getAttributeNode("aa"), document.body.getAttributeNode("AA"),
      "Wrong node!");
 
   document.body.getAttributeNode("AA").nodeValue = "FOO";
   is(document.body.getAttribute("AA"), "FOO", "Wrong value!");
 
   document.body.removeAttributeNode(document.body.getAttributeNode("AA"));
@@ -94,56 +94,56 @@ function testSetAttributeNodeKeepsRef(di
   a.nodeValue = "0";
   div.setAttributeNode(a);
   // Mutate the attribute node.
   a.nodeValue = "1";
   return div.getAttribute("attrib_name");
 }
 is(testSetAttributeNodeKeepsRef(), "1", "(4)");
 
-function testAttribNodeNamePreservesCase() {
+function testAttribNodeNameFoldsCase() {
   var div = document.createElement('div');
   var a = div.ownerDocument.createAttribute("A");
   a.nodeValue = "x";
   div.setAttributeNode(a);
   var result = [ a.name, a.nodeName ];
   return result.join(",");
 }
-is(testAttribNodeNamePreservesCase(), "A,A", "(5)");
+is(testAttribNodeNameFoldsCase(), "a,a", "(5)");
 
-function testAttribNodeNamePreservesCaseGetNode() {
+function testAttribNodeNameFoldsCaseGetNode() {
   var body = document.body;
   var a = body.ownerDocument.createAttribute("A");
   a.nodeValue = "x";
   body.setAttributeNode(a);
   a = document.body.getAttributeNode("A");
   if (!a)
       return "FAIL";
   var result = [ a.name, a.nodeName ];
   return result.join(",");
 }
-is(testAttribNodeNamePreservesCaseGetNode(), "A,A", "(6)");
+is(testAttribNodeNameFoldsCaseGetNode(), "a,a", "(6)");
 
-function testAttribNodeNamePreservesCaseGetNode2() {
+function testAttribNodeNameFoldsCaseGetNode2() {
   var body = document.body;
   var a = body.ownerDocument.createAttribute("B");
   a.nodeValue = "x";
   body.setAttributeNode(a);
   a = document.body.getAttributeNode("B");
   if (!a)
       return "FAIL";
   // Now create node second time
   a = body.ownerDocument.createAttribute("B");
   a.nodeValue = "x";
   body.setAttributeNode(a);
   a = document.body.getAttributeNode("B");
   var result = [ a.name, a.nodeName ];
   return result.join(",");
 }
-is(testAttribNodeNamePreservesCaseGetNode2(), "B,B", "(7)");
+is(testAttribNodeNameFoldsCaseGetNode2(), "b,b", "(7)");
 
 function testAttribNodeNameGetMutate() {
   var body = document.body;
   var a = body.ownerDocument.createAttribute("c");
   a.nodeValue = "0";
   body.setAttributeNode(a);
   a = document.body.getAttributeNode("c");
   a.value = "1";
@@ -154,19 +154,19 @@ is(testAttribNodeNameGetMutate(), "1", "
 
 var node = document.createElement("div");
 var attrib = document.createAttribute("myAttrib");
 attrib.nodeValue = "XXX";
 node.setAttributeNode(attrib);
 // Note, this is different to what WebKit does
 is((new XMLSerializer).serializeToString(node),
    "<div xmlns=\"http://www.w3.org/1999/xhtml\" myattrib=\"XXX\"></div>", "(9)");
-is(node.getAttributeNode('myAttrib').name, "myAttrib", "(10)");
-is(node.getAttributeNode('myattrib').name, "myAttrib", "(11)");
-is(attrib.name, "myAttrib", "(12)");
+is(node.getAttributeNode('myAttrib').name, "myattrib", "(10)");
+is(node.getAttributeNode('myattrib').name, "myattrib", "(11)");
+is(attrib.name, "myattrib", "(12)");
 
 var o = document.createElement("div");
 o.setAttribute("myAttrib","htmlattr");
 o.setAttributeNS("","myAttrib","123");
 is(o.getAttributeNodeNS("","myAttrib").nodeName, "myAttrib", "nodeName should be case-sensitive.");
 is(o.getAttributeNode("myAttrib").nodeName, "myattrib", "nodeName shouldn't be case-sensitive.");
 is(o.getAttributeNodeNS("","myAttrib").value, "123", "Should have got the case-sensitive attribute.");
 is(o.attributes.length, 2, "Should have two attributes.");
--- a/dom/events/Event.cpp
+++ b/dom/events/Event.cpp
@@ -1000,17 +1000,17 @@ Event::GetClientCoords(nsPresContext* aP
 
 // static
 CSSIntPoint
 Event::GetOffsetCoords(nsPresContext* aPresContext,
                        WidgetEvent* aEvent,
                        LayoutDeviceIntPoint aPoint,
                        CSSIntPoint aDefaultPoint)
 {
-  if (!aEvent->mFlags.mIsBeingDispatched) {
+  if (!aEvent->target) {
     return GetPageCoords(aPresContext, aEvent, aPoint, aDefaultPoint);
   }
   nsCOMPtr<nsIContent> content = do_QueryInterface(aEvent->target);
   if (!content || !aPresContext) {
     return CSSIntPoint(0, 0);
   }
   nsCOMPtr<nsIPresShell> shell = aPresContext->GetPresShell();
   if (!shell) {
--- a/dom/events/test/test_offsetxy.html
+++ b/dom/events/test/test_offsetxy.html
@@ -15,59 +15,84 @@
 <div id="d3" style="display:none; position:absolute; top:100px; left:100px; width:100px; border:5px dotted black; height:100px"></div>
 <div id="d4" style="transform:scale(0); position:absolute; top:100px; left:100px; width:100px; border:5px dotted black; height:100px"></div>
 
 <pre id="test">
 <script type="application/javascript">
 
 var offsetX = -1, offsetY = -1;
 var ev = new MouseEvent("click", {clientX:110, clientY:110});
+is(ev.offsetX, 110);
+is(ev.offsetY, 110);
+is(ev.offsetX, ev.pageX);
+is(ev.offsetY, ev.pageY);
 d.addEventListener("click", function (event) {
   is(ev, event, "Event objects must match");
   offsetX = event.offsetX;
   offsetY = event.offsetY;
 });
 d.dispatchEvent(ev);
 is(offsetX, 5);
 is(offsetY, 5);
-is(ev.offsetX, ev.pageX);
-is(ev.offsetY, ev.pageY);
+is(ev.offsetX, 5);
+is(ev.offsetY, 5);
 
 var ev2 = new MouseEvent("click", {clientX:220, clientY:130});
+is(ev2.offsetX, 220);
+is(ev2.offsetY, 130);
+is(ev2.offsetX, ev2.pageX);
+is(ev2.offsetY, ev2.pageY);
 d2.addEventListener("click", function (event) {
   is(ev2, event, "Event objects must match");
   offsetX = event.offsetX;
   offsetY = event.offsetY;
 });
 d2.dispatchEvent(ev2);
 is(offsetX, 15);
 is(offsetY, 25);
-is(ev2.offsetX, ev2.pageX);
-is(ev2.offsetY, ev2.pageY);
+is(ev2.offsetX, 15);
+is(ev2.offsetY, 25);
 
 var ev3 = new MouseEvent("click", {clientX:110, clientY:110});
+is(ev3.offsetX, 110);
+is(ev3.offsetY, 110);
+is(ev3.offsetX, ev3.pageX);
+is(ev3.offsetY, ev3.pageY);
 d3.addEventListener("click", function (event) {
   is(ev3, event, "Event objects must match");
   offsetX = event.offsetX;
   offsetY = event.offsetY;
 });
 d3.dispatchEvent(ev3);
 is(offsetX, 0);
 is(offsetY, 0);
-is(ev3.offsetX, ev3.pageX);
-is(ev3.offsetY, ev3.pageY);
+is(ev3.offsetX, 0);
+is(ev3.offsetY, 0);
 
 var ev4 = new MouseEvent("click", {clientX:110, clientY:110});
+is(ev4.offsetX, 110);
+is(ev4.offsetY, 110);
+is(ev4.offsetX, ev4.pageX);
+is(ev4.offsetY, ev4.pageY);
 d4.addEventListener("click", function (event) {
   is(ev4, event, "Event objects must match");
   offsetX = event.offsetX;
   offsetY = event.offsetY;
 });
 d4.dispatchEvent(ev4);
 is(offsetX, 0);
 is(offsetY, 0);
-is(ev4.offsetX, ev4.pageX);
-is(ev4.offsetY, ev4.pageY);
+is(ev4.offsetX, 0);
+is(ev4.offsetY, 0);
 
+// Now redispatch ev4 to "d" to make sure that its offsetX gets updated
+// relative to the new target.  Have to set "ev" to "ev4", because the listener
+// on "d" expects to see "ev" as the event.
+ev = ev4;
+d.dispatchEvent(ev4);
+is(offsetX, 5);
+is(offsetY, 5);
+is(ev.offsetX, 5);
+is(ev.offsetY, 5);
 </script>
 </pre>
 </body>
 </html>
--- a/dom/html/HTMLCanvasElement.cpp
+++ b/dom/html/HTMLCanvasElement.cpp
@@ -485,45 +485,16 @@ HTMLCanvasElement::ToDataURL(const nsASt
   // do a trust check if this is a write-only canvas
   if (mWriteOnly && !nsContentUtils::IsCallerChrome()) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
   return ToDataURLImpl(aCx, aType, aParams, aDataURL);
 }
 
-// HTMLCanvasElement::mozFetchAsStream
-
-NS_IMETHODIMP
-HTMLCanvasElement::MozFetchAsStream(nsIInputStreamCallback *aCallback,
-                                    const nsAString& aType)
-{
-  if (!nsContentUtils::IsCallerChrome())
-    return NS_ERROR_FAILURE;
-
-  nsresult rv;
-  nsCOMPtr<nsIInputStream> inputData;
-
-  nsAutoString type(aType);
-  rv = ExtractData(type, EmptyString(), getter_AddRefs(inputData));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsCOMPtr<nsIAsyncInputStream> asyncData = do_QueryInterface(inputData, &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsCOMPtr<nsIThread> mainThread;
-  rv = NS_GetMainThread(getter_AddRefs(mainThread));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsCOMPtr<nsIInputStreamCallback> asyncCallback =
-    NS_NewInputStreamReadyEvent(aCallback, mainThread);
-
-  return asyncCallback->OnInputStreamReady(asyncData);
-}
-
 void
 HTMLCanvasElement::SetMozPrintCallback(PrintCallback* aCallback)
 {
   mPrintCallback = aCallback;
 }
 
 PrintCallback*
 HTMLCanvasElement::GetMozPrintCallback() const
--- a/dom/html/HTMLCanvasElement.h
+++ b/dom/html/HTMLCanvasElement.h
@@ -155,21 +155,16 @@ public:
                                       ErrorResult& aRv);
   already_AddRefed<nsISupports> MozGetIPCContext(const nsAString& aContextId,
                                                  ErrorResult& aRv)
   {
     nsCOMPtr<nsISupports> context;
     aRv = MozGetIPCContext(aContextId, getter_AddRefs(context));
     return context.forget();
   }
-  void MozFetchAsStream(nsIInputStreamCallback* aCallback,
-                        const nsAString& aType, ErrorResult& aRv)
-  {
-    aRv = MozFetchAsStream(aCallback, aType);
-  }
   PrintCallback* GetMozPrintCallback() const;
   void SetMozPrintCallback(PrintCallback* aCallback);
 
   already_AddRefed<CanvasCaptureMediaStream>
   CaptureStream(const Optional<double>& aFrameRate, ErrorResult& aRv);
 
   /**
    * Get the size in pixels of this canvas element
--- a/dom/interfaces/html/nsIDOMHTMLCanvasElement.idl
+++ b/dom/interfaces/html/nsIDOMHTMLCanvasElement.idl
@@ -14,17 +14,17 @@
  *
  * @status UNDER_DEVELOPMENT
  */
 
 interface nsIDOMBlob;
 interface nsIVariant;
 interface nsIInputStreamCallback;
 
-[uuid(2c984658-2e7c-4774-8ac5-cf1b39f8bec3)]
+[uuid(4e8f1316-b601-471d-8f44-3c650d91ee9b)]
 interface nsIDOMHTMLCanvasElement : nsISupports
 {
   attribute unsigned long width;
   attribute unsigned long height;
   attribute boolean mozOpaque;
 
   // Valid calls are:
   //  toDataURL();              -- defaults to image/png
@@ -38,15 +38,10 @@ interface nsIDOMHTMLCanvasElement : nsIS
   // mozGetAsFile(name);              -- defaults to image/png
   // mozGetAsFile(name, type);        -- uses given type
   // The return value is a File object.
   nsISupports mozGetAsFile(in DOMString name, [optional] in DOMString type);
 
   // A Mozilla-only extension to get a canvas context backed by double-buffered
   // shared memory. Only privileged callers can call this.
   nsISupports MozGetIPCContext(in DOMString contextId);
-
-  // A Mozilla-only extension that returns the canvas' image data as a data
-  // stream in the desired image format.
-  void mozFetchAsStream(in nsIInputStreamCallback callback,
-                                        [optional] in DOMString type);
 };
 
--- a/dom/interfaces/security/moz.build
+++ b/dom/interfaces/security/moz.build
@@ -1,12 +1,13 @@
 # -*- 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/.
 
 XPIDL_SOURCES += [
+    'nsIContentSecurityManager.idl',
     'nsIContentSecurityPolicy.idl'
 ]
 
 XPIDL_MODULE = 'dom_security'
 
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/security/nsIContentSecurityManager.idl
@@ -0,0 +1,43 @@
+/* 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 "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIStreamListener;
+
+/**
+ * nsIContentSecurityManager
+ * Describes an XPCOM component used to perform security checks
+ * right before opnening a channel.
+ */
+
+[scriptable, uuid(70eaa956-1077-41f6-bef8-d722cea31245)]
+interface nsIContentSecurityManager : nsISupports
+{
+  /**
+   * Checks whether a channel is allowed to access the given URI and
+   * whether the channel should be openend or should be blocked consulting
+   * internal security checks like Same Origin Policy, Content Security
+   * Policy, Mixed Content Blocker, etc.
+   *
+   * If security checks within performSecurityCheck fail, the function
+   * throws an exception.
+   *
+   * @param aChannel
+   *     The channel about to be openend
+   * @param aStreamListener
+   *     The Streamlistener of the channel potentially wrapped
+   *     into CORSListenerProxy.
+   * @return
+   *     The StreamListener of the channel wrapped into CORSListenerProxy.
+   *
+   * @throws NS_ERROR_DOM_BAD_URI
+   *     If accessing the URI is not allowed (e.g. prohibted by SOP)
+   * @throws NS_ERROR_CONTENT_BLOCKED
+   *     If any of the security policies (CSP, Mixed content) is violated
+   */
+   nsIStreamListener performSecurityCheck(in nsIChannel aChannel,
+                                          in nsIStreamListener aStreamListener);
+};
--- a/dom/media/MediaCache.cpp
+++ b/dom/media/MediaCache.cpp
@@ -1829,22 +1829,21 @@ MediaCacheStream::NotifyDataReceived(int
 
   // Notify in case there's a waiting reader
   // XXX it would be fairly easy to optimize things a lot more to
   // avoid waking up reader threads unnecessarily
   mon.NotifyAll();
 }
 
 void
-MediaCacheStream::FlushPartialBlockInternal(bool aNotifyAll)
+MediaCacheStream::FlushPartialBlockInternal(bool aNotifyAll,
+                                            ReentrantMonitorAutoEnter& aReentrantMonitor)
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
-  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
-
   int32_t blockOffset = int32_t(mChannelOffset%BLOCK_SIZE);
   if (blockOffset > 0) {
     CACHE_LOG(LogLevel::Debug,
               ("Stream %p writing partial block: [%d] bytes; "
                "mStreamOffset [%lld] mChannelOffset[%lld] mStreamLength [%lld] "
                "notifying: [%s]",
                this, blockOffset, mStreamOffset, mChannelOffset, mStreamLength,
                aNotifyAll ? "yes" : "no"));
@@ -1856,32 +1855,32 @@ MediaCacheStream::FlushPartialBlockInter
         mMetadataInPartialBlockBuffer ? MODE_METADATA : MODE_PLAYBACK);
   }
 
   // |mChannelOffset == 0| means download ends with no bytes received.
   // We should also wake up those readers who are waiting for data
   // that will never come.
   if ((blockOffset > 0 || mChannelOffset == 0) && aNotifyAll) {
     // Wake up readers who may be waiting for this data
-    mon.NotifyAll();
+    aReentrantMonitor.NotifyAll();
   }
 }
 
 void
 MediaCacheStream::FlushPartialBlock()
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
 
   // Write the current partial block to memory.
   // Note: This writes a full block, so if data is not at the end of the
   // stream, the decoder must subsequently choose correct start and end offsets
   // for reading/seeking.
-  FlushPartialBlockInternal(false);
+  FlushPartialBlockInternal(false, mon);
 
   gMediaCache->QueueUpdate();
 }
 
 void
 MediaCacheStream::NotifyDataEnded(nsresult aStatus)
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
@@ -1892,17 +1891,17 @@ MediaCacheStream::NotifyDataEnded(nsresu
     // Disconnect from other streams sharing our resource, since they
     // should continue trying to load. Our load might have been deliberately
     // canceled and that shouldn't affect other streams.
     mResourceID = gMediaCache->AllocateResourceID();
   }
 
   // It is prudent to update channel/cache status before calling
   // CacheClientNotifyDataEnded() which will read |mChannelEnded|.
-  FlushPartialBlockInternal(true);
+  FlushPartialBlockInternal(true, mon);
   mChannelEnded = true;
   gMediaCache->QueueUpdate();
 
   MediaCache::ResourceStreamIterator iter(mResourceID);
   while (MediaCacheStream* stream = iter.Next()) {
     if (NS_SUCCEEDED(aStatus)) {
       // We read the whole stream, so remember the true length
       stream->mStreamLength = mChannelOffset;
@@ -2457,23 +2456,23 @@ nsresult MediaCacheStream::GetCachedRang
   // Take the monitor, so that the cached data ranges can't grow while we're
   // trying to loop over them.
   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
 
   // We must be pinned while running this, otherwise the cached data ranges may
   // shrink while we're trying to loop over them.
   NS_ASSERTION(mPinCount > 0, "Must be pinned");
 
-  int64_t startOffset = GetNextCachedData(0);
+  int64_t startOffset = GetNextCachedDataInternal(0);
   while (startOffset >= 0) {
-    int64_t endOffset = GetCachedDataEnd(startOffset);
+    int64_t endOffset = GetCachedDataEndInternal(startOffset);
     NS_ASSERTION(startOffset < endOffset, "Buffered range must end after its start");
     // Bytes [startOffset..endOffset] are cached.
     aRanges.AppendElement(MediaByteRange(startOffset, endOffset));
-    startOffset = GetNextCachedData(endOffset);
+    startOffset = GetNextCachedDataInternal(endOffset);
     NS_ASSERTION(startOffset == -1 || startOffset > endOffset,
       "Must have advanced to start of next range, or hit end of stream");
   }
   return NS_OK;
 }
 
 } // namespace mozilla
 
--- a/dom/media/MediaCache.h
+++ b/dom/media/MediaCache.h
@@ -420,17 +420,17 @@ private:
   // or -1 if there is no such cached data.
   // This method assumes that the cache monitor is held and can be called on
   // any thread.
   int64_t GetNextCachedDataInternal(int64_t aOffset);
   // Writes |mPartialBlock| to disk.
   // Used by |NotifyDataEnded| and |FlushPartialBlock|.
   // If |aNotifyAll| is true, this function will wake up readers who may be
   // waiting on the media cache monitor. Called on the main thread only.
-  void FlushPartialBlockInternal(bool aNotify);
+  void FlushPartialBlockInternal(bool aNotify, ReentrantMonitorAutoEnter& aReentrantMonitor);
   // A helper function to do the work of closing the stream. Assumes
   // that the cache monitor is held. Main thread only.
   // aReentrantMonitor is the nsAutoReentrantMonitor wrapper holding the cache monitor.
   // This is used to NotifyAll to wake up threads that might be
   // blocked on reading from this stream.
   void CloseInternal(ReentrantMonitorAutoEnter& aReentrantMonitor);
   // Update mPrincipal given that data has been received from aPrincipal
   bool UpdatePrincipal(nsIPrincipal* aPrincipal);
--- a/dom/media/MediaRecorder.cpp
+++ b/dom/media/MediaRecorder.cpp
@@ -590,19 +590,27 @@ private:
       LOG(LogLevel::Debug, ("Session.InitEncoder failure, mRecorder is null %p", this));
       return;
     }
     // Allocate encoder and bind with union stream.
     // At this stage, the API doesn't allow UA to choose the output mimeType format.
 
     // Make sure the application has permission to assign AUDIO_3GPP
     if (mRecorder->mMimeType.EqualsLiteral(AUDIO_3GPP) && Check3gppPermission()) {
-      mEncoder = MediaEncoder::CreateEncoder(NS_LITERAL_STRING(AUDIO_3GPP), aTrackTypes);
+      mEncoder = MediaEncoder::CreateEncoder(NS_LITERAL_STRING(AUDIO_3GPP),
+                                             mRecorder->GetAudioBitrate(),
+                                             mRecorder->GetVideoBitrate(),
+                                             mRecorder->GetBitrate(),
+                                             aTrackTypes);
     } else {
-      mEncoder = MediaEncoder::CreateEncoder(NS_LITERAL_STRING(""), aTrackTypes);
+      mEncoder = MediaEncoder::CreateEncoder(NS_LITERAL_STRING(""),
+                                             mRecorder->GetAudioBitrate(),
+                                             mRecorder->GetVideoBitrate(),
+                                             mRecorder->GetBitrate(),
+                                             aTrackTypes);
     }
 
     if (!mEncoder) {
       LOG(LogLevel::Debug, ("Session.InitEncoder !mEncoder %p", this));
       DoSessionEndTask(NS_ERROR_ABORT);
       return;
     }
 
@@ -960,17 +968,17 @@ MediaRecorder::Constructor(const GlobalO
 {
   nsCOMPtr<nsPIDOMWindow> ownerWindow = do_QueryInterface(aGlobal.GetAsSupports());
   if (!ownerWindow) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   nsRefPtr<MediaRecorder> object = new MediaRecorder(aStream, ownerWindow);
-  object->SetMimeType(aInitDict.mMimeType);
+  object->SetOptions(aInitDict);
   return object.forget();
 }
 
 /* static */ already_AddRefed<MediaRecorder>
 MediaRecorder::Constructor(const GlobalObject& aGlobal,
                            AudioNode& aSrcAudioNode,
                            uint32_t aSrcOutput,
                            const MediaRecorderOptions& aInitDict,
@@ -996,20 +1004,43 @@ MediaRecorder::Constructor(const GlobalO
        aSrcOutput >= aSrcAudioNode.NumberOfOutputs()) {
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return nullptr;
   }
 
   nsRefPtr<MediaRecorder> object = new MediaRecorder(aSrcAudioNode,
                                                      aSrcOutput,
                                                      ownerWindow);
-  object->SetMimeType(aInitDict.mMimeType);
+  object->SetOptions(aInitDict);
   return object.forget();
 }
 
+void
+MediaRecorder::SetOptions(const MediaRecorderOptions& aInitDict)
+{
+  SetMimeType(aInitDict.mMimeType);
+  mAudioBitsPerSecond = aInitDict.mAudioBitsPerSecond.WasPassed() ?
+                        aInitDict.mAudioBitsPerSecond.Value() : 0;
+  mVideoBitsPerSecond = aInitDict.mVideoBitsPerSecond.WasPassed() ?
+                        aInitDict.mVideoBitsPerSecond.Value() : 0;
+  mBitsPerSecond = aInitDict.mBitsPerSecond.WasPassed() ?
+                   aInitDict.mBitsPerSecond.Value() : 0;
+  // We're not handling dynamic changes yet. Eventually we'll handle
+  // setting audio, video and/or total -- and anything that isn't set,
+  // we'll derive. Calculated versions require querying bitrates after
+  // the encoder is Init()ed. This happens only after data is
+  // available and thus requires dynamic changes.
+  //
+  // Until dynamic changes are supported, I prefer to be safe and err
+  // slightly high
+  if (aInitDict.mBitsPerSecond.WasPassed() && !aInitDict.mVideoBitsPerSecond.WasPassed()) {
+    mVideoBitsPerSecond = mBitsPerSecond;
+  }
+}
+
 nsresult
 MediaRecorder::CreateAndDispatchBlobEvent(already_AddRefed<nsIDOMBlob>&& aBlob)
 {
   MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
   if (!CheckPrincipal()) {
     // Media is not same-origin, don't allow the data out.
     nsRefPtr<nsIDOMBlob> blob = aBlob;
     return NS_ERROR_DOM_SECURITY_ERR;
--- a/dom/media/MediaRecorder.h
+++ b/dom/media/MediaRecorder.h
@@ -99,30 +99,34 @@ public:
   IMPL_EVENT_HANDLER(dataavailable)
   IMPL_EVENT_HANDLER(error)
   IMPL_EVENT_HANDLER(start)
   IMPL_EVENT_HANDLER(stop)
   IMPL_EVENT_HANDLER(warning)
 
   NS_DECL_NSIDOCUMENTACTIVITY
 
+  uint32_t GetAudioBitrate() { return mAudioBitsPerSecond; }
+  uint32_t GetVideoBitrate() { return mVideoBitsPerSecond; }
+  uint32_t GetBitrate() { return mBitsPerSecond; }
 protected:
   virtual ~MediaRecorder();
 
   MediaRecorder& operator = (const MediaRecorder& x) = delete;
   // Create dataavailable event with Blob data and it runs in main thread
   nsresult CreateAndDispatchBlobEvent(already_AddRefed<nsIDOMBlob>&& aBlob);
   // Creating a simple event to notify UA simple event.
   void DispatchSimpleEvent(const nsAString & aStr);
   // Creating a error event with message.
   void NotifyError(nsresult aRv);
   // Check if the recorder's principal is the subsume of mediaStream
   bool CheckPrincipal();
   // Set encoded MIME type.
   void SetMimeType(const nsString &aMimeType);
+  void SetOptions(const MediaRecorderOptions& aInitDict);
 
   MediaRecorder(const MediaRecorder& x) = delete; // prevent bad usage
   // Remove session pointer.
   void RemoveSession(Session* aSession);
   // Functions for Session to query input source info.
   MediaStream* GetSourceMediaStream();
   nsIPrincipal* GetSourcePrincipal();
   // DOM wrapper for source media stream. Will be null when input is audio node.
@@ -139,16 +143,19 @@ protected:
   // The current state of the MediaRecorder object.
   RecordingState mState;
   // Hold the sessions reference and clean it when the DestroyRunnable for a
   // session is running.
   nsTArray<nsRefPtr<Session> > mSessions;
   // It specifies the container format as well as the audio and video capture formats.
   nsString mMimeType;
 
+  uint32_t mAudioBitsPerSecond;
+  uint32_t mVideoBitsPerSecond;
+  uint32_t mBitsPerSecond;
 private:
   // Register MediaRecorder into Document to listen the activity changes.
   void RegisterActivityObserver();
   void UnRegisterActivityObserver();
 
   bool Check3gppPermission();
 };
 
--- a/dom/media/encoder/MediaEncoder.cpp
+++ b/dom/media/encoder/MediaEncoder.cpp
@@ -68,17 +68,19 @@ MediaEncoder::NotifyEvent(MediaStreamGra
   }
   if (mVideoEncoder) {
     mVideoEncoder->NotifyEvent(aGraph, event);
   }
 }
 
 /* static */
 already_AddRefed<MediaEncoder>
-MediaEncoder::CreateEncoder(const nsAString& aMIMEType, uint8_t aTrackTypes)
+MediaEncoder::CreateEncoder(const nsAString& aMIMEType, uint32_t aAudioBitrate,
+                            uint32_t aVideoBitrate, uint32_t aBitrate,
+                            uint8_t aTrackTypes)
 {
   if (!gMediaEncoderLog) {
     gMediaEncoderLog = PR_NewLogModule("MediaEncoder");
   }
   PROFILER_LABEL("MediaEncoder", "CreateEncoder",
     js::ProfileEntry::Category::OTHER);
 
   nsAutoPtr<ContainerWriter> writer;
@@ -139,18 +141,25 @@ MediaEncoder::CreateEncoder(const nsAStr
   }
   else {
     LOG(LogLevel::Error, ("Can not find any encoder to record this media stream"));
     return nullptr;
   }
   LOG(LogLevel::Debug, ("Create encoder result:a[%d] v[%d] w[%d] mimeType = %s.",
                       audioEncoder != nullptr, videoEncoder != nullptr,
                       writer != nullptr, mimeType.get()));
+  if (videoEncoder && aVideoBitrate != 0) {
+    videoEncoder->SetBitrate(aVideoBitrate);
+  }
+  if (audioEncoder && aAudioBitrate != 0) {
+    audioEncoder->SetBitrate(aAudioBitrate);
+  }
   encoder = new MediaEncoder(writer.forget(), audioEncoder.forget(),
-                             videoEncoder.forget(), mimeType);
+                             videoEncoder.forget(), mimeType, aAudioBitrate,
+                             aVideoBitrate, aBitrate);
   return encoder.forget();
 }
 
 /**
  * GetEncodedData() runs as a state machine, starting with mState set to
  * GET_METADDATA, the procedure should be as follow:
  *
  * While non-stop
--- a/dom/media/encoder/MediaEncoder.h
+++ b/dom/media/encoder/MediaEncoder.h
@@ -57,17 +57,20 @@ public :
     ENCODE_TRACK,
     ENCODE_DONE,
     ENCODE_ERROR,
   };
 
   MediaEncoder(ContainerWriter* aWriter,
                AudioTrackEncoder* aAudioEncoder,
                VideoTrackEncoder* aVideoEncoder,
-               const nsAString& aMIMEType)
+               const nsAString& aMIMEType,
+               uint32_t aAudioBitrate,
+               uint32_t aVideoBitrate,
+               uint32_t aBitrate)
     : mWriter(aWriter)
     , mAudioEncoder(aAudioEncoder)
     , mVideoEncoder(aVideoEncoder)
     , mStartTime(TimeStamp::Now())
     , mMIMEType(aMIMEType)
     , mSizeOfBuffer(0)
     , mState(MediaEncoder::ENCODE_METADDATA)
     , mShutdown(false)
@@ -91,16 +94,18 @@ public :
                            MediaStreamListener::MediaStreamGraphEvent event) override;
 
   /**
    * Creates an encoder with a given MIME type. Returns null if we are unable
    * to create the encoder. For now, default aMIMEType to "audio/ogg" and use
    * Ogg+Opus if it is empty.
    */
   static already_AddRefed<MediaEncoder> CreateEncoder(const nsAString& aMIMEType,
+                                                      uint32_t aAudioBitrate, uint32_t aVideoBitrate,
+                                                      uint32_t aBitrate,
                                                       uint8_t aTrackTypes = ContainerWriter::CREATE_AUDIO_TRACK);
   /**
    * Encodes the raw track data and returns the final container data. Assuming
    * it is called on a single worker thread. The buffer of container data is
    * allocated in ContainerWriter::GetContainerData(), and is appended to
    * aOutputBufs. aMIMEType is the valid mime-type of this returned container
    * data.
    */
--- a/dom/media/encoder/OpusTrackEncoder.cpp
+++ b/dom/media/encoder/OpusTrackEncoder.cpp
@@ -182,18 +182,23 @@ OpusTrackEncoder::Init(int aChannels, in
   }
   mSamplingRate = aSamplingRate;
   NS_ENSURE_TRUE(mSamplingRate > 0, NS_ERROR_FAILURE);
 
   int error = 0;
   mEncoder = opus_encoder_create(GetOutputSampleRate(), mChannels,
                                  OPUS_APPLICATION_AUDIO, &error);
 
+
   mInitialized = (error == OPUS_OK);
 
+  if (mAudioBitrate) {
+    opus_encoder_ctl(mEncoder, OPUS_SET_BITRATE(static_cast<int>(mAudioBitrate)));
+  }
+
   mReentrantMonitor.NotifyAll();
 
   return error == OPUS_OK ? NS_OK : NS_ERROR_FAILURE;
 }
 
 int
 OpusTrackEncoder::GetOutputSampleRate()
 {
--- a/dom/media/encoder/TrackEncoder.h
+++ b/dom/media/encoder/TrackEncoder.h
@@ -79,16 +79,18 @@ public:
    */
   void NotifyCancel()
   {
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     mCanceled = true;
     mReentrantMonitor.NotifyAll();
   }
 
+  virtual void SetBitrate(const uint32_t aBitrate) {}
+
 protected:
   /**
    * Notifies track encoder that we have reached the end of source stream, and
    * wakes up mReentrantMonitor if encoder is waiting for any source data.
    */
   virtual void NotifyEndOfStream() = 0;
 
   /**
@@ -136,16 +138,17 @@ protected:
 
 class AudioTrackEncoder : public TrackEncoder
 {
 public:
   AudioTrackEncoder()
     : TrackEncoder()
     , mChannels(0)
     , mSamplingRate(0)
+    , mAudioBitrate(0)
   {}
 
   virtual void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
                                         StreamTime aTrackOffset,
                                         uint32_t aTrackEvents,
                                         const MediaSegment& aQueuedMedia) override;
 
   template<typename T>
@@ -186,16 +189,20 @@ public:
    */
   static void DeInterleaveTrackData(AudioDataValue* aInput, int32_t aDuration,
                                     int32_t aChannels, AudioDataValue* aOutput);
   /**
   * Measure size of mRawSegment
   */
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
+  virtual void SetBitrate(const uint32_t aBitrate) override
+  {
+    mAudioBitrate = aBitrate;
+  }
 protected:
   /**
    * Number of samples per channel in a pcm buffer. This is also the value of
    * frame size required by audio encoder, and mReentrantMonitor will be
    * notified when at least this much data has been added to mRawSegment.
    */
   virtual int GetPacketDuration() { return 0; }
 
@@ -234,44 +241,51 @@ protected:
    * The sampling rate of source audio data.
    */
   int mSamplingRate;
 
   /**
    * A segment queue of audio track data, protected by mReentrantMonitor.
    */
   AudioSegment mRawSegment;
+
+  uint32_t mAudioBitrate;
 };
 
 class VideoTrackEncoder : public TrackEncoder
 {
 public:
   VideoTrackEncoder()
     : TrackEncoder()
     , mFrameWidth(0)
     , mFrameHeight(0)
     , mDisplayWidth(0)
     , mDisplayHeight(0)
     , mTrackRate(0)
     , mTotalFrameDuration(0)
+    , mVideoBitrate(0)
   {}
 
   /**
    * Notified by the same callback of MediaEncoder when it has received a track
    * change from MediaStreamGraph. Called on the MediaStreamGraph thread.
    */
   virtual void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
                                         StreamTime aTrackOffset,
                                         uint32_t aTrackEvents,
                                         const MediaSegment& aQueuedMedia) override;
   /**
   * Measure size of mRawSegment
   */
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
+  virtual void SetBitrate(const uint32_t aBitrate) override
+  {
+    mVideoBitrate = aBitrate;
+  }
 protected:
   /**
    * Initialized the video encoder. In order to collect the value of width and
    * height of source frames, this initialization is delayed until we have
    * received the first valid video frame from MediaStreamGraph;
    * mReentrantMonitor will be notified after it has successfully initialized,
    * and this method is called on the MediaStramGraph thread.
    */
@@ -327,13 +341,15 @@ protected:
    * subclasses.
    */
   VideoFrame mLastFrame;
 
   /**
    * A segment queue of audio track data, protected by mReentrantMonitor.
    */
   VideoSegment mRawSegment;
+
+  uint32_t mVideoBitrate;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/encoder/VP8TrackEncoder.cpp
+++ b/dom/media/encoder/VP8TrackEncoder.cpp
@@ -15,17 +15,17 @@
 
 namespace mozilla {
 
 PRLogModuleInfo* gVP8TrackEncoderLog;
 #define VP8LOG(msg, ...) MOZ_LOG(gVP8TrackEncoderLog, mozilla::LogLevel::Debug, \
                                   (msg, ##__VA_ARGS__))
 // Debug logging macro with object pointer and class name.
 
-#define DEFAULT_BITRATE 2500 // in kbit/s
+#define DEFAULT_BITRATE_BPS 2500000
 #define DEFAULT_ENCODE_FRAMERATE 30
 
 using namespace mozilla::layers;
 
 VP8TrackEncoder::VP8TrackEncoder()
   : VideoTrackEncoder()
   , mEncodedFrameDuration(0)
   , mEncodedTimestamp(0)
@@ -82,17 +82,19 @@ VP8TrackEncoder::Init(int32_t aWidth, in
   // (actual memory is not allocated).
   vpx_img_wrap(mVPXImageWrapper, VPX_IMG_FMT_I420,
                mFrameWidth, mFrameHeight, 1, nullptr);
 
   config.g_w = mFrameWidth;
   config.g_h = mFrameHeight;
   // TODO: Maybe we should have various aFrameRate bitrate pair for each devices?
   // or for different platform
-  config.rc_target_bitrate = DEFAULT_BITRATE; // in kbit/s
+
+  // rc_target_bitrate needs kbit/s
+  config.rc_target_bitrate = (mVideoBitrate != 0 ? mVideoBitrate : DEFAULT_BITRATE_BPS)/1000;
 
   // Setting the time base of the codec
   config.g_timebase.num = 1;
   config.g_timebase.den = mTrackRate;
 
   config.g_error_resilient = 0;
 
   config.g_lag_in_frames = 0; // 0- no frame lagging
--- a/dom/media/encoder/VorbisTrackEncoder.cpp
+++ b/dom/media/encoder/VorbisTrackEncoder.cpp
@@ -51,19 +51,22 @@ VorbisTrackEncoder::Init(int aChannels, 
   // This monitor is used to wake up other methods that are waiting for encoder
   // to be completely initialized.
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
   mChannels = aChannels;
   mSamplingRate = aSamplingRate;
 
   int ret = 0;
   vorbis_info_init(&mVorbisInfo);
+  double quality = mAudioBitrate ? (double)mAudioBitrate/aSamplingRate :
+                   BASE_QUALITY;
 
+  printf("quality %f \n", quality);
   ret = vorbis_encode_init_vbr(&mVorbisInfo, mChannels, mSamplingRate,
-                               BASE_QUALITY);
+                               quality);
 
   mInitialized = (ret == 0);
 
   if (mInitialized) {
     // Set up the analysis state and auxiliary encoding storage
     vorbis_analysis_init(&mVorbisDsp, &mVorbisInfo);
     vorbis_block_init(&mVorbisDsp, &mVorbisBlock);
   }
--- a/dom/media/test/manifest.js
+++ b/dom/media/test/manifest.js
@@ -121,16 +121,21 @@ var gClosingConnectionsTest = [
 ];
 
 // Used by any media recorder test. Need one test file per decoder backend
 // currently supported by the media encoder.
 var gMediaRecorderTests = [
   { name:"detodos.opus", type:"audio/ogg; codecs=opus", duration:2.9135 }
 ];
 
+// Used by video media recorder tests
+var gMediaRecorderVideoTests = [
+  { name:"seek.webm", type:"video/webm", width:320, height:240, duration:3.966 },
+];
+
 // These are files that we want to make sure we can play through.  We can
 // also check metadata.  Put files of the same type together in this list so if
 // something crashes we have some idea of which backend is responsible.
 // Used by test_playback, which expects no error event and one ended event.
 var gPlayTests = [
   // Test playback of a WebM file with vp9 video
   //{ name:"vp9.webm", type:"video/webm", duration:4 },
   { name:"vp9cake.webm", type:"video/webm", duration:7.966 },
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -658,16 +658,19 @@ skip-if = (toolkit == 'android' && proce
 [test_loop.html]
 skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_media_selection.html]
 skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_media_sniffer.html]
 skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_mediarecorder_avoid_recursion.html]
 tags=msg
+[test_mediarecorder_bitrate.html]
+skip-if = (toolkit == 'gonk' || android_version == '10') # B2G emulator is too slow to run this without timing out and Android doesn't like the seek.webm
+tags=msg
 [test_mediarecorder_creation.html]
 tags=msg capturestream
 [test_mediarecorder_creation_fail.html]
 tags=msg
 [test_mediarecorder_getencodeddata.html]
 tags=msg
 [test_mediarecorder_record_4ch_audiocontext.html]
 tags=msg
new file mode 100644
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_bitrate.html
@@ -0,0 +1,131 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test MediaRecorder Bitrate</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var manager = new MediaTestManager;
+var results = [];
+
+/**
+ * Starts a test on every media recorder file included to check that
+ * the bitrate control works
+ */
+function startTest(test, token) {
+  manager.started(token);
+  runTest(test, token, 1000000);
+  runTest(test, token, 100000);
+}
+
+function runTest(test, token, bitrate) {
+  var element = document.createElement('video');
+  var expectedMimeType = test.type.substring(0, test.type.indexOf(';'));
+
+  element.token = token;
+
+  element.src = test.name;
+  element.test = test;
+  element.stream = element.mozCaptureStreamUntilEnded();
+
+  var mediaRecorder = new MediaRecorder(element.stream , {videoBitsPerSecond: bitrate});
+  var onStopFired = false;
+  var onDataAvailableFired = false;
+  var encoded_size = 0;
+
+  mediaRecorder.onerror = function () {
+    ok(false, 'Unexpected onerror callback fired');
+  };
+
+  mediaRecorder.onwarning = function () {
+    ok(false, 'Unexpected onwarning callback fired');
+  };
+
+  // This handler verifies that only a single onstop event handler is fired.
+  mediaRecorder.onstop = function () {
+    if (onStopFired) {
+      ok(false, 'onstop unexpectedly fired more than once');
+    } else {
+      onStopFired = true;
+
+      // ondataavailable should always fire before onstop
+      if (onDataAvailableFired) {
+        ok(true, 'onstop fired after ondataavailable');
+        info("test " + test.name + " encoded@" + bitrate + "=" + encoded_size);
+        if (results[test.name]) {
+          var big, small, temp;
+          big = {};
+	  big.bitrate = bitrate;
+	  big.size = encoded_size;
+	  small = results[test.name];
+	  // Don't assume the order that these will finish in
+	  if (results[test.name].bitrate > bitrate) {
+	    temp = big;
+	    big = small;
+	    small = temp;
+	  }
+	  // Ensure there is a big enough difference in the encoded
+	  // sizes
+          ok(small.size*1.25 < big.size,
+	     test.name + ' encoded@' + big.bitrate + '=' + big.size +
+	     ' > encoded@' + small.bitrate + '=' + small.size);
+          manager.finished(token);
+        } else {
+	  results[test.name] = {};
+	  results[test.name].bitrate = bitrate;
+          results[test.name].size = encoded_size;
+        }
+      } else {
+        ok(false, 'onstop fired without an ondataavailable event first');
+      }
+    }
+  };
+
+  // This handler verifies that only a single ondataavailable event handler
+  // is fired with the blob generated having greater than zero size
+  // and a correct mime type.
+  mediaRecorder.ondataavailable = function (evt) {
+    if (onDataAvailableFired) {
+      ok(false, 'ondataavailable unexpectedly fired more than once');
+    } else {
+      onDataAvailableFired = true;
+
+      ok(evt instanceof BlobEvent,
+         'Events fired from ondataavailable should be BlobEvent');
+      is(evt.type, 'dataavailable',
+         'Event type should dataavailable');
+      ok(evt.data.size > 0,
+         'Blob data received should be greater than zero');
+      encoded_size = evt.data.size;
+
+      // onstop should not have fired before ondataavailable
+      if (onStopFired) {
+        ok(false, 'ondataavailable unexpectedly fired later than onstop');
+        manager.finished(token);
+      }
+    }
+  };
+
+  element.preload = "metadata";
+
+  element.onloadedmetadata = function () {
+    element.onloadedmetadata = null;
+    mediaRecorder.start();
+    is(mediaRecorder.state, 'recording',
+     'Media recorder should be recording');
+    is(mediaRecorder.stream, element.stream,
+     'Media recorder stream = element stream at the start of recording');
+
+    element.play();
+  }
+}
+
+manager.runTests(gMediaRecorderVideoTests, startTest);
+</script>
+</pre>
+</body>
+</html>
--- a/dom/notification/Notification.cpp
+++ b/dom/notification/Notification.cpp
@@ -705,16 +705,20 @@ Notification::PrefEnabled(JSContext* aCx
     return Preferences::GetBool("dom.webnotifications.enabled", false);
   }
 
   WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
   if (!workerPrivate) {
     return false;
   }
 
+  if (workerPrivate->IsServiceWorker()) {
+    return workerPrivate->DOMServiceWorkerNotificationEnabled();
+  }
+
   return workerPrivate->DOMWorkerNotificationEnabled();
 }
 
 // static
 bool
 Notification::IsGetEnabled(JSContext* aCx, JSObject* aObj)
 {
   return NS_IsMainThread();
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -1363,17 +1363,17 @@ Promise::Settle(JS::Handle<JS::Value> aV
         JS_ClearPendingException(cx);
       }
     } else {
       JS_ClearPendingException(cx);
     }
   }
 #endif
 
-  if (!mGlobal || mGlobal->IsDying()) {
+  if (mGlobal->IsDying()) {
     return;
   }
 
   mSettlementTimestamp = TimeStamp::Now();
 
   SetResult(aValue);
   SetState(aState);
 
--- a/dom/push/Push.js
+++ b/dom/push/Push.js
@@ -93,24 +93,30 @@ Push.prototype = {
     typeArray.appendElement(type, false);
 
     // create a nsIContentPermissionRequest
     let request = {
       types: typeArray,
       principal: principal,
       QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionRequest]),
       allow: function() {
+        let histogram = Services.telemetry.getHistogramById("PUSH_API_PERMISSION_GRANTED");
+        histogram.add();
         aAllowCallback();
       },
       cancel: function() {
+        let histogram = Services.telemetry.getHistogramById("PUSH_API_PERMISSION_DENIED");
+        histogram.add();
         aCancelCallback();
       },
       window: this._window
     };
 
+    let histogram = Services.telemetry.getHistogramById("PUSH_API_PERMISSION_REQUESTED");
+    histogram.add(1);
     // Using askPermission from nsIDOMWindowUtils that takes care of the
     // remoting if needed.
     let windowUtils = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
                           .getInterface(Ci.nsIDOMWindowUtils);
     windowUtils.askPermission(request);
   },
 
   getEndpointResponse: function(fn) {
@@ -154,16 +160,18 @@ Push.prototype = {
         }
       );
     }.bind(this));
     return p;
   },
 
   subscribe: function() {
     debug("subscribe()");
+    let histogram = Services.telemetry.getHistogramById("PUSH_API_USED");
+    histogram.add(true);
     return this.getEndpointResponse(this._client.subscribe.bind(this._client));
   },
 
   getSubscription: function() {
     debug("getSubscription()" + this._scope);
     return this.getEndpointResponse(this._client.getSubscription.bind(this._client));
   },
 
--- a/dom/push/PushRecord.jsm
+++ b/dom/push/PushRecord.jsm
@@ -40,16 +40,17 @@ const QUOTA_REFRESH_TRANSITIONS_SQL = [
 
 function PushRecord(props) {
   this.pushEndpoint = props.pushEndpoint;
   this.scope = props.scope;
   this.originAttributes = props.originAttributes;
   this.pushCount = props.pushCount || 0;
   this.lastPush = props.lastPush || 0;
   this.setQuota(props.quota);
+  this.ctime = (typeof props.ctime === "number") ? props.ctime : 0;
 }
 
 PushRecord.prototype = {
   setQuota(suggestedQuota) {
     this.quota = (!isNaN(suggestedQuota) && suggestedQuota >= 0) ?
                  suggestedQuota : prefs.get("maxQuotaPerSubscription");
   },
 
@@ -69,21 +70,27 @@ PushRecord.prototype = {
     if (lastVisit > this.lastPush) {
       // If the user visited the site since the last time we received a
       // notification, reset the quota.
       let daysElapsed = (Date.now() - lastVisit) / 24 / 60 / 60 / 1000;
       currentQuota = Math.min(
         Math.round(8 * Math.pow(daysElapsed, -0.8)),
         prefs.get("maxQuotaPerSubscription")
       );
+      Services.telemetry.getHistogramById("PUSH_API_QUOTA_RESET_TO").add(currentQuota - 1);
     } else {
       // The user hasn't visited the site since the last notification.
       currentQuota = this.quota;
     }
     this.quota = Math.max(currentQuota - 1, 0);
+    // We check for ctime > 0 to skip older records that did not have ctime.
+    if (this.isExpired() && this.ctime > 0) {
+      let duration = Date.now() - this.ctime;
+      Services.telemetry.getHistogramById("PUSH_API_QUOTA_EXPIRATION_TIME").add(duration / 1000);
+    }
   },
 
   receivedPush(lastVisit) {
     this.updateQuota(lastVisit);
     this.pushCount++;
     this.lastPush = Date.now();
   },
 
--- a/dom/push/PushService.jsm
+++ b/dom/push/PushService.jsm
@@ -47,16 +47,26 @@ const kCHILD_PROCESS_MESSAGES = ["Push:R
 
 const PUSH_SERVICE_UNINIT = 0;
 const PUSH_SERVICE_INIT = 1; // No serverURI
 const PUSH_SERVICE_ACTIVATING = 2;//activating db
 const PUSH_SERVICE_CONNECTION_DISABLE = 3;
 const PUSH_SERVICE_ACTIVE_OFFLINE = 4;
 const PUSH_SERVICE_RUNNING = 5;
 
+// Telemetry failure to send push notification to Service Worker reasons.
+// Key not found in local database.
+const kDROP_NOTIFICATION_REASON_KEY_NOT_FOUND = 0;
+// User cleared history.
+const kDROP_NOTIFICATION_REASON_NO_HISTORY = 1;
+// Version of message received not newer than previous one.
+const kDROP_NOTIFICATION_REASON_NO_VERSION_INCREMENT = 2;
+// Subscription has expired.
+const kDROP_NOTIFICATION_REASON_EXPIRED = 3;
+
 /**
  * State is change only in couple of functions:
  *   init - change state to PUSH_SERVICE_INIT if state was PUSH_SERVICE_UNINIT
  *   changeServerURL - change state to PUSH_SERVICE_ACTIVATING if serverURL
  *                     present or PUSH_SERVICE_INIT if not present.
  *   changeStateConnectionEnabledEvent - it is call on pref change or during
  *                                       the service activation and it can
  *                                       change state to
@@ -268,30 +278,30 @@ this.PushService = {
           .then(records => {
             records.forEach(record => {
               this._db.delete(record.keyID)
                 .then(_ => {
                   // courtesy, but don't establish a connection
                   // just for it
                   if (this._ws) {
                     debug("Had a connection, so telling the server");
-                    this._sendRequest("unregister", {channelID: record.channelID})
+                    this._sendUnregister({channelID: record.channelID})
                         .catch(function(e) {
                           debug("Unregister errored " + e);
                         });
                   }
                 }, err => {
                   debug("webapps-clear-data: " + record.scope +
                         " Could not delete entry " + record.channelID);
 
                   // courtesy, but don't establish a connection
                   // just for it
                   if (this._ws) {
                     debug("Had a connection, so telling the server");
-                    this._sendRequest("unregister", {channelID: record.channelID})
+                    this._sendUnregister({channelID: record.channelID})
                         .catch(function(e) {
                           debug("Unregister errored " + e);
                         });
                   }
                   throw "Database error";
                 });
             });
           }, _ => {
@@ -668,16 +678,17 @@ this.PushService = {
       record.scope
     );
 
     let data = {
       originAttributes: record.originAttributes,
       scope: record.scope
     };
 
+    Services.telemetry.getHistogramById("PUSH_API_NOTIFY_REGISTRATION_LOST").add();
     this._notifyListeners('pushsubscriptionchange', data);
   },
 
   _notifyListeners: function(name, data) {
     if (this._childListeners.length > 0) {
       // Try to send messages to all listeners, but remove any that fail since
       // the receiver is likely gone away.
       for (var i = this._childListeners.length - 1; i >= 0; --i) {
@@ -719,48 +730,61 @@ this.PushService = {
       .then(record => this._notifySubscriptionChangeObservers(record));
   },
 
   updateRecordAndNotifyApp: function(aKeyID, aUpdateFunc) {
     return this._db.update(aKeyID, aUpdateFunc)
       .then(record => this._notifySubscriptionChangeObservers(record));
   },
 
+  _recordDidNotNotify: function(reason) {
+    Services.telemetry.
+      getHistogramById("PUSH_API_NOTIFICATION_RECEIVED_BUT_DID_NOT_NOTIFY").
+      add(reason);
+  },
+
   /**
    * Dispatches an incoming message to a service worker, recalculating the
    * quota for the associated push registration. If the quota is exceeded,
    * the registration and message will be dropped, and the worker will not
    * be notified.
    *
    * @param {String} keyID The push registration ID.
    * @param {String} message The message contents.
    * @param {Function} updateFunc A function that receives the existing
    *  registration record as its argument, and returns a new record. If the
    *  function returns `null` or `undefined`, the record will not be updated.
    *  `PushServiceWebSocket` uses this to drop incoming updates with older
    *  versions.
    */
   receivedPushMessage: function(keyID, message, updateFunc) {
     debug("receivedPushMessage()");
+    Services.telemetry.getHistogramById("PUSH_API_NOTIFICATION_RECEIVED").add();
 
     let shouldNotify = false;
     return this.getByKeyID(keyID).then(record => {
       if (!record) {
+        this._recordDidNotNotify(kDROP_NOTIFICATION_REASON_KEY_NOT_FOUND);
         throw new Error("No record for key ID " + keyID);
       }
       return record.getLastVisit();
     }).then(lastVisit => {
       // As a special case, don't notify the service worker if the user
       // cleared their history.
       shouldNotify = isFinite(lastVisit);
+      if (!shouldNotify) {
+          this._recordDidNotNotify(kDROP_NOTIFICATION_REASON_NO_HISTORY);
+      }
       return this._db.update(keyID, record => {
         let newRecord = updateFunc(record);
         if (!newRecord) {
+          this._recordDidNotNotify(kDROP_NOTIFICATION_REASON_NO_VERSION_INCREMENT);
           return null;
         }
+        // FIXME(nsm): WHY IS expired checked here but then also checked in the next case?
         if (newRecord.isExpired()) {
           // Because `unregister` is advisory only, we can still receive messages
           // for stale registrations from the server.
           debug("receivedPushMessage: Ignoring update for expired key ID " + keyID);
           return null;
         }
         newRecord.receivedPush(lastVisit);
         return newRecord;
@@ -770,20 +794,21 @@ this.PushService = {
       if (!record) {
         return notified;
       }
 
       if (shouldNotify) {
         notified = this._notifyApp(record, message);
       }
       if (record.isExpired()) {
+        this._recordDidNotNotify(kDROP_NOTIFICATION_REASON_EXPIRED);
         // Drop the registration in the background. If the user returns to the
         // site, the service worker will be notified on the next `idle-daily`
         // event.
-        this._sendRequest("unregister", record).catch(error => {
+        this._sendUnregister(record).catch(error => {
           debug("receivedPushMessage: Unregister error: " + error);
         });
       }
       return notified;
     }).catch(error => {
       debug("receivedPushMessage: Error notifying app: " + error);
     });
   },
@@ -820,16 +845,17 @@ this.PushService = {
 
     // TODO data.
     let data = {
       payload: message,
       originAttributes: aPushRecord.originAttributes,
       scope: aPushRecord.scope
     };
 
+    Services.telemetry.getHistogramById("PUSH_API_NOTIFY").add();
     this._notifyListeners('push', data);
     return true;
   },
 
   getByKeyID: function(aKeyID) {
     return this._db.getByKeyID(aKeyID);
   },
 
@@ -848,16 +874,17 @@ this.PushService = {
 
   /**
    * Called on message from the child process. aPageRecord is an object sent by
    * navigator.push, identifying the sending page and other fields.
    */
   _registerWithServer: function(aPageRecord) {
     debug("registerWithServer()" + JSON.stringify(aPageRecord));
 
+    Services.telemetry.getHistogramById("PUSH_API_SUBSCRIBE_ATTEMPT").add();
     return this._sendRequest("register", aPageRecord)
       .then(record => this._onRegisterSuccess(record),
             err => this._onRegisterError(err))
       .then(record => {
         this._deletePendingRequest(aPageRecord);
         return record;
       }, err => {
         this._deletePendingRequest(aPageRecord);
@@ -891,40 +918,57 @@ this.PushService = {
         }
         return record;
       }, error => {
         debug("getByIdentifiers failed");
         throw error;
       });
   },
 
+  _sendUnregister: function(aRecord) {
+    Services.telemetry.getHistogramById("PUSH_API_UNSUBSCRIBE_ATTEMPT").add();
+    return this._sendRequest("unregister", aRecord).then(function(v) {
+      Services.telemetry.getHistogramById("PUSH_API_UNSUBSCRIBE_SUCCEEDED").add();
+      return v;
+    }).catch(function(e) {
+      Services.telemetry.getHistogramById("PUSH_API_UNSUBSCRIBE_FAILED").add();
+      return Promise.reject(e);
+    });
+  },
+
   /**
    * Exceptions thrown in _onRegisterSuccess are caught by the promise obtained
    * from _service.request, causing the promise to be rejected instead.
    */
   _onRegisterSuccess: function(aRecord) {
     debug("_onRegisterSuccess()");
 
     return this._db.put(aRecord)
+      .then(record => {
+        Services.telemetry.getHistogramById("PUSH_API_SUBSCRIBE_SUCCEEDED").add();
+        return record;
+      })
       .catch(error => {
+        Services.telemetry.getHistogramById("PUSH_API_SUBSCRIBE_FAILED").add()
         // Unable to save. Destroy the subscription in the background.
-        this._sendRequest("unregister", aRecord).catch(err => {
+        this._sendUnregister(aRecord).catch(err => {
           debug("_onRegisterSuccess: Error unregistering stale subscription" +
             err);
         });
         throw error;
       });
   },
 
   /**
    * Exceptions thrown in _onRegisterError are caught by the promise obtained
    * from _service.request, causing the promise to be rejected instead.
    */
   _onRegisterError: function(reply) {
     debug("_onRegisterError()");
+    Services.telemetry.getHistogramById("PUSH_API_SUBSCRIBE_FAILED").add()
     if (!reply.error) {
       debug("Called without valid error message!");
       throw "Registration error";
     }
     throw reply.error;
   },
 
   receiveMessage: function(aMessage) {
@@ -1038,17 +1082,17 @@ this.PushService = {
     return this._checkActivated()
       .then(_ => this._db.getByIdentifiers(aPageRecord))
       .then(record => {
         if (record === undefined) {
           return false;
         }
 
         return Promise.all([
-          this._sendRequest("unregister", record),
+          this._sendUnregister(record),
           this._db.delete(record.keyID),
         ]).then(() => true);
       });
   },
 
   unregister: function(aPageRecord, aMessageManager) {
     debug("unregister() " + JSON.stringify(aPageRecord));
 
--- a/dom/push/PushServiceHttp2.jsm
+++ b/dom/push/PushServiceHttp2.jsm
@@ -255,16 +255,17 @@ var SubscriptionListener = function(aSub
                                     aServerURI, aPushServiceHttp2) {
   debug("Creating a new subscription listener.");
   this._subInfo = aSubInfo;
   this._resolve = aResolve;
   this._reject = aReject;
   this._data = '';
   this._serverURI = aServerURI;
   this._service = aPushServiceHttp2;
+  this._ctime = Date.now();
 };
 
 SubscriptionListener.prototype = {
 
   onStartRequest: function(aRequest, aContext) {},
 
   onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) {
     debug("subscription listener onDataAvailable()");
@@ -357,18 +358,20 @@ SubscriptionListener.prototype = {
 
     let reply = new PushRecordHttp2({
       subscriptionUri: subscriptionUri,
       pushEndpoint: linkParserResult.pushEndpoint,
       pushReceiptEndpoint: linkParserResult.pushReceiptEndpoint,
       scope: this._subInfo.record.scope,
       originAttributes: this._subInfo.record.originAttributes,
       quota: this._subInfo.record.maxQuota,
+      ctime: Date.now(),
     });
 
+    Services.telemetry.getHistogramById("PUSH_API_SUBSCRIBE_HTTP2_TIME").add(Date.now() - this._ctime);
     this._resolve(reply);
   },
 };
 
 function retryAfterParser(aRequest) {
   var retryAfter = 0;
   try {
     var retryField = aRequest.getResponseHeader("retry-after");
--- a/dom/push/PushServiceWebSocket.jsm
+++ b/dom/push/PushServiceWebSocket.jsm
@@ -806,18 +806,20 @@ this.PushServiceWebSocket = {
 
       let record = new PushRecordWebSocket({
         channelID: reply.channelID,
         pushEndpoint: reply.pushEndpoint,
         scope: tmp.record.scope,
         originAttributes: tmp.record.originAttributes,
         version: null,
         quota: tmp.record.maxQuota,
+        ctime: Date.now(),
       });
       dump("PushWebSocket " +  JSON.stringify(record));
+      Services.telemetry.getHistogramById("PUSH_API_SUBSCRIBE_WS_TIME").add(Date.now() - tmp.ctime);
       tmp.resolve(record);
     } else {
       tmp.reject(reply);
     }
   },
 
   /**
    * Protocol handler invoked by server message.
--- a/dom/security/nsContentSecurityManager.cpp
+++ b/dom/security/nsContentSecurityManager.cpp
@@ -3,16 +3,18 @@
 #include "nsIStreamListener.h"
 #include "nsILoadInfo.h"
 #include "nsContentUtils.h"
 #include "nsCORSListenerProxy.h"
 #include "nsIStreamListener.h"
 
 #include "mozilla/dom/Element.h"
 
+NS_IMPL_ISUPPORTS(nsContentSecurityManager, nsIContentSecurityManager)
+
 nsresult
 ValidateSecurityFlags(nsILoadInfo* aLoadInfo)
 {
   nsSecurityFlags securityMode = aLoadInfo->GetSecurityMode();
 
   if (securityMode != nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS &&
       securityMode != nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED &&
       securityMode != nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS &&
@@ -340,8 +342,24 @@ nsContentSecurityManager::doContentSecur
 
   // Perform all ContentPolicy checks (MixedContent, CSP, ...)
   rv = DoContentSecurityChecks(finalChannelURI, loadInfo);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // all security checks passed - lets allow the load
   return NS_OK;
 }
+
+
+// ==== nsIContentSecurityManager implementation =====
+
+NS_IMETHODIMP
+nsContentSecurityManager::PerformSecurityCheck(nsIChannel* aChannel,
+                                               nsIStreamListener* aStreamListener,
+                                               nsIStreamListener** outStreamListener)
+{
+  nsCOMPtr<nsIStreamListener> inAndOutListener = aStreamListener;
+  nsresult rv = doContentSecurityCheck(aChannel, inAndOutListener);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  inAndOutListener.forget(outStreamListener);
+  return NS_OK;
+}
--- a/dom/security/nsContentSecurityManager.h
+++ b/dom/security/nsContentSecurityManager.h
@@ -2,23 +2,36 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsContentSecurityManager_h___
 #define nsContentSecurityManager_h___
 
+#include "nsIContentSecurityManager.h"
 #include "nsIChannel.h"
+
 class nsIStreamListener;
 
-class nsContentSecurityManager
+#define NS_CONTENTSECURITYMANAGER_CONTRACTID "@mozilla.org/contentsecuritymanager;1"
+// cdcc1ab8-3cea-4e6c-a294-a651fa35227f
+#define NS_CONTENTSECURITYMANAGER_CID \
+{ 0xcdcc1ab8, 0x3cea, 0x4e6c, \
+  { 0xa2, 0x94, 0xa6, 0x51, 0xfa, 0x35, 0x22, 0x7f } }
+
+class nsContentSecurityManager : public nsIContentSecurityManager
 {
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSICONTENTSECURITYMANAGER
+
+  nsContentSecurityManager() {}
+
+  static nsresult doContentSecurityCheck(nsIChannel* aChannel,
+                                         nsCOMPtr<nsIStreamListener>& aInAndOutListener);
+
 private:
-  nsContentSecurityManager() {}
   virtual ~nsContentSecurityManager() {}
 
-public:
-  static nsresult doContentSecurityCheck(nsIChannel* aChannel,
-                                         nsCOMPtr<nsIStreamListener>& aInAndOutListener);
 };
 
 #endif /* nsContentSecurityManager_h___ */
--- a/dom/webidl/HTMLCanvasElement.webidl
+++ b/dom/webidl/HTMLCanvasElement.webidl
@@ -35,18 +35,16 @@ interface HTMLCanvasElement : HTMLElemen
 // Mozilla specific bits
 partial interface HTMLCanvasElement {
   [Pure, SetterThrows]
            attribute boolean mozOpaque;
   [Throws]
   File mozGetAsFile(DOMString name, optional DOMString? type = null);
   [ChromeOnly, Throws]
   nsISupports? MozGetIPCContext(DOMString contextId);
-  [ChromeOnly]
-  void mozFetchAsStream(nsIInputStreamCallback callback, optional DOMString? type = null);
            attribute PrintCallback? mozPrintCallback;
 
   [Throws, UnsafeInPrerendering, Pref="canvas.capturestream.enabled"]
   CanvasCaptureMediaStream captureStream(optional double frameRate);
 };
 
 [ChromeOnly]
 interface MozCanvasPrintState
--- a/dom/webidl/MediaRecorder.webidl
+++ b/dom/webidl/MediaRecorder.webidl
@@ -46,9 +46,12 @@ interface MediaRecorder : EventTarget {
   void resume();
 
   [Throws]
   void requestData();
 };
 
 dictionary MediaRecorderOptions {
   DOMString mimeType = ""; // Default encoding mimeType.
+  unsigned long audioBitsPerSecond;
+  unsigned long videoBitsPerSecond;
+  unsigned long bitsPerSecond;
 };
--- a/dom/webidl/Notification.webidl
+++ b/dom/webidl/Notification.webidl
@@ -89,13 +89,13 @@ callback NotificationPermissionCallback 
 
 enum NotificationDirection {
   "auto",
   "ltr",
   "rtl"
 };
 
 partial interface ServiceWorkerRegistration {
-  [Throws]
+  [Throws, Func="mozilla::dom::ServiceWorkerNotificationAPIVisible"]
   Promise<void> showNotification(DOMString title, optional NotificationOptions options);
-  [Throws]
+  [Throws, Func="mozilla::dom::ServiceWorkerNotificationAPIVisible"]
   Promise<sequence<Notification>> getNotifications(optional GetNotificationOptions filter);
 };
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -156,16 +156,17 @@ static_assert(MAX_WORKERS_PER_DOMAIN >= 
 #define DUMP_CONTROLLED_BY_PREF 1
 #define PREF_DOM_WINDOW_DUMP_ENABLED "browser.dom.window.dump.enabled"
 #endif
 
 #define PREF_DOM_CACHES_ENABLED        "dom.caches.enabled"
 #define PREF_DOM_CACHES_TESTING_ENABLED "dom.caches.testing.enabled"
 #define PREF_WORKERS_PERFORMANCE_LOGGING_ENABLED "dom.performance.enable_user_timing_logging"
 #define PREF_DOM_WORKERNOTIFICATION_ENABLED  "dom.webnotifications.enabled"
+#define PREF_DOM_SERVICEWORKERNOTIFICATION_ENABLED  "dom.webnotifications.serviceworker.enabled"
 #define PREF_WORKERS_LATEST_JS_VERSION "dom.workers.latestJSVersion"
 #define PREF_INTL_ACCEPT_LANGUAGES     "intl.accept_languages"
 #define PREF_SERVICEWORKERS_ENABLED    "dom.serviceWorkers.enabled"
 #define PREF_SERVICEWORKERS_TESTING_ENABLED "dom.serviceWorkers.testing.enabled"
 #define PREF_INTERCEPTION_ENABLED      "dom.serviceWorkers.interception.enabled"
 #define PREF_INTERCEPTION_OPAQUE_ENABLED "dom.serviceWorkers.interception.opaque.enabled"
 #define PREF_PUSH_ENABLED              "dom.push.enabled"
 #define PREF_REQUESTCONTEXT_ENABLED    "dom.requestcontext.enabled"
@@ -1934,16 +1935,20 @@ RuntimeService::Init()
                                   PREF_DOM_CACHES_ENABLED,
                                   reinterpret_cast<void *>(WORKERPREF_DOM_CACHES))) ||
       NS_FAILED(Preferences::RegisterCallbackAndCall(
                                   WorkerPrefChanged,
                                   PREF_DOM_WORKERNOTIFICATION_ENABLED,
                                   reinterpret_cast<void *>(WORKERPREF_DOM_WORKERNOTIFICATION))) ||
       NS_FAILED(Preferences::RegisterCallbackAndCall(
                                   WorkerPrefChanged,
+                                  PREF_DOM_SERVICEWORKERNOTIFICATION_ENABLED,
+                                  reinterpret_cast<void *>(WORKERPREF_DOM_SERVICEWORKERNOTIFICATION))) ||
+      NS_FAILED(Preferences::RegisterCallbackAndCall(
+                                  WorkerPrefChanged,
                                   PREF_SERVICEWORKERS_ENABLED,
                                   reinterpret_cast<void *>(WORKERPREF_SERVICEWORKERS))) ||
       NS_FAILED(Preferences::RegisterCallbackAndCall(
                                   WorkerPrefChanged,
                                   PREF_INTERCEPTION_ENABLED,
                                   reinterpret_cast<void *>(WORKERPREF_INTERCEPTION_ENABLED))) ||
       NS_FAILED(Preferences::RegisterCallbackAndCall(
                                   WorkerPrefChanged,
@@ -2193,16 +2198,20 @@ RuntimeService::Cleanup()
                                   PREF_DOM_CACHES_ENABLED,
                                   reinterpret_cast<void *>(WORKERPREF_DOM_CACHES))) ||
         NS_FAILED(Preferences::UnregisterCallback(
                                   WorkerPrefChanged,
                                   PREF_DOM_WORKERNOTIFICATION_ENABLED,
                                   reinterpret_cast<void *>(WORKERPREF_DOM_WORKERNOTIFICATION))) ||
         NS_FAILED(Preferences::UnregisterCallback(
                                   WorkerPrefChanged,
+                                  PREF_DOM_SERVICEWORKERNOTIFICATION_ENABLED,
+                                  reinterpret_cast<void *>(WORKERPREF_DOM_SERVICEWORKERNOTIFICATION))) ||
+        NS_FAILED(Preferences::UnregisterCallback(
+                                  WorkerPrefChanged,
                                   PREF_PUSH_ENABLED,
                                   reinterpret_cast<void *>(WORKERPREF_PUSH))) ||
         NS_FAILED(Preferences::UnregisterCallback(
                                   WorkerPrefChanged,
                                   PREF_REQUESTCONTEXT_ENABLED,
                                   reinterpret_cast<void *>(WORKERPREF_REQUESTCONTEXT))) ||
 #if DUMP_CONTROLLED_BY_PREF
         NS_FAILED(Preferences::UnregisterCallback(
@@ -2749,16 +2758,17 @@ RuntimeService::WorkerPrefChanged(const 
 
   const WorkerPreference key =
     static_cast<WorkerPreference>(reinterpret_cast<uintptr_t>(aClosure));
 
   switch (key) {
     case WORKERPREF_DOM_CACHES:
     case WORKERPREF_DOM_CACHES_TESTING:
     case WORKERPREF_DOM_WORKERNOTIFICATION:
+    case WORKERPREF_DOM_SERVICEWORKERNOTIFICATION:
     case WORKERPREF_PERFORMANCE_LOGGING_ENABLED:
 #ifdef DUMP_CONTROLLED_BY_PREF
     case WORKERPREF_DUMP:
 #endif
     case WORKERPREF_INTERCEPTION_ENABLED:
     case WORKERPREF_INTERCEPTION_OPAQUE_ENABLED:
     case WORKERPREF_SERVICEWORKERS:
     case WORKERPREF_SERVICEWORKERS_TESTING:
--- a/dom/workers/ServiceWorkerRegistration.cpp
+++ b/dom/workers/ServiceWorkerRegistration.cpp
@@ -48,16 +48,32 @@ ServiceWorkerRegistrationVisible(JSConte
   WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
   if (!workerPrivate) {
     return false;
   }
 
   return workerPrivate->ServiceWorkersEnabled();
 }
 
+bool
+ServiceWorkerNotificationAPIVisible(JSContext* aCx, JSObject* aObj)
+{
+  if (NS_IsMainThread()) {
+    return Preferences::GetBool("dom.webnotifications.serviceworker.enabled", false);
+  }
+
+  // Otherwise check the pref via the work private helper
+  WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
+  if (!workerPrivate) {
+    return false;
+  }
+
+  return workerPrivate->DOMServiceWorkerNotificationEnabled();
+}
+
 NS_IMPL_ADDREF_INHERITED(ServiceWorkerRegistrationBase, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(ServiceWorkerRegistrationBase, DOMEventTargetHelper)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ServiceWorkerRegistrationBase)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 ServiceWorkerRegistrationBase::ServiceWorkerRegistrationBase(nsPIDOMWindow* aWindow,
                                                              const nsAString& aScope)
--- a/dom/workers/ServiceWorkerRegistration.h
+++ b/dom/workers/ServiceWorkerRegistration.h
@@ -28,16 +28,19 @@ class WorkerListener;
 namespace workers {
 class ServiceWorker;
 class WorkerPrivate;
 } // namespace workers
 
 bool
 ServiceWorkerRegistrationVisible(JSContext* aCx, JSObject* aObj);
 
+bool
+ServiceWorkerNotificationAPIVisible(JSContext* aCx, JSObject* aObj);
+
 // This class exists solely so that we can satisfy some WebIDL Func= attribute
 // constraints. Func= converts the function name to a header file to include, in
 // this case "ServiceWorkerRegistration.h".
 class ServiceWorkerRegistration final
 {
 public:
   // Something that we can feed into the Func webidl property to ensure that
   // SetScope is never exposed to the user.
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -1273,16 +1273,23 @@ public:
   bool
   DOMWorkerNotificationEnabled() const
   {
     AssertIsOnWorkerThread();
     return mPreferences[WORKERPREF_DOM_WORKERNOTIFICATION];
   }
 
   bool
+  DOMServiceWorkerNotificationEnabled() const
+  {
+    AssertIsOnWorkerThread();
+    return mPreferences[WORKERPREF_DOM_SERVICEWORKERNOTIFICATION];
+  }
+
+  bool
   DOMCachesTestingEnabled() const
   {
     AssertIsOnWorkerThread();
     return mPreferences[WORKERPREF_DOM_CACHES_TESTING];
   }
 
   bool
   PerformanceLoggingEnabled() const
--- a/dom/workers/Workers.h
+++ b/dom/workers/Workers.h
@@ -197,16 +197,17 @@ struct JSSettings
 
 enum WorkerPreference
 {
   WORKERPREF_DUMP = 0, // browser.dom.window.dump.enabled
   WORKERPREF_DOM_CACHES, // dom.caches.enabled
   WORKERPREF_SERVICEWORKERS, // dom.serviceWorkers.enabled
   WORKERPREF_INTERCEPTION_ENABLED, // dom.serviceWorkers.interception.enabled
   WORKERPREF_DOM_WORKERNOTIFICATION, // dom.webnotifications.workers.enabled
+  WORKERPREF_DOM_SERVICEWORKERNOTIFICATION, // dom.webnotifications.serviceworker.enabled
   WORKERPREF_DOM_CACHES_TESTING, // dom.caches.testing.enabled
   WORKERPREF_SERVICEWORKERS_TESTING, // dom.serviceWorkers.testing.enabled
   WORKERPREF_INTERCEPTION_OPAQUE_ENABLED, // dom.serviceWorkers.interception.opaque.enabled
   WORKERPREF_PERFORMANCE_LOGGING_ENABLED, // dom.performance.enable_user_timing_logging
   WORKERPREF_PUSH, // dom.push.enabled
   WORKERPREF_REQUESTCONTEXT, // dom.requestcontext.enabled
   WORKERPREF_COUNT
 };
--- a/dom/workers/test/serviceworkers/test_notification_get.html
+++ b/dom/workers/test/serviceworkers/test_notification_get.html
@@ -142,16 +142,17 @@
   SimpleTest.waitForExplicitFinish();
 
   MockServices.register();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true],
     ["dom.webnotifications.workers.enabled", true],
+    ["dom.webnotifications.serviceworker.enabled", true],
     ["notification.prompt.testing", true],
   ]}, function() {
     registerSW()
       .then(testGet)
       .then(testGetWorker)
       .then(testGetServiceWorker)
       .then(testAcrossThreads)
       .then(function() {
--- a/dom/workers/test/serviceworkers/test_notificationclick.html
+++ b/dom/workers/test/serviceworkers/test_notificationclick.html
@@ -44,14 +44,15 @@ https://bugzilla.mozilla.org/show_bug.cg
   };
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true],
     ["dom.webnotifications.workers.enabled", true],
+    ["dom.webnotifications.serviceworker.enabled", true],
     ['dom.serviceWorkers.interception.enabled', true],
     ["notification.prompt.testing", true],
   ]}, runTest);
 </script>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_notificationclick_focus.html
+++ b/dom/workers/test/serviceworkers/test_notificationclick_focus.html
@@ -44,14 +44,15 @@ https://bugzilla.mozilla.org/show_bug.cg
   };
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true],
     ["dom.webnotifications.workers.enabled", true],
+    ["dom.webnotifications.serviceworker.enabled", true],
     ["notification.prompt.testing", true],
     ["dom.disable_open_click_delay", 1000],
   ]}, runTest);
 </script>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
+++ b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
@@ -155,19 +155,19 @@ var interfaceNamesInGlobalScope =
     "ImageData",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MessageChannel",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MessageEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MessagePort",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    "Notification",
+    { name: "Notification", release: false },
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    "NotificationEvent",
+    { name: "NotificationEvent", release: false },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Performance",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PerformanceEntry",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PerformanceMark",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PerformanceMeasure",
--- a/gfx/thebes/gfx2DGlue.h
+++ b/gfx/thebes/gfx2DGlue.h
@@ -35,17 +35,17 @@ inline Rect ToRect(const IntRect &aRect)
 }
 
 inline Color ToColor(const gfxRGBA &aRGBA)
 {
   return Color(Float(aRGBA.r), Float(aRGBA.g),
                Float(aRGBA.b), Float(aRGBA.a));
 }
 
-inline gfxRGBA ThebesColor(Color &aColor)
+inline gfxRGBA ThebesColor(const Color &aColor)
 {
   return gfxRGBA(aColor.r, aColor.g, aColor.b, aColor.a);
 }
 
 inline Matrix ToMatrix(const gfxMatrix &aMatrix)
 {
   return Matrix(Float(aMatrix._11), Float(aMatrix._12), Float(aMatrix._21),
                 Float(aMatrix._22), Float(aMatrix._31), Float(aMatrix._32));
--- a/gfx/thebes/gfxBlur.cpp
+++ b/gfx/thebes/gfxBlur.cpp
@@ -165,68 +165,109 @@ struct BlurCacheKey : public PLDHashEntr
   typedef const BlurCacheKey* KeyTypePointer;
   enum { ALLOW_MEMMOVE = true };
 
   IntSize mMinSize;
   IntSize mBlurRadius;
   gfxRGBA mShadowColor;
   BackendType mBackend;
   RectCornerRadii mCornerRadii;
+  bool mIsInset;
 
-  BlurCacheKey(IntSize aMinimumSize, gfxIntSize aBlurRadius,
+  // Only used for inset blurs
+  bool mHasBorderRadius;
+  gfxIntSize mSpreadRadius;
+  IntSize mInnerMinSize;
+
+  BlurCacheKey(IntSize aMinSize, gfxIntSize aBlurRadius,
                RectCornerRadii* aCornerRadii, gfxRGBA aShadowColor,
-               BackendType aBackend)
-    : mMinSize(aMinimumSize)
-    , mBlurRadius(aBlurRadius)
-    , mShadowColor(aShadowColor)
-    , mBackend(aBackend)
-    , mCornerRadii(aCornerRadii ? *aCornerRadii : RectCornerRadii())
-  { }
+               BackendType aBackendType)
+    : BlurCacheKey(aMinSize, IntSize(0, 0),
+                   aBlurRadius, IntSize(0, 0),
+                   aCornerRadii, aShadowColor,
+                   false, false, aBackendType)
+  {}
 
   explicit BlurCacheKey(const BlurCacheKey* aOther)
     : mMinSize(aOther->mMinSize)
     , mBlurRadius(aOther->mBlurRadius)
     , mShadowColor(aOther->mShadowColor)
     , mBackend(aOther->mBackend)
     , mCornerRadii(aOther->mCornerRadii)
+    , mIsInset(aOther->mIsInset)
+    , mHasBorderRadius(aOther->mHasBorderRadius)
+    , mSpreadRadius(aOther->mSpreadRadius)
+    , mInnerMinSize(aOther->mInnerMinSize)
+  { }
+
+  explicit BlurCacheKey(IntSize aOuterMinSize, IntSize aInnerMinSize,
+                        gfxIntSize aBlurRadius, gfxIntSize aSpreadRadius,
+                        const RectCornerRadii* aCornerRadii, gfxRGBA aShadowColor,
+                        bool aIsInset,
+                        bool aHasBorderRadius, BackendType aBackendType)
+    : mMinSize(aOuterMinSize)
+    , mBlurRadius(aBlurRadius)
+    , mShadowColor(aShadowColor)
+    , mBackend(aBackendType)
+    , mCornerRadii(aCornerRadii ? *aCornerRadii : RectCornerRadii())
+    , mIsInset(aIsInset)
+    , mHasBorderRadius(aHasBorderRadius)
+    , mSpreadRadius(aSpreadRadius)
+    , mInnerMinSize(aInnerMinSize)
   { }
 
   static PLDHashNumber
   HashKey(const KeyTypePointer aKey)
   {
     PLDHashNumber hash = 0;
     hash = AddToHash(hash, aKey->mMinSize.width, aKey->mMinSize.height);
     hash = AddToHash(hash, aKey->mBlurRadius.width, aKey->mBlurRadius.height);
 
     hash = AddToHash(hash, HashBytes(&aKey->mShadowColor.r, sizeof(gfxFloat)));
     hash = AddToHash(hash, HashBytes(&aKey->mShadowColor.g, sizeof(gfxFloat)));
     hash = AddToHash(hash, HashBytes(&aKey->mShadowColor.b, sizeof(gfxFloat)));
     hash = AddToHash(hash, HashBytes(&aKey->mShadowColor.a, sizeof(gfxFloat)));
 
     for (int i = 0; i < 4; i++) {
-    hash = AddToHash(hash, aKey->mCornerRadii[i].width, aKey->mCornerRadii[i].height);
+      hash = AddToHash(hash, aKey->mCornerRadii[i].width, aKey->mCornerRadii[i].height);
     }
 
     hash = AddToHash(hash, (uint32_t)aKey->mBackend);
+
+    if (aKey->mIsInset) {
+      hash = AddToHash(hash, aKey->mSpreadRadius.width, aKey->mSpreadRadius.height);
+      hash = AddToHash(hash, aKey->mInnerMinSize.width, aKey->mInnerMinSize.height);
+      hash = AddToHash(hash, HashBytes(&aKey->mHasBorderRadius, sizeof(bool)));
+    }
     return hash;
   }
 
-  bool KeyEquals(KeyTypePointer aKey) const
+  bool
+  KeyEquals(KeyTypePointer aKey) const
   {
     if (aKey->mMinSize == mMinSize &&
         aKey->mBlurRadius == mBlurRadius &&
         aKey->mCornerRadii == mCornerRadii &&
         aKey->mShadowColor == mShadowColor &&
         aKey->mBackend == mBackend) {
+
+      if (mIsInset) {
+        return (mHasBorderRadius == aKey->mHasBorderRadius) &&
+                (mInnerMinSize == aKey->mInnerMinSize) &&
+                (mSpreadRadius == aKey->mSpreadRadius);
+      }
+
       return true;
      }
 
      return false;
   }
-  static KeyTypePointer KeyToPointer(KeyType aKey)
+
+  static KeyTypePointer
+  KeyToPointer(KeyType aKey)
   {
     return &aKey;
   }
 };
 
 /**
  * This class is what is cached. It need to be allocated in an object separated
  * to the cache entry to be able to be tracked by the nsExpirationTracker.
@@ -286,16 +327,37 @@ class BlurCache final : public nsExpirat
                                       aBackendType));
       if (blur) {
         MarkUsed(blur);
       }
 
       return blur;
     }
 
+    BlurCacheData* LookupInsetBoxShadow(const IntSize aOuterMinSize,
+                                        const IntSize aInnerMinSize,
+                                        const gfxIntSize& aBlurRadius,
+                                        const gfxIntSize& aSpreadRadius,
+                                        const RectCornerRadii* aCornerRadii,
+                                        const gfxRGBA& aShadowColor,
+                                        const bool& aHasBorderRadius,
+                                        BackendType aBackendType)
+    {
+      BlurCacheKey key(aOuterMinSize, aInnerMinSize,
+                       aBlurRadius, aSpreadRadius,
+                       aCornerRadii, aShadowColor,
+                       true, aHasBorderRadius, aBackendType);
+      BlurCacheData* blur = mHashEntries.Get(key);
+      if (blur) {
+        MarkUsed(blur);
+      }
+
+      return blur;
+    }
+
     // Returns true if we successfully register the blur in the cache, false
     // otherwise.
     bool RegisterEntry(BlurCacheData* aValue)
     {
       nsresult rv = AddObject(aValue);
       if (NS_FAILED(rv)) {
         // We are OOM, and we cannot track this object. We don't want stall
         // entries in the hash table (since the expiration tracker is responsible
@@ -426,33 +488,33 @@ CreateBlurMask(const IntSize& aRectSize,
 
   MOZ_ASSERT(aSliceBorder.LeftRight() <= expandedMinRect.width);
   MOZ_ASSERT(aSliceBorder.TopBottom() <= expandedMinRect.height);
 
   return result.forget();
 }
 
 static already_AddRefed<SourceSurface>
-CreateBoxShadow(DrawTarget& aDT, SourceSurface* aBlurMask, const gfxRGBA& aShadowColor)
+CreateBoxShadow(SourceSurface* aBlurMask, const gfxRGBA& aShadowColor)
 {
   IntSize blurredSize = aBlurMask->GetSize();
   gfxPlatform* platform = gfxPlatform::GetPlatform();
   RefPtr<DrawTarget> boxShadowDT =
     platform->CreateOffscreenContentDrawTarget(blurredSize, SurfaceFormat::B8G8R8A8);
 
   if (!boxShadowDT) {
     return nullptr;
   }
 
   ColorPattern shadowColor(ToDeviceColor(aShadowColor));
   boxShadowDT->MaskSurface(shadowColor, aBlurMask, Point(0, 0));
   return boxShadowDT->Snapshot();
 }
 
-SourceSurface*
+static SourceSurface*
 GetBlur(DrawTarget& aDT,
         const IntSize& aRectSize,
         const gfxIntSize& aBlurRadius,
         RectCornerRadii* aCornerRadii,
         const gfxRGBA& aShadowColor,
         IntMargin& aExtendDestBy,
         IntMargin& aSlice)
 {
@@ -475,17 +537,17 @@ GetBlur(DrawTarget& aDT,
 
   RefPtr<SourceSurface> blurMask =
     CreateBlurMask(aRectSize, aCornerRadii, aBlurRadius, aExtendDestBy, aSlice, aDT);
 
   if (!blurMask) {
     return nullptr;
   }
 
-  RefPtr<SourceSurface> boxShadow = CreateBoxShadow(aDT, blurMask, aShadowColor);
+  RefPtr<SourceSurface> boxShadow = CreateBoxShadow(blurMask, aShadowColor);
   if (!boxShadow) {
     return nullptr;
   }
 
   CacheBlur(aDT, minSize, aBlurRadius, aCornerRadii, aShadowColor, aExtendDestBy, boxShadow);
   return boxShadow;
 }
 
@@ -536,16 +598,78 @@ DrawCorner(DrawTarget& aDT, SourceSurfac
 {
   if (aSkipRect.Contains(aDest)) {
     return;
   }
 
   aDT.DrawSurface(aSurface, aDest, aSrc);
 }
 
+static void
+DrawBoxShadows(DrawTarget& aDestDrawTarget, SourceSurface* aSourceBlur,
+               Rect aDstOuter, Rect aDstInner, Rect aSrcOuter, Rect aSrcInner,
+               Rect aSkipRect)
+{
+  // Corners: top left, top right, bottom left, bottom right
+  DrawCorner(aDestDrawTarget, aSourceBlur,
+             RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.X(),
+                               aDstInner.Y(), aDstOuter.X()),
+             RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.X(),
+                               aSrcInner.Y(), aSrcOuter.X()),
+             aSkipRect);
+
+  DrawCorner(aDestDrawTarget, aSourceBlur,
+             RectWithEdgesTRBL(aDstOuter.Y(), aDstOuter.XMost(),
+                               aDstInner.Y(), aDstInner.XMost()),
+             RectWithEdgesTRBL(aSrcOuter.Y(), aSrcOuter.XMost(),
+                               aSrcInner.Y(), aSrcInner.XMost()),
+             aSkipRect);
+
+  DrawCorner(aDestDrawTarget, aSourceBlur,
+             RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.X(),
+                               aDstOuter.YMost(), aDstOuter.X()),
+             RectWithEdgesTRBL(aSrcInner.YMost(), aSrcInner.X(),
+                               aSrcOuter.YMost(), aSrcOuter.X()),
+             aSkipRect);
+
+  DrawCorner(aDestDrawTarget, aSourceBlur,
+             RectWithEdgesTRBL(aDstInner.YMost(), aDstOuter.XMost(),
+                               aDstOuter.YMost(), aDstInner.XMost()),
+             RectWithEdgesTRBL(aSrcInner.YMost(), aSrcOuter.XMost(),
+                               aSrcOuter.YMost(), aSrcInner.XMost()),
+             aSkipRect);
+
+  // Edges: top, left, right, bottom
+  RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
+                         RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.XMost(),
+                                           aDstInner.Y(), aDstInner.X()),
+                         RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.XMost(),
+                                           aSrcInner.Y(), aSrcInner.X()),
+                         aSkipRect);
+  RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
+                         RectWithEdgesTRBL(aDstInner.Y(), aDstInner.X(),
+                                           aDstInner.YMost(), aDstOuter.X()),
+                         RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.X(),
+                                           aSrcInner.YMost(), aSrcOuter.X()),
+                         aSkipRect);
+  RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
+                         RectWithEdgesTRBL(aDstInner.Y(), aDstOuter.XMost(),
+                                           aDstInner.YMost(), aDstInner.XMost()),
+                         RectWithEdgesTRBL(aSrcInner.Y(), aSrcOuter.XMost(),
+                                           aSrcInner.YMost(), aSrcInner.XMost()),
+                         aSkipRect);
+  RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
+                         RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.XMost(),
+                                           aDstOuter.YMost(), aDstInner.X()),
+                         RectWithEdgesTRBL(aSrcInner.YMost(), aSrcInner.XMost(),
+                                           aSrcOuter.YMost(), aSrcInner.X()),
+                         aSkipRect);
+}
+
+
 /***
  * We draw a blurred a rectangle by only blurring a smaller rectangle and
  * splitting the rectangle into 9 parts.
  * First, a small minimum source rect is calculated and used to create a blur
  * mask since the actual blurring itself is expensive. Next, we use the mask
  * with the given shadow color to create a minimally-sized box shadow of the
  * right color. Finally, we cut out the 9 parts from the box-shadow source and
  * paint each part in the right place, stretching the non-corner parts to fill
@@ -593,70 +717,18 @@ gfxAlphaBoxBlur::BlurRectangle(gfxContex
 
   Rect skipRect = ToRect(aSkipRect);
 
   if (srcInner.IsEqualInterior(srcOuter)) {
     MOZ_ASSERT(dstInner.IsEqualInterior(dstOuter));
     // The target rect is smaller than the minimal size so just draw the surface
     destDrawTarget.DrawSurface(boxShadow, dstInner, srcInner);
   } else {
-    // Corners: top left, top right, bottom left, bottom right
-    DrawCorner(destDrawTarget, boxShadow,
-               RectWithEdgesTRBL(dstOuter.Y(), dstInner.X(),
-                                 dstInner.Y(), dstOuter.X()),
-               RectWithEdgesTRBL(srcOuter.Y(), srcInner.X(),
-                                 srcInner.Y(), srcOuter.X()),
-               skipRect);
-
-    DrawCorner(destDrawTarget, boxShadow,
-               RectWithEdgesTRBL(dstOuter.Y(), dstOuter.XMost(),
-                                 dstInner.Y(), dstInner.XMost()),
-               RectWithEdgesTRBL(srcOuter.Y(), srcOuter.XMost(),
-                                 srcInner.Y(), srcInner.XMost()),
-               skipRect);
-
-    DrawCorner(destDrawTarget, boxShadow,
-               RectWithEdgesTRBL(dstInner.YMost(), dstInner.X(),
-                                 dstOuter.YMost(), dstOuter.X()),
-               RectWithEdgesTRBL(srcInner.YMost(), srcInner.X(),
-                                 srcOuter.YMost(), srcOuter.X()),
-               skipRect);
-
-    DrawCorner(destDrawTarget, boxShadow,
-               RectWithEdgesTRBL(dstInner.YMost(), dstOuter.XMost(),
-                                 dstOuter.YMost(), dstInner.XMost()),
-               RectWithEdgesTRBL(srcInner.YMost(), srcOuter.XMost(),
-                                 srcOuter.YMost(), srcInner.XMost()),
-               skipRect);
-
-    // Edges: top, left, right, bottom
-    RepeatOrStretchSurface(destDrawTarget, boxShadow,
-                           RectWithEdgesTRBL(dstOuter.Y(), dstInner.XMost(),
-                                             dstInner.Y(), dstInner.X()),
-                           RectWithEdgesTRBL(srcOuter.Y(), srcInner.XMost(),
-                                             srcInner.Y(), srcInner.X()),
-                           skipRect);
-    RepeatOrStretchSurface(destDrawTarget, boxShadow,
-                           RectWithEdgesTRBL(dstInner.Y(), dstInner.X(),
-                                             dstInner.YMost(), dstOuter.X()),
-                           RectWithEdgesTRBL(srcInner.Y(), srcInner.X(),
-                                             srcInner.YMost(), srcOuter.X()),
-                           skipRect);
-    RepeatOrStretchSurface(destDrawTarget, boxShadow,
-                           RectWithEdgesTRBL(dstInner.Y(), dstOuter.XMost(),
-                                             dstInner.YMost(), dstInner.XMost()),
-                           RectWithEdgesTRBL(srcInner.Y(), srcOuter.XMost(),
-                                             srcInner.YMost(), srcInner.XMost()),
-                           skipRect);
-    RepeatOrStretchSurface(destDrawTarget, boxShadow,
-                           RectWithEdgesTRBL(dstInner.YMost(), dstInner.XMost(),
-                                             dstOuter.YMost(), dstInner.X()),
-                           RectWithEdgesTRBL(srcInner.YMost(), srcInner.XMost(),
-                                             srcOuter.YMost(), srcInner.X()),
-                           skipRect);
+    DrawBoxShadows(destDrawTarget, boxShadow, dstOuter, dstInner,
+                   srcOuter, srcInner, skipRect);
 
     // Middle part
     RepeatOrStretchSurface(destDrawTarget, boxShadow,
                            RectWithEdgesTRBL(dstInner.Y(), dstInner.XMost(),
                                              dstInner.YMost(), dstInner.X()),
                            RectWithEdgesTRBL(srcInner.Y(), srcInner.XMost(),
                                              srcInner.YMost(), srcInner.X()),
                            skipRect);
@@ -677,8 +749,263 @@ gfxAlphaBoxBlur::BlurRectangle(gfxContex
   // will not just fill the pixels that have their pixel center inside the
   // filled shape. Instead, it will fill all the pixels which are partially
   // covered by the shape. So for pixels on the edge between two adjacent parts,
   // all those pixels will be painted to by both parts, which looks very bad.
 
   destDrawTarget.PopClip();
 }
 
+static already_AddRefed<Path>
+GetBoxShadowInsetPath(DrawTarget* aDrawTarget,
+                      const Rect aOuterRect, const Rect aInnerRect,
+                      const bool aHasBorderRadius, const RectCornerRadii& aInnerClipRadii)
+{
+  /***
+   * We create an inset path by having two rects.
+   *
+   *  -----------------------
+   *  |  ________________   |
+   *  | |                |  |
+   *  | |                |  |
+   *  | ------------------  |
+   *  |_____________________|
+   *
+   * The outer rect and the inside rect. The path
+   * creates a frame around the content where we draw the inset shadow.
+   */
+  RefPtr<PathBuilder> builder =
+    aDrawTarget->CreatePathBuilder(FillRule::FILL_EVEN_ODD);
+  AppendRectToPath(builder, aOuterRect, true);
+
+  if (aHasBorderRadius) {
+    AppendRoundedRectToPath(builder, aInnerRect, aInnerClipRadii, false);
+  } else {
+    AppendRectToPath(builder, aInnerRect, false);
+  }
+  return builder->Finish();
+}
+
+static void
+ComputeRectsForInsetBoxShadow(gfxIntSize aBlurRadius,
+                              gfxIntSize aSpreadRadius,
+                              const Rect& aDestRect,
+                              const Rect& aShadowClipRect,
+                              Rect& aOutOuterRect,
+                              Rect& aOutInnerRect,
+                              Margin& aOutPathMargins)
+{
+  gfxIntSize marginSize = aBlurRadius + aSpreadRadius;
+  // The sizes we're given for aBlurRadius/aSpreadRadius are radius'.
+  // We actually want to paint the whole blur, so we need the diameter.
+  // We render both the outer / inner blur portions of a blur,
+  // Then we clip out the outer portion later.
+  aOutPathMargins.SizeTo(marginSize.height, marginSize.width, marginSize.height, marginSize.width);
+  aOutPathMargins += aOutPathMargins;
+
+  aOutOuterRect.x = 0;
+  aOutInnerRect.x = marginSize.width;
+
+  aOutOuterRect.y = 0;
+  aOutInnerRect.y = marginSize.height;
+
+  // + 1 for the middle edges so we can sample them
+  aOutInnerRect.width = aOutPathMargins.LeftRight() + 1;
+  aOutInnerRect.height = aOutPathMargins.TopBottom() + 1;
+
+  // The outer path rect needs to be 1 blur radius past the inner edges
+  aOutOuterRect.width = aOutInnerRect.XMost() + marginSize.width;
+  aOutOuterRect.height = aOutInnerRect.YMost() + marginSize.height;
+
+  if ((aOutOuterRect.width >= aDestRect.width) ||
+      (aOutOuterRect.height >= aDestRect.height) ||
+      (aOutInnerRect.width >= aShadowClipRect.width) ||
+      (aOutInnerRect.height >= aShadowClipRect.height))
+  {
+    aOutOuterRect.width = aDestRect.width;
+    aOutOuterRect.height = aDestRect.height;
+    aOutInnerRect.width = aShadowClipRect.width;
+    aOutInnerRect.height = aShadowClipRect.height;
+    aOutPathMargins.SizeTo(0, 0, 0, 0);
+  }
+}
+
+static void
+FillDestinationPath(gfxContext* aDestinationCtx,
+                    const Rect aDestinationRect,
+                    const Rect aShadowClipRect,
+                    const Color& aShadowColor,
+                    const bool aHasBorderRadius,
+                    const RectCornerRadii& aInnerClipRadii)
+{
+  // When there is no blur radius, fill the path onto the destination
+  // surface.
+  aDestinationCtx->SetColor(ThebesColor(aShadowColor));
+  DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget();
+  RefPtr<Path> shadowPath = GetBoxShadowInsetPath(destDrawTarget, aDestinationRect,
+                                                  aShadowClipRect, aHasBorderRadius,
+                                                  aInnerClipRadii);
+
+  aDestinationCtx->SetPath(shadowPath);
+  aDestinationCtx->Fill();
+}
+
+void
+CacheInsetBlur(const IntSize aMinOuterSize,
+               const IntSize aMinInnerSize,
+               const gfxIntSize& aBlurRadius,
+               const gfxIntSize& aSpreadRadius,
+               const RectCornerRadii* aCornerRadii,
+               const gfxRGBA& aShadowColor,
+               const bool& aHasBorderRadius,
+               BackendType aBackendType,
+               IntMargin aExtendBy,
+               SourceSurface* aBoxShadow)
+{
+  BlurCacheKey key(aMinOuterSize, aMinInnerSize,
+                   aBlurRadius, aSpreadRadius,
+                   aCornerRadii, aShadowColor,
+                   true, aHasBorderRadius, aBackendType);
+  BlurCacheData* data = new BlurCacheData(aBoxShadow, aExtendBy, key);
+  if (!gBlurCache->RegisterEntry(data)) {
+    delete data;
+  }
+}
+
+already_AddRefed<mozilla::gfx::SourceSurface>
+gfxAlphaBoxBlur::GetInsetBlur(Rect& aOuterRect,
+                              Rect& aInnerRect,
+                              const gfxIntSize& aBlurRadius,
+                              const gfxIntSize& aSpreadRadius,
+                              const RectCornerRadii& aInnerClipRadii,
+                              const Color& aShadowColor,
+                              const bool& aHasBorderRadius,
+                              IntPoint& aOutTopLeft,
+                              gfxContext* aDestinationCtx)
+
+{
+  if (!gBlurCache) {
+    gBlurCache = new BlurCache();
+  }
+
+  gfxIntSize outerRectSize = RoundedToInt(aOuterRect).Size();
+  gfxIntSize innerRectSize = RoundedToInt(aInnerRect).Size();
+  DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget();
+
+  BlurCacheData* cached =
+      gBlurCache->LookupInsetBoxShadow(outerRectSize, innerRectSize, aBlurRadius, aSpreadRadius,
+                                       &aInnerClipRadii, ThebesColor(aShadowColor),
+                                       aHasBorderRadius, destDrawTarget->GetBackendType());
+
+  if (cached) {
+    IntMargin extends = cached->mExtendDest;
+    aOutTopLeft.x = extends.left;
+    aOutTopLeft.y = extends.top;
+    // So we don't forget the actual cached blur
+    RefPtr<SourceSurface> cachedBlur = cached->mBlur;
+    return cachedBlur.forget();
+  }
+
+  // Dirty rect and skip rect are null for the min inset shadow.
+  // When rendering inset box shadows, we respect the spread radius by changing
+  // the shape of the unblurred shadow, and can pass a spread radius of zero here.
+  gfxIntSize zeroSpread(0, 0);
+  gfxContext* minGfxContext = Init(ThebesRect(aOuterRect), zeroSpread, aBlurRadius, nullptr, nullptr);
+  if (!minGfxContext) {
+    return nullptr;
+  }
+
+  DrawTarget* minDrawTarget = minGfxContext->GetDrawTarget();
+  RefPtr<Path> maskPath = GetBoxShadowInsetPath(minDrawTarget, aOuterRect,
+                                                aInnerRect, aHasBorderRadius,
+                                                aInnerClipRadii);
+
+  minGfxContext->SetColor(ThebesColor(aShadowColor));
+  minGfxContext->SetPath(maskPath);
+  minGfxContext->Fill();
+
+  RefPtr<SourceSurface> minMask = DoBlur(minDrawTarget, &aOutTopLeft);
+  if (!minMask) {
+    return nullptr;
+  }
+
+  RefPtr<SourceSurface> minInsetBlur = CreateBoxShadow(minMask, ThebesColor(aShadowColor));
+  if (!minInsetBlur) {
+    return nullptr;
+  }
+
+  IntMargin extendBy(aOutTopLeft.y, 0, 0, aOutTopLeft.x);
+  CacheInsetBlur(outerRectSize, innerRectSize,
+                 aBlurRadius, aSpreadRadius,
+                 &aInnerClipRadii, ThebesColor(aShadowColor),
+                 aHasBorderRadius, destDrawTarget->GetBackendType(),
+                 extendBy, minInsetBlur);
+  return minInsetBlur.forget();
+}
+
+/***
+ * Blur an inset box shadow by doing:
+ * 1) Create a minimal box shadow path that creates a frame.
+ * 2) Draw the box shadow portion over the destination surface.
+ * 3) The "inset" part is created by a clip rect that properly clips
+ *    the alpha mask so that it has clean edges. We still create the full
+ *    proper alpha mask, but let the clip deal with the clean edges.
+ *
+ * All parameters should already be in device pixels.
+ */
+void
+gfxAlphaBoxBlur::BlurInsetBox(gfxContext* aDestinationCtx,
+                              const Rect aDestinationRect,
+                              const Rect aShadowClipRect,
+                              const gfxIntSize aBlurRadius,
+                              const gfxIntSize aSpreadRadius,
+                              const Color& aShadowColor,
+                              bool aHasBorderRadius,
+                              const RectCornerRadii& aInnerClipRadii,
+                              const Rect aSkipRect)
+{
+  if ((aBlurRadius.width <= 0 && aBlurRadius.height <= 0)) {
+    // The outer path must be rounded out
+    // If not blurring, we're done now.
+    Rect pathRect(aDestinationRect);
+    pathRect.RoundOut();
+    FillDestinationPath(aDestinationCtx, pathRect, aShadowClipRect,
+        aShadowColor, aHasBorderRadius, aInnerClipRadii);
+    return;
+  }
+
+  DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget();
+  Rect outerRect;
+  Rect innerRect;
+  Margin pathMargins;
+  ComputeRectsForInsetBoxShadow(aBlurRadius, aSpreadRadius,
+                                aDestinationRect, aShadowClipRect,
+                                outerRect, innerRect,
+                                pathMargins);
+  IntPoint topLeft;
+  RefPtr<SourceSurface> minInsetBlur = GetInsetBlur(outerRect, innerRect,
+                                                    aBlurRadius, aSpreadRadius,
+                                                    aInnerClipRadii, aShadowColor,
+                                                    aHasBorderRadius,
+                                                    topLeft, aDestinationCtx);
+  if (!minInsetBlur) {
+    return;
+  }
+
+  Rect destRectOuter(aDestinationRect);
+  destRectOuter.RoundIn();
+  Rect destRectInner(destRectOuter);
+  destRectInner.Deflate(pathMargins);
+
+  Rect srcRectOuter(outerRect);
+  srcRectOuter.MoveBy(abs(topLeft.x), abs(topLeft.y));
+  Rect srcRectInner(srcRectOuter);
+  srcRectInner.Deflate(pathMargins);
+
+  if (srcRectOuter.IsEqualInterior(srcRectInner)) {
+    destDrawTarget->DrawSurface(minInsetBlur, destRectOuter, srcRectOuter);
+  } else {
+    DrawBoxShadows(*destDrawTarget, minInsetBlur,
+                   destRectOuter, destRectInner,
+                   srcRectOuter, srcRectInner,
+                   aSkipRect);
+ }
+}
--- a/gfx/thebes/gfxBlur.h
+++ b/gfx/thebes/gfxBlur.h
@@ -15,16 +15,17 @@
 
 class gfxContext;
 struct gfxRect;
 struct gfxRGBA;
 
 namespace mozilla {
   namespace gfx {
     class AlphaBoxBlur;
+    struct Color;
     struct RectCornerRadii;
     class SourceSurface;
     class DrawTarget;
   } // namespace gfx
 } // namespace mozilla
 
 /**
  * Implementation of a triple box blur approximation of a Gaussian blur.
@@ -130,19 +131,54 @@ public:
                               RectCornerRadii* aCornerRadii,
                               const gfxPoint& aBlurStdDev,
                               const gfxRGBA& aShadowColor,
                               const gfxRect& aDirtyRect,
                               const gfxRect& aSkipRect);
 
     static void ShutdownBlurCache();
 
-
+    /***
+     * Blurs an inset box shadow according to a given path.
+     * This is equivalent to calling Init(), drawing the inset path,
+     * and calling paint. Do not call Init() if using this method.
+     *
+     * @param aDestinationCtx     The destination to blur to.
+     * @param aDestinationRect    The destination rect in device pixels
+     * @param aShadowClipRect     The destiniation inner rect of the
+     *                            inset path in device pixels.
+     * @param aBlurRadius         The standard deviation of the blur.
+     * @param aSpreadRadius       The spread radius in device pixels.
+     * @param aShadowColor        The color of the blur.
+     * @param aHasBorderRadius    If this element also has a border radius
+     * @param aInnerClipRadii     Corner radii for the inside rect if it is a rounded rect.
+     * @param aSkipRect           An area in device pixels we don't have to paint in.
+     */
+    void BlurInsetBox(gfxContext* aDestinationCtx,
+                      const mozilla::gfx::Rect aDestinationRect,
+                      const mozilla::gfx::Rect aShadowClipRect,
+                      const gfxIntSize aBlurRadius,
+                      const gfxIntSize aSpreadRadius,
+                      const mozilla::gfx::Color& aShadowColor,
+                      const bool aHasBorderRadius,
+                      const RectCornerRadii& aInnerClipRadii,
+                      const mozilla::gfx::Rect aSkipRect);
 
 protected:
+    already_AddRefed<mozilla::gfx::SourceSurface>
+                   GetInsetBlur(mozilla::gfx::Rect& aOuterRect,
+                                mozilla::gfx::Rect& aInnerRect,
+                                const gfxIntSize& aBlurRadius,
+                                const gfxIntSize& aSpreadRadius,
+                                const RectCornerRadii& aInnerClipRadii,
+                                const mozilla::gfx::Color& aShadowColor,
+                                const bool& aHasBorderRadius,
+                                mozilla::gfx::IntPoint& aOutTopLeft,
+                                gfxContext* aDestinationCtx);
+
     /**
      * The context of the temporary alpha surface.
      */
     nsRefPtr<gfxContext> mContext;
 
     /**
      * The temporary alpha surface.
      */
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -2546,27 +2546,26 @@ BytecodeEmitter::emitNameOp(ParseNode* p
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitPropLHS(ParseNode* pn)
 {
     MOZ_ASSERT(pn->isKind(PNK_DOT));
+    MOZ_ASSERT(!pn->as<PropertyAccess>().isSuper());
+
     ParseNode* pn2 = pn->maybeExpr();
 
-    // Don't want super sneaking in here.
-    MOZ_ASSERT(!pn2->isKind(PNK_POSHOLDER));
-
     /*
      * If the object operand is also a dotted property reference, reverse the
      * list linked via pn_expr temporarily so we can iterate over it from the
      * bottom up (reversing again as we go), to avoid excessive recursion.
      */
-    if (pn2->isKind(PNK_DOT)) {
+    if (pn2->isKind(PNK_DOT) && !pn2->as<PropertyAccess>().isSuper()) {
         ParseNode* pndot = pn2;
         ParseNode* pnup = nullptr;
         ParseNode* pndown;
         ptrdiff_t top = offset();
         for (;;) {
             /* Reverse pndot->pn_expr to point up, not down. */
             pndot->pn_offset = top;
             MOZ_ASSERT(!pndot->isUsed());
--- a/js/src/gc/GCInternals.h
+++ b/js/src/gc/GCInternals.h
@@ -51,16 +51,17 @@ class MOZ_RAII AutoTraceSession
     AutoLockForExclusiveAccess lock;
     JSRuntime* runtime;
 
   private:
     AutoTraceSession(const AutoTraceSession&) = delete;
     void operator=(const AutoTraceSession&) = delete;
 
     JS::HeapState prevState;
+    AutoSPSEntry pseudoFrame;
 };
 
 struct MOZ_RAII AutoPrepareForTracing
 {
     AutoFinishGC finish;
     AutoTraceSession session;
     AutoCopyFreeListToArenas copy;
 
--- a/js/src/gc/Heap.h
+++ b/js/src/gc/Heap.h
@@ -1446,17 +1446,18 @@ TenuredCell::readBarrier(TenuredCell* th
     if (thing->isMarked(GRAY))
         UnmarkGrayCellRecursively(thing, thing->getTraceKind());
 }
 
 /* static */ MOZ_ALWAYS_INLINE void
 TenuredCell::writeBarrierPre(TenuredCell* thing)
 {
     MOZ_ASSERT(!CurrentThreadIsIonCompiling());
-    if (isNullLike(thing) || thing->shadowRuntimeFromAnyThread()->isHeapBusy())
+    MOZ_ASSERT_IF(thing, !isNullLike(thing));
+    if (!thing || thing->shadowRuntimeFromAnyThread()->isHeapBusy())
         return;
 
     JS::shadow::Zone* shadowZone = thing->shadowZoneFromAnyThread();
     if (shadowZone->needsIncrementalBarrier()) {
         MOZ_ASSERT(!RuntimeFromMainThreadIsHeapMajorCollecting(shadowZone));
         Cell* tmp = thing;
         TraceManuallyBarrieredGenericPointerEdge(shadowZone->barrierTracer(), &tmp, "pre barrier");
         MOZ_ASSERT(tmp == thing);
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -252,16 +252,23 @@ CheckTracedThing<Value>(JSTracer* trc, V
 
 template <>
 void
 CheckTracedThing<jsid>(JSTracer* trc, jsid id)
 {
     DispatchIdTyped(CheckTracedFunctor<jsid>(), id, trc);
 }
 
+template <>
+void
+CheckTracedThing<TaggedProto>(JSTracer* trc, TaggedProto proto)
+{
+    DispatchTaggedProtoTyped(CheckTracedFunctor<TaggedProto>(), proto, trc);
+}
+
 #define IMPL_CHECK_TRACED_THING(_, type, __) \
     template void CheckTracedThing<type*>(JSTracer*, type*);
 JS_FOR_EACH_TRACEKIND(IMPL_CHECK_TRACED_THING);
 #undef IMPL_CHECK_TRACED_THING
 } // namespace js
 
 static bool
 ShouldMarkCrossCompartment(JSTracer* trc, JSObject* src, Cell* cell)
@@ -379,17 +386,18 @@ AssertRootMarkingPhase(JSTracer* trc)
     D(JSAtom*) \
     D(JSString*) \
     D(JSFlatString*) \
     D(JSLinearString*) \
     D(PropertyName*) \
     D(JS::Symbol*) \
     D(js::ObjectGroup*) \
     D(Value) \
-    D(jsid)
+    D(jsid) \
+    D(TaggedProto)
 
 // The second parameter to BaseGCType is derived automatically based on T. The
 // relation here is that for any T, the TraceKind will automatically,
 // statically select the correct Cell layout for marking. Below, we instantiate
 // each override with a declaration of the most derived layout type.
 //
 // Usage:
 //   BaseGCType<T>::type
@@ -416,16 +424,17 @@ JS_FOR_EACH_TRACEKIND(IMPL_BASE_GC_TYPE)
 
 // Our barrier templates are parameterized on the pointer types so that we can
 // share the definitions with Value and jsid. Thus, we need to strip the
 // pointer before sending the type to BaseGCType and re-add it on the other
 // side. As such:
 template <typename T> struct PtrBaseGCType {};
 template <> struct PtrBaseGCType<Value> { typedef Value type; };
 template <> struct PtrBaseGCType<jsid> { typedef jsid type; };
+template <> struct PtrBaseGCType<TaggedProto> { typedef TaggedProto type; };
 template <typename T> struct PtrBaseGCType<T*> { typedef typename BaseGCType<T>::type* type; };
 
 template <typename T>
 typename PtrBaseGCType<T>::type*
 ConvertToBase(T* thingp)
 {
     return reinterpret_cast<typename PtrBaseGCType<T>::type*>(thingp);
 }
@@ -588,17 +597,18 @@ js::TraceManuallyBarrieredGenericPointer
 template <typename T>
 void
 DispatchToTracer(JSTracer* trc, T* thingp, const char* name)
 {
 #define IS_SAME_TYPE_OR(name, type, _) mozilla::IsSame<type*, T>::value ||
     static_assert(
             JS_FOR_EACH_TRACEKIND(IS_SAME_TYPE_OR)
             mozilla::IsSame<T, JS::Value>::value ||
-            mozilla::IsSame<T, jsid>::value,
+            mozilla::IsSame<T, jsid>::value ||
+            mozilla::IsSame<T, TaggedProto>::value,
             "Only the base cell layout types are allowed into marking/tracing internals");
 #undef IS_SAME_TYPE_OR
     if (trc->isMarkingTracer())
         return DoMarking(static_cast<GCMarker*>(trc), *thingp);
     if (trc->isTenuringTracer())
         return static_cast<TenuringTracer*>(trc)->traverse(thingp);
     MOZ_ASSERT(trc->isCallbackTracer());
     DoCallback(trc->asCallbackTracer(), thingp, name);
@@ -751,16 +761,24 @@ DoMarking<Value>(GCMarker* gcmarker, Val
 
 template <>
 void
 DoMarking<jsid>(GCMarker* gcmarker, jsid id)
 {
     DispatchIdTyped(DoMarkingFunctor<jsid>(), id, gcmarker);
 }
 
+template <>
+void
+DoMarking<TaggedProto>(GCMarker* gcmarker, TaggedProto proto)
+{
+    if (proto.isObject())
+        DoMarking<JSObject*>(gcmarker, proto.toObject());
+}
+
 // The simplest traversal calls out to the fully generic traceChildren function
 // to visit the child edges. In the absence of other traversal mechanisms, this
 // function will rapidly grow the stack past its bounds and crash the process.
 // Thus, this generic tracing should only be used in cases where subsequent
 // tracing will not recurse.
 template <typename T>
 void
 js::GCMarker::markAndTraceChildren(T* thing)
@@ -1094,17 +1112,17 @@ js::ObjectGroup::traceChildren(JSTracer*
 {
     unsigned count = getPropertyCount();
     for (unsigned i = 0; i < count; i++) {
         if (ObjectGroup::Property* prop = getProperty(i))
             TraceEdge(trc, &prop->id, "group_property");
     }
 
     if (proto().isObject())
-        TraceEdge(trc, &protoRaw(), "group_proto");
+        TraceEdge(trc, &proto(), "group_proto");
 
     if (newScript())
         newScript()->trace(trc);
 
     if (maybePreliminaryObjects())
         maybePreliminaryObjects()->trace(trc);
 
     if (maybeUnboxedLayout())
@@ -1896,16 +1914,28 @@ TenuringTracer::traverse(Value* valp)
     if (!valp->isObject())
         return;
 
     JSObject *obj = &valp->toObject();
     traverse(&obj);
     valp->setObject(*obj);
 }
 
+template <>
+void
+TenuringTracer::traverse(TaggedProto* protop)
+{
+    if (!protop->isObject())
+        return;
+
+    JSObject *obj = protop->toObject();
+    traverse(&obj);
+    *protop = TaggedProto(obj);
+}
+
 template <> void js::TenuringTracer::traverse(js::BaseShape**) {}
 template <> void js::TenuringTracer::traverse(js::jit::JitCode**) {}
 template <> void js::TenuringTracer::traverse(JSScript**) {}
 template <> void js::TenuringTracer::traverse(js::LazyScript**) {}
 template <> void js::TenuringTracer::traverse(js::Shape**) {}
 template <> void js::TenuringTracer::traverse(JSString**) {}
 template <> void js::TenuringTracer::traverse(JS::Symbol**) {}
 template <> void js::TenuringTracer::traverse(js::ObjectGroup**) {}
@@ -2284,17 +2314,17 @@ IsMarkedInternal(JSObject** thingp)
     }
     return IsMarkedInternalCommon(thingp);
 }
 
 template <typename S>
 struct IsMarkedFunctor : public IdentityDefaultAdaptor<S> {
     template <typename T> S operator()(T* t, bool* rv) {
         *rv = IsMarkedInternal(&t);
-        return js::gc::RewrapValueOrId<S, T*>::wrap(t);
+        return js::gc::RewrapTaggedPointer<S, T*>::wrap(t);
     }
 };
 
 template <>
 bool
 IsMarkedInternal<Value>(Value* valuep)
 {
     bool rv = true;
@@ -2306,16 +2336,25 @@ template <>
 bool
 IsMarkedInternal<jsid>(jsid* idp)
 {
     bool rv = true;
     *idp = DispatchIdTyped(IsMarkedFunctor<jsid>(), *idp, &rv);
     return rv;
 }
 
+template <>
+bool
+IsMarkedInternal<TaggedProto>(TaggedProto* protop)
+{
+    bool rv = true;
+    *protop = DispatchTaggedProtoTyped(IsMarkedFunctor<TaggedProto>(), *protop, &rv);
+    return rv;
+}
+
 template <typename T>
 static bool
 IsAboutToBeFinalizedInternal(T* thingp)
 {
     CheckIsMarkedThing(thingp);
     T thing = *thingp;
     JSRuntime* rt = thing->runtimeFromAnyThread();
 
@@ -2344,17 +2383,17 @@ IsAboutToBeFinalizedInternal(T* thingp)
 
     return false;
 }
 
 template <typename S>
 struct IsAboutToBeFinalizedFunctor : public IdentityDefaultAdaptor<S> {
     template <typename T> S operator()(T* t, bool* rv) {
         *rv = IsAboutToBeFinalizedInternal(&t);
-        return js::gc::RewrapValueOrId<S, T*>::wrap(t);
+        return js::gc::RewrapTaggedPointer<S, T*>::wrap(t);
     }
 };
 
 template <>
 bool
 IsAboutToBeFinalizedInternal<Value>(Value* valuep)
 {
     bool rv = false;
@@ -2366,16 +2405,25 @@ template <>
 bool
 IsAboutToBeFinalizedInternal<jsid>(jsid* idp)
 {
     bool rv = false;
     *idp = DispatchIdTyped(IsAboutToBeFinalizedFunctor<jsid>(), *idp, &rv);
     return rv;
 }
 
+template <>
+bool
+IsAboutToBeFinalizedInternal<TaggedProto>(TaggedProto* protop)
+{
+    bool rv = false;
+    *protop = DispatchTaggedProtoTyped(IsAboutToBeFinalizedFunctor<TaggedProto>(), *protop, &rv);
+    return rv;
+}
+
 namespace js {
 namespace gc {
 
 template <typename T>
 bool
 IsMarkedUnbarriered(T* thingp)
 {
     return IsMarkedInternal(ConvertToBase(thingp));
--- a/js/src/gc/Marking.h
+++ b/js/src/gc/Marking.h
@@ -15,16 +15,17 @@
 
 #include "ds/OrderedHashTable.h"
 #include "gc/Heap.h"
 #include "gc/Tracer.h"
 #include "js/GCAPI.h"
 #include "js/HeapAPI.h"
 #include "js/SliceBudget.h"
 #include "js/TracingAPI.h"
+#include "vm/TaggedProto.h"
 
 class JSLinearString;
 class JSRope;
 namespace js {
 class BaseShape;
 class GCMarker;
 class LazyScript;
 class NativeObject;
@@ -448,26 +449,27 @@ class HashKeyRef : public BufferableRef
         TraceManuallyBarrieredEdge(trc, &key, "HashKeyRef");
         map->rekeyIfMoved(prior, key);
     }
 };
 
 // Wrap a GC thing pointer into a new Value or jsid. The type system enforces
 // that the thing pointer is a wrappable type.
 template <typename S, typename T>
-struct RewrapValueOrId {};
+struct RewrapTaggedPointer{};
 #define DECLARE_REWRAP(S, T, method, prefix) \
-    template <> struct RewrapValueOrId<S, T> { \
+    template <> struct RewrapTaggedPointer<S, T> { \
         static S wrap(T thing) { return method ( prefix thing ); } \
     }
 DECLARE_REWRAP(JS::Value, JSObject*, JS::ObjectOrNullValue, );
 DECLARE_REWRAP(JS::Value, JSString*, JS::StringValue, );
 DECLARE_REWRAP(JS::Value, JS::Symbol*, JS::SymbolValue, );
 DECLARE_REWRAP(jsid, JSString*, NON_INTEGER_ATOM_TO_JSID, (JSAtom*));
 DECLARE_REWRAP(jsid, JS::Symbol*, SYMBOL_TO_JSID, );
+DECLARE_REWRAP(js::TaggedProto, JSObject*, js::TaggedProto, );
 
 } /* namespace gc */
 
 bool
 UnmarkGrayShapeRecursively(Shape* shape);
 
 template<typename T>
 void
--- a/js/src/gc/Tracer.cpp
+++ b/js/src/gc/Tracer.cpp
@@ -54,17 +54,17 @@ DoCallback(JS::CallbackTracer* trc, T* t
 #define INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS(name, type, _) \
     template type* DoCallback<type*>(JS::CallbackTracer*, type**, const char*);
 JS_FOR_EACH_TRACEKIND(INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS);
 #undef INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS
 
 template <typename S>
 struct DoCallbackFunctor : public IdentityDefaultAdaptor<S> {
     template <typename T> S operator()(T* t, JS::CallbackTracer* trc, const char* name) {
-        return js::gc::RewrapValueOrId<S, T*>::wrap(DoCallback(trc, &t, name));
+        return js::gc::RewrapTaggedPointer<S, T*>::wrap(DoCallback(trc, &t, name));
     }
 };
 
 template <>
 Value
 DoCallback<Value>(JS::CallbackTracer* trc, Value* vp, const char* name)
 {
     *vp = DispatchValueTyped(DoCallbackFunctor<Value>(), *vp, trc, name);
@@ -74,16 +74,24 @@ DoCallback<Value>(JS::CallbackTracer* tr
 template <>
 jsid
 DoCallback<jsid>(JS::CallbackTracer* trc, jsid* idp, const char* name)
 {
     *idp = DispatchIdTyped(DoCallbackFunctor<jsid>(), *idp, trc, name);
     return *idp;
 }
 
+template <>
+TaggedProto
+DoCallback<TaggedProto>(JS::CallbackTracer* trc, TaggedProto* protop, const char* name)
+{
+    *protop = DispatchTaggedProtoTyped(DoCallbackFunctor<TaggedProto>(), *protop, trc, name);
+    return *protop;
+}
+
 void
 JS::CallbackTracer::getTracingEdgeName(char* buffer, size_t bufferSize)
 {
     MOZ_ASSERT(bufferSize > 0);
     if (contextFunctor_) {
         (*contextFunctor_)(this, buffer, bufferSize);
         return;
     }
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug1204675.js
@@ -0,0 +1,13 @@
+function f(m, x) {
+        for (var i = 0; i < 2; ++i) {
+                    print(m(x[0]));
+                        }
+}
+function g() {
+        return false;
+}
+function h(y) {
+        return (y === 0);
+}
+f(g, [objectEmulatingUndefined()]);
+f(h, [false]);
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -2711,16 +2711,31 @@ IonBuilder::processNextTableSwitchCase(C
 
     // Add current block as predecessor if available.
     // This means the previous case didn't have a break statement.
     // So flow will continue in this block.
     if (current) {
         current->end(MGoto::New(alloc(), successor));
         if (!successor->addPredecessor(alloc(), current))
             return ControlStatus_Error;
+    } else {
+        // If this is an actual case statement, optimize by replacing the
+        // input to the switch case with the actual number of the case.
+        // This constant has been emitted when creating the case blocks.
+        if (state.tableswitch.ins->getDefault() != successor) {
+            MConstant* constant = successor->begin()->toConstant();
+            for (uint32_t j = 0; j < successor->stackDepth(); j++) {
+                MDefinition* ins = successor->getSlot(j);
+                if (ins != state.tableswitch.ins->getOperand(0))
+                    continue;
+
+                constant->setDependency(state.tableswitch.ins);
+                successor->setSlot(j, constant);
+            }
+        }
     }
 
     // Insert successor after the current block, to maintain RPO.
     graph().moveBlockToEnd(successor);
 
     // If this is the last successor the block should stop at the end of the tableswitch
     // Else it should stop at the start of the next successor
     if (state.tableswitch.currentBlock+1 < state.tableswitch.ins->numBlocks())
@@ -3362,20 +3377,26 @@ IonBuilder::tableSwitch(JSOp op, jssrcno
             caseblock->end(MGoto::New(alloc(), defaultcase));
             if (!defaultcase->addPredecessor(alloc(), caseblock))
                 return ControlStatus_Error;
         }
 
         tableswitch->addCase(tableswitch->addSuccessor(caseblock));
 
         // If this is an actual case (not filled gap),
-        // add this block to the list that still needs to get processed
-        if (casepc != pc)
+        // add this block to the list that still needs to get processed.
+        if (casepc != pc) {
             tableswitch->addBlock(caseblock);
 
+            // Add constant to indicate which case this is for use by
+            // processNextTableSwitchCase.
+            MConstant* constant = MConstant::New(alloc(), Int32Value(i + low));
+            caseblock->add(constant);
+        }
+
         pc2 += JUMP_OFFSET_LEN;
     }
 
     // Move defaultcase to the end, to maintain RPO.
     graph().moveBlockToEnd(defaultcase);
 
     MOZ_ASSERT(tableswitch->numCases() == (uint32_t)(high - low + 1));
     MOZ_ASSERT(tableswitch->numSuccessors() > 0);
@@ -6848,16 +6869,20 @@ IonBuilder::compareTrySpecializedOnBasel
                                                      MDefinition* right)
 {
     MOZ_ASSERT(*emitted == false);
 
     // Try to specialize based on any baseline caches that have been generated
     // for the opcode. These will cause the instruction's type policy to insert
     // fallible unboxes to the appropriate input types.
 
+    // Strict equality isn't supported.
+    if (op == JSOP_STRICTEQ || op == JSOP_STRICTNE)
+        return true;
+
     MCompare::CompareType type = inspector->expectedCompareType(pc);
     if (type == MCompare::Compare_Unknown)
         return true;
 
     MCompare* ins = MCompare::New(alloc(), left, right, op);
     ins->setCompareType(type);
     ins->cacheOperandMightEmulateUndefined(constraints());
 
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -5575,21 +5575,39 @@ GCRuntime::finishCollection(JS::gcreason
     // a zeal-triggered GC, we want to ensure that the mutator can continue
     // allocating on the same pages to reduce fragmentation.
     if (IsOOMReason(reason) || reason == JS::gcreason::DEBUG_GC) {
         gcstats::AutoPhase ap(stats, gcstats::PHASE_WAIT_BACKGROUND_THREAD);
         rt->gc.waitBackgroundSweepOrAllocEnd();
     }
 }
 
+static const char*
+HeapStateToLabel(JS::HeapState heapState)
+{
+    switch (heapState) {
+      case JS::HeapState::MinorCollecting:
+        return "js::Nursery::collect";
+      case JS::HeapState::MajorCollecting:
+        return "js::GCRuntime::collect";
+      case JS::HeapState::Tracing:
+        return "JS_IterateCompartments";
+      case JS::HeapState::Idle:
+        MOZ_CRASH("Should never have an Idle heap state when pushing GC pseudo frames!");
+    }
+    MOZ_ASSERT_UNREACHABLE("Should have exhausted every JS::HeapState variant!");
+    return nullptr;
+}
+
 /* Start a new heap session. */
 AutoTraceSession::AutoTraceSession(JSRuntime* rt, JS::HeapState heapState)
   : lock(rt),
     runtime(rt),
-    prevState(rt->heapState_)
+    prevState(rt->heapState_),
+    pseudoFrame(rt, HeapStateToLabel(heapState), ProfileEntry::Category::GC)
 {
     MOZ_ASSERT(rt->heapState_ == JS::HeapState::Idle);
     MOZ_ASSERT(heapState != JS::HeapState::Idle);
     MOZ_ASSERT_IF(heapState == JS::HeapState::MajorCollecting, rt->gc.nursery.isEmpty());
 
     // Threads with an exclusive context can hit refillFreeList while holding
     // the exclusive access lock. To avoid deadlocking when we try to acquire
     // this lock during GC and the other thread is waiting, make sure we hold
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -1195,17 +1195,17 @@ Forwarded(T* t)
 {
     RelocationOverlay* overlay = RelocationOverlay::fromCell(t);
     MOZ_ASSERT(overlay->isForwarded());
     return reinterpret_cast<T*>(overlay->forwardingAddress());
 }
 
 struct ForwardedFunctor : public IdentityDefaultAdaptor<Value> {
     template <typename T> inline Value operator()(T* t) {
-        return js::gc::RewrapValueOrId<Value, T*>::wrap(Forwarded(t));
+        return js::gc::RewrapTaggedPointer<Value, T*>::wrap(Forwarded(t));
     }
 };
 
 inline Value
 Forwarded(const JS::Value& value)
 {
     return DispatchValueTyped(ForwardedFunctor(), value);
 }
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -613,47 +613,51 @@ struct JSObject_Slots2 : JSObject { void
 struct JSObject_Slots4 : JSObject { void* data[3]; js::Value fslots[4]; };
 struct JSObject_Slots8 : JSObject { void* data[3]; js::Value fslots[8]; };
 struct JSObject_Slots12 : JSObject { void* data[3]; js::Value fslots[12]; };
 struct JSObject_Slots16 : JSObject { void* data[3]; js::Value fslots[16]; };
 
 /* static */ MOZ_ALWAYS_INLINE void
 JSObject::readBarrier(JSObject* obj)
 {
-    if (!isNullLike(obj) && obj->isTenured())
+    MOZ_ASSERT_IF(obj, !isNullLike(obj));
+    if (obj && obj->isTenured())
         obj->asTenured().readBarrier(&obj->asTenured());
 }
 
 /* static */ MOZ_ALWAYS_INLINE void
 JSObject::writeBarrierPre(JSObject* obj)
 {
-    if (!isNullLike(obj) && obj->isTenured())
+    MOZ_ASSERT_IF(obj, !isNullLike(obj));
+    if (obj && obj->isTenured())
         obj->asTenured().writeBarrierPre(&obj->asTenured());
 }
 
 /* static */ MOZ_ALWAYS_INLINE void
 JSObject::writeBarrierPost(void* cellp, JSObject* prev, JSObject* next)
 {
     MOZ_ASSERT(cellp);
+    MOZ_ASSERT_IF(next, !IsNullTaggedPointer(next));
+    MOZ_ASSERT_IF(prev, !IsNullTaggedPointer(prev));
 
     // If the target needs an entry, add it.
     js::gc::StoreBuffer* buffer;
-    if (!IsNullTaggedPointer(next) && (buffer = next->storeBuffer())) {
+    if (next && (buffer = next->storeBuffer())) {
         // If we know that the prev has already inserted an entry, we can skip
         // doing the lookup to add the new entry.
-        if (!IsNullTaggedPointer(prev) && prev->storeBuffer()) {
+        if (prev && prev->storeBuffer()) {
             buffer->assertHasCellEdge(static_cast<js::gc::Cell**>(cellp));
             return;
         }
         buffer->putCellFromAnyThread(static_cast<js::gc::Cell**>(cellp));
         return;
     }
 
     // Remove the prev entry if the new value does not need it.
-    if (!IsNullTaggedPointer(prev) && (buffer = prev->storeBuffer()))
+    if (prev && (buffer = prev->storeBuffer()))
         buffer->unputCellFromAnyThread(static_cast<js::gc::Cell**>(cellp));
 }
 
 namespace js {
 
 inline bool
 IsCallable(const Value& v)
 {
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -317,16 +317,17 @@ UNIFIED_SOURCES += [
     'vm/SharedArrayObject.cpp',
     'vm/SharedTypedArrayObject.cpp',
     'vm/SPSProfiler.cpp',
     'vm/Stack.cpp',
     'vm/String.cpp',
     'vm/StringBuffer.cpp',
     'vm/StructuredClone.cpp',
     'vm/Symbol.cpp',
+    'vm/TaggedProto.cpp',
     'vm/Time.cpp',
     'vm/TypedArrayObject.cpp',
     'vm/TypeInference.cpp',
     'vm/UbiNode.cpp',
     'vm/UbiNodeCensus.cpp',
     'vm/UnboxedObject.cpp',
     'vm/Unicode.cpp',
     'vm/Value.cpp',
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Class/superPropBasicChain.js
@@ -0,0 +1,18 @@
+var test = `
+
+var o = {
+    access() {
+        super.foo.bar;
+    }
+};
+
+// Delazify
+assertThrowsInstanceOf(o.access, TypeError);
+
+`;
+
+if (classesEnabled())
+    eval(test);
+
+if (typeof reportCompare === 'function')
+    reportCompare(0,0,"OK");
--- a/js/src/vm/ObjectGroup.cpp
+++ b/js/src/vm/ObjectGroup.cpp
@@ -31,17 +31,17 @@ ObjectGroup::ObjectGroup(const Class* cl
                          ObjectGroupFlags initialFlags)
 {
     PodZero(this);
 
     /* Inner objects may not appear on prototype chains. */
     MOZ_ASSERT_IF(proto.isObject(), !proto.toObject()->getClass()->ext.outerObject);
 
     this->clasp_ = clasp;
-    this->proto_ = proto.raw();
+    this->proto_ = proto;
     this->compartment_ = comp;
     this->flags_ = initialFlags;
 
     setGeneration(zone()->types.generation);
 }
 
 void
 ObjectGroup::finalize(FreeOp* fop)
@@ -53,18 +53,19 @@ ObjectGroup::finalize(FreeOp* fop)
     if (maybePreliminaryObjectsDontCheckGeneration())
         maybePreliminaryObjectsDontCheckGeneration()->clear();
     fop->delete_(maybePreliminaryObjectsDontCheckGeneration());
 }
 
 void
 ObjectGroup::setProtoUnchecked(TaggedProto proto)
 {
-    proto_ = proto.raw();
-    MOZ_ASSERT_IF(proto_ && proto_->isNative(), proto_->isDelegate());
+    proto_ = proto;
+    MOZ_ASSERT_IF(proto_.isObject() && proto_.toObject()->isNative(),
+                  proto_.toObject()->isDelegate());
 }
 
 void
 ObjectGroup::setProto(TaggedProto proto)
 {
     MOZ_ASSERT(singleton());
     setProtoUnchecked(proto);
 }
--- a/js/src/vm/ObjectGroup.h
+++ b/js/src/vm/ObjectGroup.h
@@ -7,119 +7,30 @@
 #ifndef vm_ObjectGroup_h
 #define vm_ObjectGroup_h
 
 #include "jsbytecode.h"
 #include "jsfriendapi.h"
 
 #include "ds/IdValuePair.h"
 #include "gc/Barrier.h"
+#include "vm/TaggedProto.h"
 #include "vm/TypeInference.h"
 
 namespace js {
 
 class TypeDescr;
 class UnboxedLayout;
 
 class PreliminaryObjectArrayWithTemplate;
 class TypeNewScript;
 class HeapTypeSet;
 class AutoClearTypeInferenceStateOnOOM;
 class CompilerConstraintList;
 
-// Information about an object prototype, which can be either a particular
-// object, null, or a lazily generated object. The latter is only used by
-// certain kinds of proxies.
-class TaggedProto
-{
-  public:
-    static JSObject * const LazyProto;
-
-    TaggedProto() : proto(nullptr) {}
-    explicit TaggedProto(JSObject* proto) : proto(proto) {}
-
-    uintptr_t toWord() const { return uintptr_t(proto); }
-
-    bool isLazy() const {
-        return proto == LazyProto;
-    }
-    bool isObject() const {
-        /* Skip nullptr and LazyProto. */
-        return uintptr_t(proto) > uintptr_t(TaggedProto::LazyProto);
-    }
-    JSObject* toObject() const {
-        MOZ_ASSERT(isObject());
-        return proto;
-    }
-    JSObject* toObjectOrNull() const {
-        MOZ_ASSERT(!proto || isObject());
-        return proto;
-    }
-    JSObject* raw() const { return proto; }
-
-    bool operator ==(const TaggedProto& other) { return proto == other.proto; }
-    bool operator !=(const TaggedProto& other) { return proto != other.proto; }
-
-  private:
-    JSObject* proto;
-};
-
-template <>
-struct RootKind<TaggedProto>
-{
-    static ThingRootKind rootKind() { return THING_ROOT_OBJECT; }
-};
-
-template <> struct GCMethods<const TaggedProto>
-{
-    static TaggedProto initial() { return TaggedProto(); }
-};
-
-template <> struct GCMethods<TaggedProto>
-{
-    static TaggedProto initial() { return TaggedProto(); }
-};
-
-template<class Outer>
-class TaggedProtoOperations
-{
-    const TaggedProto& value() const {
-        return static_cast<const Outer*>(this)->get();
-    }
-
-  public:
-    uintptr_t toWord() const { return value().toWord(); }
-    inline bool isLazy() const { return value().isLazy(); }
-    inline bool isObject() const { return value().isObject(); }
-    inline JSObject* toObject() const { return value().toObject(); }
-    inline JSObject* toObjectOrNull() const { return value().toObjectOrNull(); }
-    JSObject* raw() const { return value().raw(); }
-};
-
-template <>
-class HandleBase<TaggedProto> : public TaggedProtoOperations<Handle<TaggedProto> >
-{};
-
-template <>
-class RootedBase<TaggedProto> : public TaggedProtoOperations<Rooted<TaggedProto> >
-{};
-
-// Since JSObject pointers are either nullptr or a valid object and since the
-// object layout of TaggedProto is identical to a bare object pointer, we can
-// safely treat a pointer to an already-rooted object (e.g. HandleObject) as a
-// pointer to a TaggedProto.
-inline Handle<TaggedProto>
-AsTaggedProto(HandleObject obj)
-{
-    static_assert(sizeof(JSObject*) == sizeof(TaggedProto),
-                  "TaggedProto must be binary compatible with JSObject");
-    return Handle<TaggedProto>::fromMarkedLocation(
-            reinterpret_cast<TaggedProto const*>(obj.address()));
-}
-
 namespace gc {
 void MergeCompartments(JSCompartment* source, JSCompartment* target);
 } // namespace gc
 
 /*
  * The NewObjectKind allows an allocation site to specify the type properties
  * and lifetime requirements that must be fixed at allocation time.
  */
@@ -167,37 +78,38 @@ enum NewObjectKind {
 class ObjectGroup : public gc::TenuredCell
 {
     friend void gc::MergeCompartments(JSCompartment* source, JSCompartment* target);
 
     /* Class shared by objects in this group. */
     const Class* clasp_;
 
     /* Prototype shared by objects in this group. */
-    HeapPtrObject proto_;
+    HeapPtr<TaggedProto> proto_;
 
     /* Compartment shared by objects in this group. */
     JSCompartment* compartment_;
 
   public:
 
     const Class* clasp() const {
         return clasp_;
     }
 
     void setClasp(const Class* clasp) {
         clasp_ = clasp;
     }
 
-    TaggedProto proto() const {
-        return TaggedProto(proto_);
+    const HeapPtr<TaggedProto>& proto() const {
+        return proto_;
     }
 
-    // For use during marking, don't call otherwise.
-    HeapPtrObject& protoRaw() { return proto_; }
+    HeapPtr<TaggedProto>& proto() {
+        return proto_;
+    }
 
     void setProto(TaggedProto proto);
     void setProtoUnchecked(TaggedProto proto);
 
     bool singleton() const {
         return flagsDontCheckGeneration() & OBJECT_FLAG_SINGLETON;
     }
 
--- a/js/src/vm/String.cpp
+++ b/js/src/vm/String.cpp
@@ -9,16 +9,17 @@
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/RangedPtr.h"
 #include "mozilla/TypeTraits.h"
 
 #include "gc/Marking.h"
 #include "js/UbiNode.h"
+#include "vm/SPSProfiler.h"
 
 #include "jscntxtinlines.h"
 #include "jscompartmentinlines.h"
 
 using namespace js;
 
 using mozilla::IsSame;
 using mozilla::PodCopy;
@@ -549,16 +550,20 @@ JSRope::flattenInternal(ExclusiveContext
     if (hasTwoByteChars())
         return flattenInternal<b, char16_t>(maybecx);
     return flattenInternal<b, Latin1Char>(maybecx);
 }
 
 JSFlatString*
 JSRope::flatten(ExclusiveContext* maybecx)
 {
+    mozilla::Maybe<AutoSPSEntry> sps;
+    if (maybecx && maybecx->isJSContext())
+        sps.emplace(maybecx->asJSContext()->runtime(), "JSRope::flatten");
+
     if (zone()->needsIncrementalBarrier())
         return flattenInternal<WithIncrementalBarrier>(maybecx);
     return flattenInternal<NoBarrier>(maybecx);
 }
 
 template <AllowGC allowGC>
 JSString*
 js::ConcatStrings(ExclusiveContext* cx,
new file mode 100644
--- /dev/null
+++ b/js/src/vm/TaggedProto.cpp
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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/. */
+
+/* static */ void
+js::InternalGCMethods<TaggedProto>::preBarrier(TaggedProto& proto)
+{
+    InternalGCMethods<JSObject*>::preBarrier(proto.toObjectOrNull());
+}
+
+/* static */ void
+js::InternalGCMethods<TaggedProto>::postBarrier(TaggedProto* vp, TaggedProto prev, TaggedProto next)
+{
+    JSObject* prevObj = prev.isObject() ? prev.toObject() : nullptr;
+    JSObject* nextObj = next.isObject() ? next.toObject() : nullptr;
+    InternalGCMethods<JSObject*>::postBarrier(reinterpret_cast<JSObject**>(vp), prevObj,
+                                              nextObj);
+}
new file mode 100644
--- /dev/null
+++ b/js/src/vm/TaggedProto.h
@@ -0,0 +1,130 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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 vm_TaggedProto_h
+#define vm_TaggedProto_h
+
+#include "gc/Tracer.h"
+
+namespace js {
+
+// Information about an object prototype, which can be either a particular
+// object, null, or a lazily generated object. The latter is only used by
+// certain kinds of proxies.
+class TaggedProto : public JS::Traceable
+{
+  public:
+    static JSObject * const LazyProto;
+
+    TaggedProto() : proto(nullptr) {}
+    explicit TaggedProto(JSObject* proto) : proto(proto) {}
+
+    uintptr_t toWord() const { return uintptr_t(proto); }
+
+    bool isLazy() const {
+        return proto == LazyProto;
+    }
+    bool isObject() const {
+        /* Skip nullptr and LazyProto. */
+        return uintptr_t(proto) > uintptr_t(TaggedProto::LazyProto);
+    }
+    JSObject* toObject() const {
+        MOZ_ASSERT(isObject());
+        return proto;
+    }
+    JSObject* toObjectOrNull() const {
+        MOZ_ASSERT(!proto || isObject());
+        return proto;
+    }
+    JSObject* raw() const { return proto; }
+
+    bool operator ==(const TaggedProto& other) const { return proto == other.proto; }
+    bool operator !=(const TaggedProto& other) const { return proto != other.proto; }
+
+    static void trace(TaggedProto* protop, JSTracer* trc) {
+        TraceManuallyBarrieredEdge(trc, protop, "TaggedProto");
+    }
+
+  private:
+    JSObject* proto;
+};
+
+template <> struct GCMethods<TaggedProto>
+{
+    static TaggedProto initial() { return TaggedProto(); }
+};
+
+template <> struct InternalGCMethods<TaggedProto>
+{
+    static void preBarrier(TaggedProto& proto);
+
+    static void postBarrier(TaggedProto* vp, TaggedProto prev, TaggedProto next);
+
+    static bool isMarkableTaggedPointer(TaggedProto proto) {
+        return proto.isObject();
+    }
+
+    static bool isMarkable(TaggedProto proto) {
+        return proto.isObject();
+    }
+};
+
+template<class Outer>
+class TaggedProtoOperations
+{
+    const TaggedProto& value() const {
+        return static_cast<const Outer*>(this)->get();
+    }
+
+  public:
+    uintptr_t toWord() const { return value().toWord(); }
+    inline bool isLazy() const { return value().isLazy(); }
+    inline bool isObject() const { return value().isObject(); }
+    inline JSObject* toObject() const { return value().toObject(); }
+    inline JSObject* toObjectOrNull() const { return value().toObjectOrNull(); }
+    JSObject* raw() const { return value().raw(); }
+};
+
+template <>
+class HandleBase<TaggedProto> : public TaggedProtoOperations<Handle<TaggedProto>>
+{};
+
+template <>
+class RootedBase<TaggedProto> : public TaggedProtoOperations<Rooted<TaggedProto>>
+{};
+
+template <>
+class BarrieredBaseMixins<TaggedProto> : public TaggedProtoOperations<HeapPtr<TaggedProto>>
+{};
+
+// If the TaggedProto is a JSObject pointer, convert to that type and call |f|
+// with the pointer. If the TaggedProto is lazy, calls F::defaultValue.
+template <typename F, typename... Args>
+auto
+DispatchTaggedProtoTyped(F f, TaggedProto& proto, Args&&... args)
+  -> decltype(f(static_cast<JSObject*>(nullptr), mozilla::Forward<Args>(args)...))
+{
+    if (proto.isObject())
+        return f(proto.toObject(), mozilla::Forward<Args>(args)...);
+    return F::defaultValue(proto);
+}
+
+// Since JSObject pointers are either nullptr or a valid object and since the
+// object layout of TaggedProto is identical to a bare object pointer, we can
+// safely treat a pointer to an already-rooted object (e.g. HandleObject) as a
+// pointer to a TaggedProto.
+inline Handle<TaggedProto>
+AsTaggedProto(HandleObject obj)
+{
+    static_assert(sizeof(JSObject*) == sizeof(TaggedProto),
+                  "TaggedProto must be binary compatible with JSObject");
+    return Handle<TaggedProto>::fromMarkedLocation(
+            reinterpret_cast<TaggedProto const*>(obj.address()));
+}
+
+} // namespace js
+
+#endif // vm_TaggedProto_h
--- a/js/src/vm/TypeInference.h
+++ b/js/src/vm/TypeInference.h
@@ -18,26 +18,26 @@
 #include "ds/IdValuePair.h"
 #include "ds/LifoAlloc.h"
 #include "gc/Barrier.h"
 #include "gc/Marking.h"
 #include "jit/IonTypes.h"
 #include "js/UbiNode.h"
 #include "js/Utility.h"
 #include "js/Vector.h"
+#include "vm/TaggedProto.h"
 
 namespace js {
 
 namespace jit {
     struct IonScript;
     class JitAllocPolicy;
     class TempAllocator;
 } // namespace jit
 
-class TaggedProto;
 struct TypeZone;
 class TypeConstraint;
 class TypeNewScript;
 class CompilerConstraintList;
 class HeapTypeSetKey;
 
 /*
  * Type inference memory management overview.
--- a/layout/base/AccessibleCaret.cpp
+++ b/layout/base/AccessibleCaret.cpp
@@ -27,62 +27,76 @@ using namespace dom;
 
 NS_IMPL_ISUPPORTS(AccessibleCaret::DummyTouchListener, nsIDOMEventListener)
 
 float AccessibleCaret::sWidth = 0.0f;
 float AccessibleCaret::sHeight = 0.0f;
 float AccessibleCaret::sMarginLeft = 0.0f;
 float AccessibleCaret::sBarWidth = 0.0f;
 
+#define AC_PROCESS_ENUM_TO_STREAM(e) case(e): aStream << #e; break;
 std::ostream&
 operator<<(std::ostream& aStream, const AccessibleCaret::Appearance& aAppearance)
 {
   using Appearance = AccessibleCaret::Appearance;
-
-#define AC_PROCESS_APPEARANCE_TO_STREAM(e) case(e): aStream << #e; break;
   switch (aAppearance) {
-    AC_PROCESS_APPEARANCE_TO_STREAM(Appearance::None);
-    AC_PROCESS_APPEARANCE_TO_STREAM(Appearance::Normal);
-    AC_PROCESS_APPEARANCE_TO_STREAM(Appearance::NormalNotShown);
-    AC_PROCESS_APPEARANCE_TO_STREAM(Appearance::Left);
-    AC_PROCESS_APPEARANCE_TO_STREAM(Appearance::Right);
+    AC_PROCESS_ENUM_TO_STREAM(Appearance::None);
+    AC_PROCESS_ENUM_TO_STREAM(Appearance::Normal);
+    AC_PROCESS_ENUM_TO_STREAM(Appearance::NormalNotShown);
+    AC_PROCESS_ENUM_TO_STREAM(Appearance::Left);
+    AC_PROCESS_ENUM_TO_STREAM(Appearance::Right);
   }
-#undef AC_PROCESS_APPEARANCE_TO_STREAM
-
   return aStream;
 }
 
+std::ostream&
+operator<<(std::ostream& aStream,
+           const AccessibleCaret::PositionChangedResult& aResult)
+{
+  using PositionChangedResult = AccessibleCaret::PositionChangedResult;
+  switch (aResult) {
+    AC_PROCESS_ENUM_TO_STREAM(PositionChangedResult::NotChanged);
+    AC_PROCESS_ENUM_TO_STREAM(PositionChangedResult::Changed);
+    AC_PROCESS_ENUM_TO_STREAM(PositionChangedResult::Invisible);
+  }
+  return aStream;
+}
+#undef AC_PROCESS_ENUM_TO_STREAM
+
 // -----------------------------------------------------------------------------
 // Implementation of AccessibleCaret methods
 
 AccessibleCaret::AccessibleCaret(nsIPresShell* aPresShell)
   : mPresShell(aPresShell)
 {
   // Check all resources required.
-  MOZ_ASSERT(mPresShell);
-  MOZ_ASSERT(RootFrame());
-  MOZ_ASSERT(mPresShell->GetDocument());
-  MOZ_ASSERT(mPresShell->GetCanvasFrame());
-  MOZ_ASSERT(mPresShell->GetCanvasFrame()->GetCustomContentContainer());
+  if (mPresShell) {
+    MOZ_ASSERT(RootFrame());
+    MOZ_ASSERT(mPresShell->GetDocument());
+    MOZ_ASSERT(mPresShell->GetCanvasFrame());
+    MOZ_ASSERT(mPresShell->GetCanvasFrame()->GetCustomContentContainer());
 
-  InjectCaretElement(mPresShell->GetDocument());
+    InjectCaretElement(mPresShell->GetDocument());
+  }
 
   static bool prefsAdded = false;
   if (!prefsAdded) {
     Preferences::AddFloatVarCache(&sWidth, "layout.accessiblecaret.width");
     Preferences::AddFloatVarCache(&sHeight, "layout.accessiblecaret.height");
     Preferences::AddFloatVarCache(&sMarginLeft, "layout.accessiblecaret.margin-left");
     Preferences::AddFloatVarCache(&sBarWidth, "layout.accessiblecaret.bar.width");
     prefsAdded = true;
   }
 }
 
 AccessibleCaret::~AccessibleCaret()
 {
-  RemoveCaretElement(mPresShell->GetDocument());
+  if (mPresShell) {
+    RemoveCaretElement(mPresShell->GetDocument());
+  }
 }
 
 void
 AccessibleCaret::SetAppearance(Appearance aAppearance)
 {
   if (mAppearance == aAppearance) {
     return;
   }
--- a/layout/base/AccessibleCaret.h
+++ b/layout/base/AccessibleCaret.h
@@ -32,21 +32,21 @@ namespace mozilla {
 //
 // All the rect or point are relative to root frame except being specified
 // explicitly.
 //
 // None of the methods in AccessibleCaret will flush layout or style. To ensure
 // that SetPosition() works correctly, the caller must make sure the layout is
 // up to date.
 //
-class AccessibleCaret final
+class AccessibleCaret
 {
 public:
   explicit AccessibleCaret(nsIPresShell* aPresShell);
-  ~AccessibleCaret();
+  virtual ~AccessibleCaret();
 
   // This enumeration representing the visibility and visual style of an
   // AccessibleCaret.
   //
   // Use SetAppearance() to change the appearance, and use GetAppearance() to
   // get the current appearance.
   enum class Appearance : uint8_t {
     // Do not display the caret at all.
@@ -64,22 +64,25 @@ public:
 
     // Display the caret which is tilted to the left.
     Left,
 
     // Display the caret which is tilted to the right.
     Right
   };
 
+  friend std::ostream& operator<<(std::ostream& aStream,
+                                  const Appearance& aAppearance);
+
   Appearance GetAppearance() const
   {
     return mAppearance;
   }
 
-  void SetAppearance(Appearance aAppearance);
+  virtual void SetAppearance(Appearance aAppearance);
 
   // Return true if current appearance is either Normal, NormalNotShown, Left,
   // or Right.
   bool IsLogicallyVisible() const
   {
       return mAppearance != Appearance::None;
   }
 
@@ -87,30 +90,34 @@ public:
   bool IsVisuallyVisible() const
   {
     return (mAppearance != Appearance::None) &&
            (mAppearance != Appearance::NormalNotShown);
   }
 
   // Set true to enable the "Text Selection Bar" described in "Text Selection
   // Visual Spec" in bug 921965.
-  void SetSelectionBarEnabled(bool aEnabled);
+  virtual void SetSelectionBarEnabled(bool aEnabled);
 
   // This enumeration representing the result returned by SetPosition().
   enum class PositionChangedResult : uint8_t {
     // Position is not changed.
     NotChanged,
 
     // Position is changed.
     Changed,
 
     // Position is out of scroll port.
     Invisible
   };
-  PositionChangedResult SetPosition(nsIFrame* aFrame, int32_t aOffset);
+
+  friend std::ostream& operator<<(std::ostream& aStream,
+                                  const PositionChangedResult& aResult);
+
+  virtual PositionChangedResult SetPosition(nsIFrame* aFrame, int32_t aOffset);
 
   // Does two AccessibleCarets overlap?
   bool Intersects(const AccessibleCaret& aCaret) const;
 
   // Is the point within the caret's rect? The point should be relative to root
   // frame.
   bool Contains(const nsPoint& aPoint) const;
 
@@ -122,17 +129,17 @@ public:
   }
 
   // Element for 'Intersects' test. Container of image and bar elements.
   dom::Element* CaretElement() const
   {
     return mCaretElementHolder->GetContentNode();
   }
 
-private:
+protected:
   // Argument aRect should be relative to CustomContentContainerFrame().
   void SetCaretElementStyle(const nsRect& aRect);
   void SetSelectionBarElementStyle(const nsRect& aRect);
 
   // Get current zoom level.
   float GetZoomLevel();
 
   // Element which contains the caret image for 'Contains' test.
@@ -208,11 +215,17 @@ private:
   // Static class variables
   static float sWidth;
   static float sHeight;
   static float sMarginLeft;
   static float sBarWidth;
 
 }; // class AccessibleCaret
 
+std::ostream& operator<<(std::ostream& aStream,
+                         const AccessibleCaret::Appearance& aAppearance);
+
+std::ostream& operator<<(std::ostream& aStream,
+                         const AccessibleCaret::PositionChangedResult& aResult);
+
 } // namespace mozilla
 
 #endif // AccessibleCaret_h__
--- a/layout/base/AccessibleCaretManager.cpp
+++ b/layout/base/AccessibleCaretManager.cpp
@@ -29,16 +29,42 @@ namespace mozilla {
 #undef AC_LOGV
 #define AC_LOGV(message, ...)                                                  \
   AC_LOGV_BASE("AccessibleCaretManager (%p): " message, this, ##__VA_ARGS__);
 
 using namespace dom;
 using Appearance = AccessibleCaret::Appearance;
 using PositionChangedResult = AccessibleCaret::PositionChangedResult;
 
+#define AC_PROCESS_ENUM_TO_STREAM(e) case(e): aStream << #e; break;
+std::ostream&
+operator<<(std::ostream& aStream,
+           const AccessibleCaretManager::CaretMode& aCaretMode)
+{
+  using CaretMode = AccessibleCaretManager::CaretMode;
+  switch (aCaretMode) {
+    AC_PROCESS_ENUM_TO_STREAM(CaretMode::None);
+    AC_PROCESS_ENUM_TO_STREAM(CaretMode::Cursor);
+    AC_PROCESS_ENUM_TO_STREAM(CaretMode::Selection);
+  }
+  return aStream;
+}
+
+std::ostream& operator<<(std::ostream& aStream,
+                         const AccessibleCaretManager::UpdateCaretsHint& aHint)
+{
+  using UpdateCaretsHint = AccessibleCaretManager::UpdateCaretsHint;
+  switch (aHint) {
+    AC_PROCESS_ENUM_TO_STREAM(UpdateCaretsHint::Default);
+    AC_PROCESS_ENUM_TO_STREAM(UpdateCaretsHint::RespectOldAppearance);
+  }
+  return aStream;
+}
+#undef AC_PROCESS_ENUM_TO_STREAM
+
 AccessibleCaretManager::AccessibleCaretManager(nsIPresShell* aPresShell)
   : mPresShell(aPresShell)
 {
   if (mPresShell) {
     mFirstCaret = MakeUnique<AccessibleCaret>(mPresShell);
     mSecondCaret = MakeUnique<AccessibleCaret>(mPresShell);
 
     mCaretTimeoutTimer = do_CreateInstance("@mozilla.org/timer;1");
@@ -94,69 +120,94 @@ AccessibleCaretManager::HideCarets()
     DispatchCaretStateChangedEvent(CaretChangedReason::Visibilitychange);
     CancelCaretTimeoutTimer();
   }
 }
 
 void
 AccessibleCaretManager::UpdateCarets(UpdateCaretsHint aHint)
 {
-  mCaretMode = GetCaretMode();
+  mLastUpdateCaretMode = GetCaretMode();
 
-  switch (mCaretMode) {
+  switch (mLastUpdateCaretMode) {
   case CaretMode::None:
     HideCarets();
     break;
   case CaretMode::Cursor:
     UpdateCaretsForCursorMode(aHint);
     break;
   case CaretMode::Selection:
     UpdateCaretsForSelectionMode(aHint);
     break;
   }
 }
 
-void
-AccessibleCaretManager::UpdateCaretsForCursorMode(UpdateCaretsHint aHint)
+bool
+AccessibleCaretManager::IsCaretDisplayableInCursorMode(nsIFrame** aOutFrame,
+                                                       int32_t* aOutOffset) const
 {
-  AC_LOG("%s, selection: %p", __FUNCTION__, GetSelection());
-
   nsRefPtr<nsCaret> caret = mPresShell->GetCaret();
   if (!caret || !caret->IsVisible()) {
-    HideCarets();
-    return;
+    return false;
   }
 
   int32_t offset = 0;
   nsIFrame* frame = nsCaret::GetFrameAndOffset(GetSelection(), nullptr, 0, &offset);
 
   if (!frame) {
-    HideCarets();
-    return;
+    return false;
+  }
+
+  if (!GetEditingHostForFrame(frame)) {
+    return false;
+  }
+
+  if (aOutFrame) {
+    *aOutFrame = frame;
+  }
+
+  if (aOutOffset) {
+    *aOutOffset = offset;
   }
 
-  Element* editingHost = frame->GetContent()->GetEditingHost();
-  if (!editingHost) {
+  return true;
+}
+
+bool
+AccessibleCaretManager::HasNonEmptyTextContent(nsINode* aNode) const
+{
+  return nsContentUtils::HasNonEmptyTextContent(
+           aNode, nsContentUtils::eRecurseIntoChildren);
+}
+
+
+void
+AccessibleCaretManager::UpdateCaretsForCursorMode(UpdateCaretsHint aHint)
+{
+  AC_LOG("%s, selection: %p", __FUNCTION__, GetSelection());
+
+  int32_t offset = 0;
+  nsIFrame* frame = nullptr;
+  if (!IsCaretDisplayableInCursorMode(&frame, &offset)) {
     HideCarets();
     return;
   }
 
   bool oldSecondCaretVisible = mSecondCaret->IsLogicallyVisible();
   PositionChangedResult result = mFirstCaret->SetPosition(frame, offset);
 
   switch (result) {
     case PositionChangedResult::NotChanged:
       // Do nothing
       break;
 
     case PositionChangedResult::Changed:
       switch (aHint) {
         case UpdateCaretsHint::Default:
-          if (nsContentUtils::HasNonEmptyTextContent(
-                editingHost, nsContentUtils::eRecurseIntoChildren)) {
+          if (HasNonEmptyTextContent(GetEditingHostForFrame(frame))) {
             mFirstCaret->SetAppearance(Appearance::Normal);
           } else {
             mFirstCaret->SetAppearance(Appearance::NormalNotShown);
           }
           break;
 
         case UpdateCaretsHint::RespectOldAppearance:
           // Do nothing to prevent the appearance of the caret being
@@ -187,18 +238,18 @@ AccessibleCaretManager::UpdateCaretsForS
   AC_LOG("%s: selection: %p", __FUNCTION__, GetSelection());
 
   int32_t startOffset = 0;
   nsIFrame* startFrame = FindFirstNodeWithFrame(false, &startOffset);
 
   int32_t endOffset = 0;
   nsIFrame* endFrame = FindFirstNodeWithFrame(true, &endOffset);
 
-  if (!startFrame || !endFrame ||
-      nsLayoutUtils::CompareTreePosition(startFrame, endFrame) > 0) {
+  if (!CompareTreePosition(startFrame, endFrame)) {
+    // XXX: Do we really have to hide carets if this condition isn't satisfied?
     HideCarets();
     return;
   }
 
   auto updateSingleCaret = [](AccessibleCaret* aCaret, nsIFrame* aFrame,
                               int32_t aOffset) -> PositionChangedResult
   {
     PositionChangedResult result = aCaret->SetPosition(aFrame, aOffset);
@@ -223,17 +274,17 @@ AccessibleCaretManager::UpdateCaretsForS
   PositionChangedResult firstCaretResult =
     updateSingleCaret(mFirstCaret.get(), startFrame, startOffset);
   PositionChangedResult secondCaretResult =
     updateSingleCaret(mSecondCaret.get(), endFrame, endOffset);
 
   if (firstCaretResult == PositionChangedResult::Changed ||
       secondCaretResult == PositionChangedResult::Changed) {
     // Flush layout to make the carets intersection correct.
-    mPresShell->FlushPendingNotifications(Flush_Layout);
+    FlushLayout();
   }
 
   UpdateCaretsForTilt();
 
   if ((firstCaretResult == PositionChangedResult::Changed ||
        secondCaretResult == PositionChangedResult::Changed ||
        firstCaretResult == PositionChangedResult::Invisible ||
        secondCaretResult == PositionChangedResult::Invisible) &&
@@ -350,20 +401,19 @@ AccessibleCaretManager::SelectWordOrShor
 #ifdef DEBUG_FRAME_DUMP
   AC_LOG("%s: Found %s under (%d, %d)", __FUNCTION__, ptFrame->ListTag().get(),
          aPoint.x, aPoint.y);
   AC_LOG("%s: Found %s focusable", __FUNCTION__,
          focusableFrame ? focusableFrame->ListTag().get() : "no frame");
 #endif
 
   // Firstly check long press on an empty editable content.
-  Element* newFocusEditingHost = ptFrame->GetContent()->GetEditingHost();
+  Element* newFocusEditingHost = GetEditingHostForFrame(ptFrame);
   if (focusableFrame && newFocusEditingHost &&
-      !nsContentUtils::HasNonEmptyTextContent(
-        newFocusEditingHost, nsContentUtils::eRecurseIntoChildren)) {
+      !HasNonEmptyTextContent(newFocusEditingHost)) {
     ChangeFocusToOrClearOldFocus(focusableFrame);
     // We need to update carets to get correct information before dispatching
     // CaretStateChangedEvent.
     UpdateCarets();
     DispatchCaretStateChangedEvent(CaretChangedReason::Longpressonemptycontent);
     return NS_OK;
   }
 
@@ -397,44 +447,44 @@ AccessibleCaretManager::OnScrollStart()
   AC_LOG("%s", __FUNCTION__);
 
   HideCarets();
 }
 
 void
 AccessibleCaretManager::OnScrollEnd()
 {
-  if (mCaretMode != GetCaretMode()) {
+  if (mLastUpdateCaretMode != GetCaretMode()) {
     return;
   }
 
   if (GetCaretMode() == CaretMode::Cursor) {
     AC_LOG("%s: HideCarets()", __FUNCTION__);
     HideCarets();
   } else {
     AC_LOG("%s: UpdateCarets()", __FUNCTION__);
     UpdateCarets();
   }
 }
 
 void
 AccessibleCaretManager::OnScrollPositionChanged()
 {
-  if (mCaretMode != GetCaretMode()) {
+  if (mLastUpdateCaretMode != GetCaretMode()) {
     return;
   }
 
   AC_LOG("%s: UpdateCarets(RespectOldAppearance)", __FUNCTION__);
   UpdateCarets(UpdateCaretsHint::RespectOldAppearance);
 }
 
 void
 AccessibleCaretManager::OnReflow()
 {
-  if (mCaretMode != GetCaretMode()) {
+  if (mLastUpdateCaretMode != GetCaretMode()) {
     return;
   }
 
   if (mFirstCaret->IsVisuallyVisible() || mSecondCaret->IsVisuallyVisible()) {
     AC_LOG("%s: UpdateCarets()", __FUNCTION__);
     UpdateCarets();
   }
 }
@@ -450,38 +500,37 @@ void
 AccessibleCaretManager::OnKeyboardEvent()
 {
   if (GetCaretMode() == CaretMode::Cursor) {
     AC_LOG("%s: HideCarets()", __FUNCTION__);
     HideCarets();
   }
 }
 
-nsIContent*
-AccessibleCaretManager::GetFocusedContent() const
-{
-  nsFocusManager* fm = nsFocusManager::GetFocusManager();
-  MOZ_ASSERT(fm);
-  return fm->GetFocusedContent();
-}
-
 Selection*
 AccessibleCaretManager::GetSelection() const
 {
   nsRefPtr<nsFrameSelection> fs = GetFrameSelection();
   if (!fs) {
     return nullptr;
   }
   return fs->GetSelection(nsISelectionController::SELECTION_NORMAL);
 }
 
 already_AddRefed<nsFrameSelection>
 AccessibleCaretManager::GetFrameSelection() const
 {
-  nsIContent* focusedContent = GetFocusedContent();
+  if (!mPresShell) {
+    return nullptr;
+  }
+
+  nsFocusManager* fm = nsFocusManager::GetFocusManager();
+  MOZ_ASSERT(fm);
+
+  nsIContent* focusedContent = fm->GetFocusedContent();
   if (focusedContent) {
     nsIFrame* focusFrame = focusedContent->GetPrimaryFrame();
     if (!focusFrame) {
       return nullptr;
     }
 
     // Prevent us from touching the nsFrameSelection associated with other
     // PresShell.
@@ -492,16 +541,32 @@ AccessibleCaretManager::GetFrameSelectio
 
     return fs.forget();
   } else {
     // For non-editable content
     return mPresShell->FrameSelection();
   }
 }
 
+Element*
+AccessibleCaretManager::GetEditingHostForFrame(nsIFrame* aFrame) const
+{
+  if (!aFrame) {
+    return nullptr;
+  }
+
+  auto content = aFrame->GetContent();
+  if (!content) {
+    return nullptr;
+  }
+
+  return content->GetEditingHost();
+}
+
+
 AccessibleCaretManager::CaretMode
 AccessibleCaretManager::GetCaretMode() const
 {
   Selection* selection = GetSelection();
   if (!selection) {
     return CaretMode::None;
   }
 
@@ -590,16 +655,24 @@ AccessibleCaretManager::ClearMaintainedS
   // Selection made by double-clicking for example will maintain the original
   // word selection. We should clear it so that we can drag caret freely.
   nsRefPtr<nsFrameSelection> fs = GetFrameSelection();
   if (fs) {
     fs->MaintainSelection(eSelectNoAmount);
   }
 }
 
+void
+AccessibleCaretManager::FlushLayout() const
+{
+  if (mPresShell) {
+    mPresShell->FlushPendingNotifications(Flush_Layout);
+  }
+}
+
 nsIFrame*
 AccessibleCaretManager::FindFirstNodeWithFrame(bool aBackward,
                                                int32_t* aOutOffset) const
 {
   if (!mPresShell) {
     return nullptr;
   }
 
@@ -729,16 +802,24 @@ AccessibleCaretManager::CompareRangeWith
     aOffsets.content = pos.mResultContent;
     aOffsets.offset = pos.mContentOffset;
     aOffsets.secondaryOffset = pos.mContentOffset;
   }
 
   return true;
 }
 
+bool
+AccessibleCaretManager::CompareTreePosition(nsIFrame* aStartFrame,
+                                            nsIFrame* aEndFrame) const
+{
+  return (aStartFrame && aEndFrame &&
+          nsLayoutUtils::CompareTreePosition(aStartFrame, aEndFrame) <= 0);
+}
+
 nsresult
 AccessibleCaretManager::DragCaretInternal(const nsPoint& aPoint)
 {
   if (!mPresShell) {
     return NS_ERROR_NULL_POINTER;
   }
 
   nsIFrame* rootFrame = mPresShell->GetRootFrame();
@@ -889,18 +970,18 @@ AccessibleCaretManager::CancelCaretTimeo
   }
 }
 
 void
 AccessibleCaretManager::DispatchCaretStateChangedEvent(CaretChangedReason aReason) const
 {
   // Holding PresShell to prevent AccessibleCaretManager to be destroyed.
   nsCOMPtr<nsIPresShell> presShell = mPresShell;
-  // XXX: Do we need to flush layout?
-  presShell->FlushPendingNotifications(Flush_Layout);
+
+  FlushLayout();
   if (presShell->IsDestroying()) {
     return;
   }
 
   Selection* sel = GetSelection();
   if (!sel) {
     return;
   }
@@ -941,17 +1022,17 @@ AccessibleCaretManager::DispatchCaretSta
   } else {
     domRect->SetLayoutRect(rect);
     init.mSelectionVisible = true;
   }
 
   // Send isEditable info w/ event detail. This info can help determine
   // whether to show cut command on selection dialog or not.
   init.mSelectionEditable = commonAncestorFrame &&
-    commonAncestorFrame->GetContent()->GetEditingHost();
+    GetEditingHostForFrame(commonAncestorFrame);
 
   init.mBoundingClientRect = domRect;
   init.mReason = aReason;
   init.mCollapsed = sel->IsCollapsed();
   init.mCaretVisible = mFirstCaret->IsLogicallyVisible() ||
                        mSecondCaret->IsLogicallyVisible();
   sel->Stringify(init.mSelectedTextContent);
 
--- a/layout/base/AccessibleCaretManager.h
+++ b/layout/base/AccessibleCaretManager.h
@@ -21,16 +21,17 @@
 class nsFrameSelection;
 class nsIContent;
 class nsIPresShell;
 struct nsPoint;
 
 namespace mozilla {
 
 namespace dom {
+class Element;
 class Selection;
 } // namespace dom
 
 class AccessibleCaret;
 
 // -----------------------------------------------------------------------------
 // AccessibleCaretManager does not deal with events or callbacks directly. It
 // relies on AccessibleCaretEventHub to call its public methods to do the work.
@@ -99,33 +100,41 @@ protected:
     None,
 
     // One caret, i.e. the selection is collapsed.
     Cursor,
 
     // Two carets, i.e. the selection is not collapsed.
     Selection
   };
-  CaretMode GetCaretMode() const;
+
+  friend std::ostream& operator<<(std::ostream& aStream,
+                                  const CaretMode& aCaretMode);
 
   enum class UpdateCaretsHint : uint8_t {
     // Update everything including appearance and position.
     Default,
 
     // Update everything while respecting the old appearance. For example, if
     // the caret in cursor mode is hidden due to timeout, do not change its
     // appearance to Normal.
     RespectOldAppearance
   };
+
+  friend std::ostream& operator<<(std::ostream& aStream,
+                                  const UpdateCaretsHint& aResult);
+
+  // Update carets based on current selection status.
   void UpdateCarets(UpdateCaretsHint aHint = UpdateCaretsHint::Default);
+
+  // Force hiding all carets regardless of the current selection status.
   void HideCarets();
 
   void UpdateCaretsForCursorMode(UpdateCaretsHint aHint);
   void UpdateCaretsForSelectionMode(UpdateCaretsHint aHint);
-  void UpdateCaretsForTilt();
 
   // Get the nearest enclosing focusable frame of aFrame.
   // @return focusable frame if there is any; nullptr otherwise.
   nsIFrame* GetFocusableFrame(nsIFrame* aFrame) const;
 
   // Change focus to aFrame if it isn't nullptr. Otherwise, clear the old focus
   // then re-focus the window.
   void ChangeFocusToOrClearOldFocus(nsIFrame* aFrame) const;
@@ -137,38 +146,61 @@ protected:
   // If aBackward is false, find the first node from the first range in current
   // selection, and return the frame and the offset into that frame. If aBackward
   // is true, find the last node from the last range instead.
   nsIFrame* FindFirstNodeWithFrame(bool aBackward, int32_t* aOutOffset) const;
 
   nsresult DragCaretInternal(const nsPoint& aPoint);
   nsPoint AdjustDragBoundary(const nsPoint& aPoint) const;
   void ClearMaintainedSelection() const;
-
+  void FlushLayout() const;
+  dom::Element* GetEditingHostForFrame(nsIFrame* aFrame) const;
   dom::Selection* GetSelection() const;
   already_AddRefed<nsFrameSelection> GetFrameSelection() const;
-  nsIContent* GetFocusedContent() const;
-
-  // This function will call FlushPendingNotifications. So caller must ensure
-  // everything exists after calling this method.
-  void DispatchCaretStateChangedEvent(dom::CaretChangedReason aReason) const;
 
   // If we're dragging the first caret, we do not want to drag it over the
   // previous character of the second caret. Same as the second caret. So we
   // check if content offset exceeds the previous/next character of second/first
   // caret base the active caret.
   bool CompareRangeWithContentOffset(nsIFrame::ContentOffsets& aOffsets);
 
   // Timeout in milliseconds to hide the AccessibleCaret under cursor mode while
   // no one touches it.
   uint32_t CaretTimeoutMs() const;
   void LaunchCaretTimeoutTimer();
   void CancelCaretTimeoutTimer();
 
+  // ---------------------------------------------------------------------------
+  // The following functions are made virtual for stubbing or mocking in gtest.
+  //
+  // Get caret mode based on current selection.
+  virtual CaretMode GetCaretMode() const;
+
+  // @return true if aStartFrame comes before aEndFrame.
+  virtual bool CompareTreePosition(nsIFrame* aStartFrame,
+                                   nsIFrame* aEndFrame) const;
+
+  // Check if the two carets is overlapping to become tilt.
+  virtual void UpdateCaretsForTilt();
+
+  // Check whether AccessibleCaret is displayable in cursor mode or not.
+  // @param aOutFrame returns frame of the cursor if it's displayable.
+  // @param aOutOffset returns frame offset as well.
+  virtual bool IsCaretDisplayableInCursorMode(nsIFrame** aOutFrame = nullptr,
+                                              int32_t* aOutOffset = nullptr) const;
+
+  virtual bool HasNonEmptyTextContent(nsINode* aNode) const;
+
+  // This function will call FlushPendingNotifications. So caller must ensure
+  // everything exists after calling this method.
+  virtual void DispatchCaretStateChangedEvent(dom::CaretChangedReason aReason) const;
+
+  // ---------------------------------------------------------------------------
   // Member variables
+  //
   nscoord mOffsetYToCaretLogicalPosition = NS_UNCONSTRAINEDSIZE;
 
   // AccessibleCaretEventHub owns us. When it's Terminate() called by
   // PresShell::Destroy(), we will be destroyed. No need to worry we outlive
   // mPresShell.
   nsIPresShell* MOZ_NON_OWNING_REF const mPresShell = nullptr;
 
   // First caret is attached to nsCaret in cursor mode, and is attached to
@@ -177,17 +209,27 @@ protected:
 
   // Second caret is used solely in selection mode, and is attached to selection
   // highlight as the right caret.
   UniquePtr<AccessibleCaret> mSecondCaret;
 
   // The caret being pressed or dragged.
   AccessibleCaret* mActiveCaret = nullptr;
 
+  // The timer for hiding the caret in cursor mode after timeout behind the
+  // preference "layout.accessiblecaret.timeout_ms".
   nsCOMPtr<nsITimer> mCaretTimeoutTimer;
-  CaretMode mCaretMode = CaretMode::None;
+
+  // The caret mode since last update carets.
+  CaretMode mLastUpdateCaretMode = CaretMode::None;
 
   static const int32_t kAutoScrollTimerDelay = 30;
 };
 
+std::ostream& operator<<(std::ostream& aStream,
+                         const AccessibleCaretManager::CaretMode& aCaretMode);
+
+std::ostream& operator<<(std::ostream& aStream,
+                         const AccessibleCaretManager::UpdateCaretsHint& aResult);
+
 } // namespace mozilla
 
 #endif // AccessibleCaretManager_h
new file mode 100644
--- /dev/null
+++ b/layout/base/gtest/TestAccessibleCaretManager.cpp
@@ -0,0 +1,208 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#include <string>
+
+#include "AccessibleCaret.h"
+#include "AccessibleCaretManager.h"
+
+using ::testing::DefaultValue;
+using ::testing::Eq;
+using ::testing::InSequence;
+using ::testing::MockFunction;
+using ::testing::Return;
+using ::testing::_;
+
+// -----------------------------------------------------------------------------
+// This file tests CaretStateChanged events and the appearance of the two
+// AccessibleCarets manipulated by AccessibleCaretManager.
+
+namespace mozilla
+{
+using dom::CaretChangedReason;
+
+class AccessibleCaretManagerTester : public ::testing::Test
+{
+public:
+  class MockAccessibleCaret : public AccessibleCaret
+  {
+  public:
+    MockAccessibleCaret() : AccessibleCaret(nullptr) {}
+
+    virtual void SetAppearance(Appearance aAppearance) override
+    {
+      // A simplified version without touching CaretElement().
+      mAppearance = aAppearance;
+    }
+
+    virtual void SetSelectionBarEnabled(bool aEnabled) override
+    {
+      // A simplified version without touching CaretElement().
+      mSelectionBarEnabled = aEnabled;
+    }
+
+    MOCK_METHOD2(SetPosition,
+                 PositionChangedResult(nsIFrame* aFrame, int32_t aOffset));
+
+  }; // class MockAccessibleCaret
+
+  class MockAccessibleCaretManager : public AccessibleCaretManager
+  {
+  public:
+    using CaretMode = AccessibleCaretManager::CaretMode;
+    using AccessibleCaretManager::UpdateCarets;
+
+    MockAccessibleCaretManager()
+      : AccessibleCaretManager(nullptr)
+    {
+      mFirstCaret = MakeUnique<MockAccessibleCaret>();
+      mSecondCaret = MakeUnique<MockAccessibleCaret>();
+    }
+
+    CaretMode LastUpdateCaretMode() const
+    {
+      return mLastUpdateCaretMode;
+    }
+
+    MockAccessibleCaret& FirstCaret()
+    {
+      return static_cast<MockAccessibleCaret&>(*mFirstCaret);
+    }
+
+    MockAccessibleCaret& SecondCaret()
+    {
+      return static_cast<MockAccessibleCaret&>(*mSecondCaret);
+    }
+
+    virtual bool CompareTreePosition(nsIFrame* aStartFrame,
+                                     nsIFrame* aEndFrame) const override
+    {
+      return true;
+    }
+
+    virtual bool IsCaretDisplayableInCursorMode(
+      nsIFrame** aOutFrame = nullptr, int32_t* aOutOffset = nullptr) const override
+    {
+      return true;
+    }
+
+    virtual void UpdateCaretsForTilt() override {}
+
+    MOCK_CONST_METHOD0(GetCaretMode, CaretMode());
+    MOCK_CONST_METHOD1(DispatchCaretStateChangedEvent,
+                       void(CaretChangedReason aReason));
+    MOCK_CONST_METHOD1(HasNonEmptyTextContent, bool(nsINode* aNode));
+
+  }; // class MockAccessibleCaretManager
+
+  using Appearance = AccessibleCaret::Appearance;
+  using PositionChangedResult = AccessibleCaret::PositionChangedResult;
+  using CaretMode = MockAccessibleCaretManager::CaretMode;
+
+  AccessibleCaretManagerTester()
+  {
+    DefaultValue<CaretMode>::Set(CaretMode::None);
+    DefaultValue<PositionChangedResult>::Set(PositionChangedResult::NotChanged);
+
+    EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _))
+      .WillRepeatedly(Return(PositionChangedResult::Changed));
+
+    EXPECT_CALL(mManager.SecondCaret(), SetPosition(_, _))
+      .WillRepeatedly(Return(PositionChangedResult::Changed));
+  }
+
+  void CheckStates(CaretMode aCaretMode,
+                   Appearance aFirstCaretAppearance,
+                   Appearance aSecondCaretAppearance)
+  {
+    EXPECT_EQ(mManager.LastUpdateCaretMode(), aCaretMode);
+    EXPECT_EQ(mManager.FirstCaret().GetAppearance(), aFirstCaretAppearance);
+    EXPECT_EQ(mManager.SecondCaret().GetAppearance(), aSecondCaretAppearance);
+  }
+
+  // Member variables
+  MockAccessibleCaretManager mManager;
+
+}; // class AccessibleCaretManagerTester
+
+TEST_F(AccessibleCaretManagerTester, TestUpdatesInSelectionMode)
+{
+  EXPECT_CALL(mManager, GetCaretMode())
+    .WillRepeatedly(Return(CaretMode::Selection));
+
+  EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+                CaretChangedReason::Updateposition)).Times(3);
+
+  mManager.UpdateCarets();
+  CheckStates(CaretMode::Selection, Appearance::Normal, Appearance::Normal);
+
+  mManager.OnReflow();
+  CheckStates(CaretMode::Selection, Appearance::Normal, Appearance::Normal);
+
+  mManager.OnScrollPositionChanged();
+  CheckStates(CaretMode::Selection, Appearance::Normal, Appearance::Normal);
+}
+
+TEST_F(AccessibleCaretManagerTester, TestUpdatesInCursorModeOnNonEmptyContent)
+{
+  EXPECT_CALL(mManager, GetCaretMode())
+    .WillRepeatedly(Return(CaretMode::Cursor));
+
+  EXPECT_CALL(mManager, HasNonEmptyTextContent(_))
+    .WillRepeatedly(Return(true));
+
+  MockFunction<void(std::string aCheckPointName)> check;
+  {
+    InSequence dummy;
+
+    EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+                  CaretChangedReason::Updateposition)).Times(1);
+    EXPECT_CALL(check, Call("mouse down"));
+
+    EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+                  CaretChangedReason::Updateposition)).Times(1);
+    EXPECT_CALL(check, Call("reflow"));
+
+    EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+                  CaretChangedReason::Visibilitychange)).Times(1);
+    EXPECT_CALL(check, Call("blur"));
+
+    EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+                  CaretChangedReason::Updateposition)).Times(1);
+    EXPECT_CALL(check, Call("mouse up"));
+
+    EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+                  CaretChangedReason::Updateposition)).Times(1);
+  }
+
+  // Simulate a single tap on a non-empty input.
+  mManager.OnSelectionChanged(nullptr, nullptr,
+                              nsISelectionListener::DRAG_REASON |
+                              nsISelectionListener::MOUSEDOWN_REASON);
+  CheckStates(CaretMode::Cursor, Appearance::Normal, Appearance::None);
+  check.Call("mouse down");
+
+  mManager.OnReflow();
+  CheckStates(CaretMode::Cursor, Appearance::Normal, Appearance::None);
+  check.Call("reflow");
+
+  mManager.OnBlur();
+  CheckStates(CaretMode::Cursor, Appearance::None, Appearance::None);
+  check.Call("blur");
+
+  mManager.OnSelectionChanged(nullptr, nullptr,
+                              nsISelectionListener::MOUSEUP_REASON);
+  CheckStates(CaretMode::Cursor, Appearance::Normal, Appearance::None);
+  check.Call("mouse up");
+
+  mManager.OnScrollPositionChanged();
+  CheckStates(CaretMode::Cursor, Appearance::Normal, Appearance::None);
+}
+
+} // namespace mozilla
--- a/layout/base/gtest/moz.build
+++ b/layout/base/gtest/moz.build
@@ -1,23 +1,30 @@
 # -*- 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/.
 
 UNIFIED_SOURCES += [
-    'TestAccessibleCaretEventHub.cpp'
+    'TestAccessibleCaretEventHub.cpp',
+    'TestAccessibleCaretManager.cpp',
 ]
 
+# XXX: Allow -Winconsistent-missing-override for TestAccessibleCaretManager.cpp
+# that stub virtual methods have 'override' keyword while mocked methods by
+# MOCK_METHOD macro does not. (See 1169974)
+ALLOW_COMPILER_WARNINGS = True
+
 include('/ipc/chromium/chromium-config.mozbuild')
 
 LOCAL_INCLUDES += [
     '/docshell/base',
-    '/layout/base'
+    '/layout/base',
+    '/layout/style',
 ]
 
 if CONFIG['GNU_CXX']:
     CXXFLAGS += ['-Wshadow']
 
 # Workaround bug 1142396. Suppress the warning from gmock library for clang.
 if CONFIG['CLANG_CXX']:
     CXXFLAGS += ['-Wno-null-dereference']
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -1576,63 +1576,47 @@ nsCSSRendering::PaintBoxShadowInner(nsPr
 
     // When there's a blur radius, gfxAlphaBoxBlur leaves the skiprect area
     // unchanged. And by construction the gfxSkipRect is not touched by the
     // rendered shadow (even after blurring), so those pixels must be completely
     // transparent in the shadow, so drawing them changes nothing.
     gfxContext* renderContext = aRenderingContext.ThebesContext();
     DrawTarget* drawTarget = renderContext->GetDrawTarget();
     nsContextBoxBlur blurringArea;
-    gfxContext* shadowContext =
-      blurringArea.Init(shadowPaintRect, 0, blurRadius, twipsPerPixel,
-                        renderContext, aDirtyRect, &skipGfxRect);
-    if (!shadowContext)
-      continue;
-    DrawTarget* shadowDT = shadowContext->GetDrawTarget();
-
-    // shadowContext is owned by either blurringArea or aRenderingContext.
-    MOZ_ASSERT(shadowContext == renderContext ||
-               shadowContext == blurringArea.GetContext());
-
-    // Set the shadow color; if not specified, use the foreground color
-    Color shadowColor = Color::FromABGR(shadowItem->mHasColor ?
-                                          shadowItem->mColor :
-                                          aForFrame->StyleColor()->mColor);
-    renderContext->Save();
-    renderContext->SetColor(ThebesColor(shadowColor));
 
     // Clip the context to the area of the frame's padding rect, so no part of the
     // shadow is painted outside. Also cut out anything beyond where the inset shadow
     // will be.
     Rect shadowGfxRect = NSRectToRect(paddingRect, twipsPerPixel);
     shadowGfxRect.Round();
+
+    // Set the shadow color; if not specified, use the foreground color
+    Color shadowColor = Color::FromABGR(shadowItem->mHasColor ?
+                                          shadowItem->mColor :
+                                          aForFrame->StyleColor()->mColor);
+
+    renderContext->Save();
+
+    // This clips the outside border radius.
+    // clipRectRadii is the border radius inside the inset shadow.
     if (hasBorderRadius) {
       RefPtr<Path> roundedRect =
         MakePathForRoundedRect(*drawTarget, shadowGfxRect, innerRadii);
       renderContext->Clip(roundedRect);
     } else {
       renderContext->Clip(shadowGfxRect);
     }
 
-    // Fill the surface minus the area within the frame that we should
-    // not paint in, and blur and apply it.
-    RefPtr<PathBuilder> builder =
-      shadowDT->CreatePathBuilder(FillRule::FILL_EVEN_ODD);
-    AppendRectToPath(builder, shadowPaintGfxRect, true);
-    if (hasBorderRadius) {
-      AppendRoundedRectToPath(builder, shadowClipGfxRect, clipRectRadii, false);
-    } else {
-      AppendRectToPath(builder, shadowClipGfxRect, false);
-    }
-    RefPtr<Path> path = builder->Finish();
-    shadowContext->SetPath(path);
-    shadowContext->Fill();
-    shadowContext->NewPath();
-
-    blurringArea.DoPaint();
+    nsContextBoxBlur insetBoxBlur;
+    gfxRect destRect = nsLayoutUtils::RectToGfxRect(shadowPaintRect, twipsPerPixel);
+    insetBoxBlur.InsetBoxBlur(renderContext, ToRect(destRect),
+                              shadowClipGfxRect, shadowColor,
+                              blurRadius, spreadDistanceAppUnits,
+                              twipsPerPixel, hasBorderRadius,
+                              clipRectRadii, ToRect(skipGfxRect));
     renderContext->Restore();
   }
 }
 
 DrawResult
 nsCSSRendering::PaintBackground(nsPresContext* aPresContext,
                                 nsRenderingContext& aRenderingContext,
                                 nsIFrame* aForFrame,
@@ -5318,37 +5302,22 @@ nsContextBoxBlur::Init(const nsRect& aRe
                        const gfxRect* aSkipRect,
                        uint32_t aFlags)
 {
   if (aRect.IsEmpty()) {
     mContext = nullptr;
     return nullptr;
   }
 
-  gfxFloat scaleX = 1;
-  gfxFloat scaleY = 1;
-
-  // Do blurs in device space when possible.
-  // Chrome/Skia always does the blurs in device space
-  // and will sometimes get incorrect results (e.g. rotated blurs)
-  gfxMatrix transform = aDestinationCtx->CurrentMatrix();
-  // XXX: we could probably handle negative scales but for now it's easier just to fallback
-  if (transform.HasNonAxisAlignedTransform() || transform._11 <= 0.0 || transform._22 <= 0.0) {
-    transform = gfxMatrix();
-  } else {
-    scaleX = transform._11;
-    scaleY = transform._22;
-  }
-
-  // compute a large or smaller blur radius
-  gfxIntSize blurRadius = ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY);
-  gfxIntSize spreadRadius = gfxIntSize(std::min(int32_t(aSpreadRadius * scaleX / aAppUnitsPerDevPixel),
-                                              int32_t(MAX_SPREAD_RADIUS)),
-                                       std::min(int32_t(aSpreadRadius * scaleY / aAppUnitsPerDevPixel),
-                                              int32_t(MAX_SPREAD_RADIUS)));
+  gfxIntSize blurRadius;
+  gfxIntSize spreadRadius;
+  GetBlurAndSpreadRadius(aDestinationCtx, aAppUnitsPerDevPixel,
+                         aBlurRadius, aSpreadRadius,
+                         blurRadius, spreadRadius);
+
   mDestinationCtx = aDestinationCtx;
 
   // If not blurring, draw directly onto the destination device
   if (blurRadius.width <= 0 && blurRadius.height <= 0 &&
       spreadRadius.width <= 0 && spreadRadius.height <= 0 &&
       !(aFlags & FORCE_MASK)) {
     mContext = aDestinationCtx;
     return mContext;
@@ -5356,16 +5325,17 @@ nsContextBoxBlur::Init(const nsRect& aRe
 
   // Convert from app units to device pixels
   gfxRect rect = nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerDevPixel);
 
   gfxRect dirtyRect =
     nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel);
   dirtyRect.RoundOut();
 
+  gfxMatrix transform = aDestinationCtx->CurrentMatrix();
   rect = transform.TransformBounds(rect);
 
   mPreTransformed = !transform.IsIdentity();
 
   // Create the temporary surface for blurring
   dirtyRect = transform.TransformBounds(dirtyRect);
   if (aSkipRect) {
     gfxRect skipRect = transform.TransformBounds(*aSkipRect);
@@ -5382,18 +5352,19 @@ nsContextBoxBlur::Init(const nsRect& aRe
     mContext->Multiply(transform);
   }
   return mContext;
 }
 
 void
 nsContextBoxBlur::DoPaint()
 {
-  if (mContext == mDestinationCtx)
+  if (mContext == mDestinationCtx) {
     return;
+  }
 
   gfxContextMatrixAutoSaveRestore saveMatrix(mDestinationCtx);
 
   if (mPreTransformed) {
     mDestinationCtx->SetMatrix(gfxMatrix());
   }
 
   mAlphaBoxBlur.Paint(mDestinationCtx);
@@ -5481,8 +5452,104 @@ nsContextBoxBlur::BlurRectangle(gfxConte
   gfxAlphaBoxBlur::BlurRectangle(aDestinationCtx,
                                  shadowThebesRect,
                                  aCornerRadii,
                                  blurStdDev,
                                  aShadowColor,
                                  dirtyRect,
                                  skipRect);
 }
+
+/* static */ void
+nsContextBoxBlur::GetBlurAndSpreadRadius(gfxContext* aDestinationCtx,
+                                         int32_t aAppUnitsPerDevPixel,
+                                         nscoord aBlurRadius,
+                                         nscoord aSpreadRadius,
+                                         gfxIntSize& aOutBlurRadius,
+                                         gfxIntSize& aOutSpreadRadius,
+                                         bool aConstrainSpreadRadius)
+{
+  gfxFloat scaleX = 1;
+  gfxFloat scaleY = 1;
+
+  // Do blurs in device space when possible.
+  // Chrome/Skia always does the blurs in device space
+  // and will sometimes get incorrect results (e.g. rotated blurs)
+  gfxMatrix transform = aDestinationCtx->CurrentMatrix();
+  // XXX: we could probably handle negative scales but for now it's easier just to fallback
+  if (transform.HasNonAxisAlignedTransform() || transform._11 <= 0.0 || transform._22 <= 0.0) {
+    transform = gfxMatrix();
+  } else {
+    scaleX = transform._11;
+    scaleY = transform._22;
+  }
+
+  // compute a large or smaller blur radius
+  aOutBlurRadius = ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY);
+  aOutSpreadRadius =
+      gfxIntSize(int32_t(aSpreadRadius * scaleX / aAppUnitsPerDevPixel),
+                 int32_t(aSpreadRadius * scaleY / aAppUnitsPerDevPixel));
+
+
+  if (aConstrainSpreadRadius) {
+    aOutSpreadRadius.width = std::min(aOutSpreadRadius.width, int32_t(MAX_SPREAD_RADIUS));
+    aOutSpreadRadius.height = std::min(aOutSpreadRadius.height, int32_t(MAX_SPREAD_RADIUS));
+  }
+}
+
+/* static */ bool
+nsContextBoxBlur::InsetBoxBlur(gfxContext* aDestinationCtx,
+                               Rect aDestinationRect,
+                               Rect aShadowClipRect,
+                               Color& aShadowColor,
+                               nscoord aBlurRadiusAppUnits,
+                               nscoord aSpreadDistanceAppUnits,
+                               int32_t aAppUnitsPerDevPixel,
+                               bool aHasBorderRadius,
+                               RectCornerRadii& aInnerClipRectRadii,
+                               Rect aSkipRect)
+{
+  if (aDestinationRect.IsEmpty()) {
+    mContext = nullptr;
+    return false;
+  }
+
+  gfxIntSize blurRadius;
+  gfxIntSize spreadRadius;
+  // Convert the blur and spread radius to device pixels
+  bool constrainSpreadRadius = false;
+  GetBlurAndSpreadRadius(aDestinationCtx, aAppUnitsPerDevPixel,
+                         aBlurRadiusAppUnits, aSpreadDistanceAppUnits,
+                         blurRadius, spreadRadius, constrainSpreadRadius);
+
+  // The blur and spread radius are scaled already, so scale all
+  // input data to the blur. This way, we don't have to scale the min
+  // inset blur to the invert of the dest context, then rescale it back
+  // when we draw to the destination surface.
+  gfxSize scale = aDestinationCtx->CurrentMatrix().ScaleFactors(true);
+  Matrix currentMatrix = ToMatrix(aDestinationCtx->CurrentMatrix());
+
+  Rect transformedDestRect = currentMatrix.TransformBounds(aDestinationRect);
+  Rect transformedShadowClipRect = currentMatrix.TransformBounds(aShadowClipRect);
+  Rect transformedSkipRect = currentMatrix.TransformBounds(aSkipRect);
+
+  transformedDestRect.RoundIn();
+  transformedShadowClipRect.Round();
+  transformedSkipRect.RoundIn();
+
+  for (size_t i = 0; i < 4; i++) {
+    aInnerClipRectRadii[i].width = std::floor(scale.width * aInnerClipRectRadii[i].width);
+    aInnerClipRectRadii[i].height = std::floor(scale.height * aInnerClipRectRadii[i].height);
+  }
+
+  {
+    gfxContextAutoSaveRestore autoRestore(aDestinationCtx);
+    aDestinationCtx->SetMatrix(gfxMatrix());
+
+    mAlphaBoxBlur.BlurInsetBox(aDestinationCtx, transformedDestRect,
+                               transformedShadowClipRect,
+                               blurRadius, spreadRadius,
+                               aShadowColor,
+                               aHasBorderRadius,
+                               aInnerClipRectRadii, transformedSkipRect);
+  }
+  return true;
+}
--- a/layout/base/nsCSSRendering.h
+++ b/layout/base/nsCSSRendering.h
@@ -20,16 +20,17 @@
 class gfxDrawable;
 class nsStyleContext;
 class nsPresContext;
 class nsRenderingContext;
 
 namespace mozilla {
 
 namespace gfx {
+struct Color;
 class DrawTarget;
 } // namespace gfx
 
 namespace layers {
 class ImageContainer;
 } // namespace layers
 
 // A CSSSizeOrRatio represents a (possibly partially specified) size for use
@@ -939,20 +940,60 @@ public:
                             const nsRect& aRect,
                             int32_t aAppUnitsPerDevPixel,
                             RectCornerRadii* aCornerRadii,
                             nscoord aBlurRadius,
                             const gfxRGBA& aShadowColor,
                             const nsRect& aDirtyRect,
                             const gfxRect& aSkipRect);
 
+  /**
+   * Draws a blurred inset box shadow shape onto the destination surface.
+   * Like BlurRectangle, this is equivalent to calling Init(),
+   * drawing a rectangle onto the returned surface
+   * and then calling DoPaint, but may let us optimize better in the
+   * backend.
+   *
+   * @param aDestinationCtx      The destination to blur to.
+   * @param aDestinationRect     The rectangle to blur in app units.
+   * @param aShadowClipRect      The inside clip rect that creates the path.
+   * @param aShadowColor         The color of the blur
+   * @param aBlurRadiusAppUnits  The blur radius in app units
+   * @param aSpreadRadiusAppUnits The spread radius in app units.
+   * @param aAppUnitsPerDevPixel The number of app units in a device pixel,
+   *                             for conversion.  Most of the time you'll
+   *                             pass this from the current PresContext if
+   *                             available.
+   * @param aHasBorderRadius     If this inset box blur has a border radius
+   * @param aInnerClipRectRadii  The clip rect radii used for the inside rect's path.
+   * @param aSkipRect            An area in device pixels (NOT app units!) to avoid
+   *                             blurring over, to prevent unnecessary work.
+   */
+  bool InsetBoxBlur(gfxContext* aDestinationCtx,
+                    mozilla::gfx::Rect aDestinationRect,
+                    mozilla::gfx::Rect aShadowClipRect,
+                    mozilla::gfx::Color& aShadowColor,
+                    nscoord aBlurRadiusAppUnits,
+                    nscoord aSpreadRadiusAppUnits,
+                    int32_t aAppUnitsPerDevPixel,
+                    bool aHasBorderRadius,
+                    RectCornerRadii& aInnerClipRectRadii,
+                    mozilla::gfx::Rect aSkipRect);
+
 protected:
+  static void GetBlurAndSpreadRadius(gfxContext* aContext,
+                                     int32_t aAppUnitsPerDevPixel,
+                                     nscoord aBlurRadius,
+                                     nscoord aSpreadRadius,
+                                     gfxIntSize& aOutBlurRadius,
+                                     gfxIntSize& aOutSpreadRadius,
+                                     bool aConstrainSpreadRadius = true);
+
   gfxAlphaBoxBlur mAlphaBoxBlur;
   nsRefPtr<gfxContext> mContext;
   gfxContext* mDestinationCtx;
 
   /* This is true if the blur already has it's content transformed
    * by mDestinationCtx's transform */
   bool mPreTransformed;
-
 };
 
 #endif /* nsCSSRendering_h___ */
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -212,16 +212,17 @@ NS_NewXULTreeBuilder(nsISupports* aOuter
 
 static void Shutdown();
 
 #include "nsGeolocation.h"
 #include "nsDeviceSensors.h"
 #ifdef MOZ_GAMEPAD
 #include "mozilla/dom/GamepadServiceTest.h"
 #endif
+#include "mozilla/dom/nsContentSecurityManager.h"
 #include "mozilla/dom/nsCSPService.h"
 #include "mozilla/dom/nsCSPContext.h"
 #include "nsICellBroadcastService.h"
 #include "nsIIccService.h"
 #include "nsISmsService.h"
 #include "nsIMmsService.h"
 #include "nsIMobileConnectionService.h"
 #include "nsIMobileMessageService.h"
@@ -647,16 +648,17 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR
 
 #ifdef MOZ_WEBSPEECH_TEST_BACKEND
 NS_GENERIC_FACTORY_CONSTRUCTOR(FakeSpeechRecognitionService)
 #endif
 #ifdef MOZ_WEBSPEECH_POCKETSPHINX
 NS_GENERIC_FACTORY_CONSTRUCTOR(PocketSphinxSpeechRecognitionService)
 #endif
 
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsContentSecurityManager)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsCSPContext)
 NS_GENERIC_FACTORY_CONSTRUCTOR(CSPService)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsMixedContentBlocker)
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsPrincipal)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsSystemPrincipal,
     nsScriptSecurityManager::SystemPrincipalSingletonConstructor)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsNullPrincipal, Init)
@@ -792,16 +794,17 @@ NS_DEFINE_NAMED_CID(NS_EDITINGCONTROLLER
 NS_DEFINE_NAMED_CID(NS_EDITORCOMMANDTABLE_CID);
 NS_DEFINE_NAMED_CID(NS_EDITINGCOMMANDTABLE_CID);
 NS_DEFINE_NAMED_CID(NS_TEXTSERVICESDOCUMENT_CID);
 NS_DEFINE_NAMED_CID(NS_GEOLOCATION_SERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_GEOLOCATION_CID);
 NS_DEFINE_NAMED_CID(NS_AUDIOCHANNEL_SERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_DATASTORE_SERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_FOCUSMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_CONTENTSECURITYMANAGER_CID);
 NS_DEFINE_NAMED_CID(CSPSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_CSPCONTEXT_CID);
 NS_DEFINE_NAMED_CID(NS_MIXEDCONTENTBLOCKER_CID);
 NS_DEFINE_NAMED_CID(NS_EVENTLISTENERSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_GLOBALMESSAGEMANAGER_CID);
 NS_DEFINE_NAMED_CID(NS_PARENTPROCESSMESSAGEMANAGER_CID);
 NS_DEFINE_NAMED_CID(NS_CHILDPROCESSMESSAGEMANAGER_CID);
 NS_DEFINE_NAMED_CID(NS_SCRIPTSECURITYMANAGER_CID);
@@ -1110,16 +1113,17 @@ static const mozilla::Module::CIDEntry k
   { &kNS_FAKE_SPEECH_RECOGNITION_SERVICE_CID, false, nullptr, FakeSpeechRecognitionServiceConstructor },
 #endif
 #ifdef MOZ_WEBSPEECH_POCKETSPHINX
   { &kNS_POCKETSPHINX_SPEECH_RECOGNITION_SERVICE_CID, false, nullptr, PocketSphinxSpeechRecognitionServiceConstructor },
 #endif
 #ifdef MOZ_WEBSPEECH
   { &kNS_SYNTHVOICEREGISTRY_CID, true, nullptr, nsSynthVoiceRegistryConstructor },
 #endif
+  { &kNS_CONTENTSECURITYMANAGER_CID, false, nullptr, nsContentSecurityManagerConstructor },
   { &kCSPSERVICE_CID, false, nullptr, CSPServiceConstructor },
   { &kNS_CSPCONTEXT_CID, false, nullptr, nsCSPContextConstructor },
   { &kNS_MIXEDCONTENTBLOCKER_CID, false, nullptr, nsMixedContentBlockerConstructor },
   { &kNS_EVENTLISTENERSERVICE_CID, false, nullptr, CreateEventListenerService },
   { &kNS_GLOBALMESSAGEMANAGER_CID, false, nullptr, CreateGlobalMessageManager },
   { &kNS_PARENTPROCESSMESSAGEMANAGER_CID, false, nullptr, CreateParentMessageManager },
   { &kNS_CHILDPROCESSMESSAGEMANAGER_CID, false, nullptr, CreateChildMessageManager },
   { &kNS_SCRIPTSECURITYMANAGER_CID, false, nullptr, Construct_nsIScriptSecurityManager },
@@ -1279,16 +1283,17 @@ static const mozilla::Module::ContractID
   { NS_SPEECH_RECOGNITION_SERVICE_CONTRACTID_PREFIX "fake", &kNS_FAKE_SPEECH_RECOGNITION_SERVICE_CID },
 #endif
 #ifdef MOZ_WEBSPEECH_POCKETSPHINX
   { NS_SPEECH_RECOGNITION_SERVICE_CONTRACTID_PREFIX "pocketsphinx-en-US", &kNS_POCKETSPHINX_SPEECH_RECOGNITION_SERVICE_CID },
 #endif
 #ifdef MOZ_WEBSPEECH
   { NS_SYNTHVOICEREGISTRY_CONTRACTID, &kNS_SYNTHVOICEREGISTRY_CID },
 #endif
+  { NS_CONTENTSECURITYMANAGER_CONTRACTID, &kNS_CONTENTSECURITYMANAGER_CID },
   { CSPSERVICE_CONTRACTID, &kCSPSERVICE_CID },
   { NS_CSPCONTEXT_CONTRACTID, &kNS_CSPCONTEXT_CID },
   { NS_MIXEDCONTENTBLOCKER_CONTRACTID, &kNS_MIXEDCONTENTBLOCKER_CID },
   { NS_EVENTLISTENERSERVICE_CONTRACTID, &kNS_EVENTLISTENERSERVICE_CID },
   { NS_GLOBALMESSAGEMANAGER_CONTRACTID, &kNS_GLOBALMESSAGEMANAGER_CID },
   { NS_PARENTPROCESSMESSAGEMANAGER_CONTRACTID, &kNS_PARENTPROCESSMESSAGEMANAGER_CID },
   { NS_CHILDPROCESSMESSAGEMANAGER_CONTRACTID, &kNS_CHILDPROCESSMESSAGEMANAGER_CID },
   { NS_SCRIPTSECURITYMANAGER_CONTRACTID, &kNS_SCRIPTSECURITYMANAGER_CID },
new file mode 100644
--- /dev/null
+++ b/layout/reftests/box-shadow/boxshadow-color-rounding-middle-ref.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<style>
+#outerDiv {
+  width: 500px;
+  height: 500px;
+  background: lime;
+  position: absolute;
+}
+
+#middleBlur {
+  width: 300px;
+  height: 300px;
+  margin-left: 100px;
+  margin-top: 100px;
+  background: black;
+  box-shadow: inset 0 0 20px 100px lime;
+}
+</style>
+
+<div id="outerDiv">
+  <div id="middleBlur">
+  </div>
+</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/box-shadow/boxshadow-color-rounding-middle.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<style>
+#thediv {
+  width: 500px;
+  height: 500px;
+  background: black;
+  box-shadow: inset 0 0 20px 200px lime;
+}
+</style>
+
+<div id="thediv"></div>
--- a/layout/reftests/box-shadow/reftest.list
+++ b/layout/reftests/box-shadow/reftest.list
@@ -16,16 +16,17 @@ random-if(layersGPUAccelerated) == boxsh
 random-if(d2d) fuzzy-if(B2G,12,18) == boxshadow-rounded-spread.html boxshadow-rounded-spread-ref.html
 skip-if((B2G&&browserIsRemote)||Mulet) HTTP(..) == boxshadow-dynamic.xul boxshadow-dynamic-ref.xul # Initial mulet triage: parity with B2G/B2G Desktop
 random-if(d2d) == boxshadow-onecorner.html boxshadow-onecorner-ref.html
 random-if(d2d) == boxshadow-twocorners.html boxshadow-twocorners-ref.html
 random-if(d2d) == boxshadow-threecorners.html boxshadow-threecorners-ref.html
 == boxshadow-skiprect.html boxshadow-skiprect-ref.html
 == boxshadow-opacity.html boxshadow-opacity-ref.html
 == boxshadow-color-rounding.html boxshadow-color-rounding-ref.html
+== boxshadow-color-rounding-middle.html boxshadow-color-rounding-middle-ref.html
 
 == overflow-not-scrollable-1.html overflow-not-scrollable-1-ref.html
 == overflow-not-scrollable-1.html overflow-not-scrollable-1-ref2.html
 == overflow-not-scrollable-2.html overflow-not-scrollable-2-ref.html
 fails-if(B2G||Mulet) == 611574-1.html 611574-1-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 fails-if(B2G||Mulet) == 611574-2.html 611574-2-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 fuzzy-if(winWidget,5,30) == fieldset.html fieldset-ref.html # minor anti-aliasing problem on Windows
 fuzzy-if(winWidget,5,30) == fieldset-inset.html fieldset-inset-ref.html # minor anti-aliasing problem on Windows
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -940,8 +940,20 @@ pref("dom.vr.cardboard.enabled", true);
 
 pref("browser.tabs.showAudioPlayingIcon", true);
 
 // Enable service workers and fetch interception on Nightly Fennec
 #ifdef NIGHTLY_BUILD
 pref("dom.serviceWorkers.enabled", true);
 pref("dom.serviceWorkers.interception.enabled", true);
 #endif
+
+// The remote content URL where FxAccountsWebChannel messages originate.  Must use HTTPS.
+pref("identity.fxaccounts.remote.webchannel.uri", "https://accounts.firefox.com");
+
+// The remote URL of the Firefox Account profile server.
+pref("identity.fxaccounts.remote.profile.uri", "https://profile.accounts.firefox.com/v1");
+
+// The remote URL of the Firefox Account oauth server.
+pref("identity.fxaccounts.remote.oauth.uri", "https://oauth.accounts.firefox.com/v1"); 
+
+// Token server used by Firefox Account-authenticated Sync.
+pref("identity.sync.tokenserver.uri", "https://token.services.mozilla.com/1.0/sync/1.5");
--- a/mobile/android/base/AboutPages.java
+++ b/mobile/android/base/AboutPages.java
@@ -7,16 +7,17 @@ package org.mozilla.gecko;
 
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.home.HomeConfig;
 import org.mozilla.gecko.home.HomeConfig.PanelType;
 import org.mozilla.gecko.util.StringUtils;
 
 public class AboutPages {
     // All of our special pages.
+    public static final String ACCOUNTS        = "about:accounts";
     public static final String ADDONS          = "about:addons";
     public static final String CONFIG          = "about:config";
     public static final String DOWNLOADS       = "about:downloads";
     public static final String FIREFOX         = "about:firefox";
     public static final String HEALTHREPORT    = "about:healthreport";
     public static final String HOME            = "about:home";
     public static final String LOGINS          = "about:logins";
     public static final String PRIVATEBROWSING = "about:privatebrowsing";
@@ -67,16 +68,17 @@ public class AboutPages {
     }
 
     public static boolean isAboutPage(String page, String url) {
         return url != null && url.toLowerCase().startsWith(page);
 
     }
 
     private static final String[] DEFAULT_ICON_PAGES = new String[] {
+        ACCOUNTS,
         ADDONS,
         CONFIG,
         DOWNLOADS,
         FIREFOX,
         HEALTHREPORT,
         UPDATER
     };
 
--- a/mobile/android/base/AccountsHelper.java
+++ b/mobile/android/base/AccountsHelper.java
@@ -16,16 +16,17 @@ import android.content.Intent;
 import android.util.Log;
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.login.Engaged;
 import org.mozilla.gecko.fxa.login.State;
+import org.mozilla.gecko.restrictions.Restriction;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.setup.SyncAccounts;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSObject;
 
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
@@ -69,16 +70,26 @@ public class AccountsHelper implements N
                 "Accounts:UpdateFirefoxAccountFromJSON",
                 "Accounts:Create",
                 "Accounts:DeleteFirefoxAccount",
                 "Accounts:Exist");
     }
 
     @Override
     public void handleMessage(String event, NativeJSObject message, final EventCallback callback) {
+        if (!RestrictedProfiles.isAllowed(mContext, Restriction.DISALLOW_MODIFY_ACCOUNTS)) {
+            // We register for messages in all contexts; we drop, with a log and an error to JavaScript,
+            // when the profile is restricted.  It's better to return errors than silently ignore messages.
+            Log.e(LOGTAG, "Profile is not allowed to modify accounts!  Ignoring event: " + event);
+            if (callback != null) {
+                callback.sendError("Profile is not allowed to modify accounts!");
+            }
+            return;
+        }
+
         if ("Accounts:CreateFirefoxAccountFromJSON".equals(event)) {
             AndroidFxAccount fxAccount = null;
             try {
                 final NativeJSObject json = message.getObject("json");
                 final String email = json.getString("email");
                 final String uid = json.getString("uid");
                 final boolean verified = json.optBoolean("verified", false);
                 final byte[] unwrapkB = Utils.hex2Byte(json.getString("unwrapBKey"));
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -5,31 +5,29 @@
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.DynamicToolbar.PinReason;
 import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
 import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
-import org.mozilla.gecko.PrintHelper;
 import org.mozilla.gecko.Tabs.TabEvents;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.TransitionsTracker;
 import org.mozilla.gecko.animation.ViewHelper;
 import org.mozilla.gecko.db.BrowserContract.Combined;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.SuggestedSites;
 import org.mozilla.gecko.distribution.Distribution;
 import org.mozilla.gecko.favicons.Favicons;
 import org.mozilla.gecko.favicons.LoadFaviconTask;
 import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
 import org.mozilla.gecko.favicons.decoders.IconDirectoryEntry;
 import org.mozilla.gecko.firstrun.FirstrunPane;
-import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.gfx.DynamicToolbarAnimator;
 import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.health.BrowserHealthRecorder;
 import org.mozilla.gecko.health.BrowserHealthReporter;
 import org.mozilla.gecko.health.HealthRecorder;
 import org.mozilla.gecko.health.SessionInformation;
 import org.mozilla.gecko.home.BrowserSearch;
@@ -92,17 +90,16 @@ import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.nfc.NdefMessage;
 import android.nfc.NdefRecord;
 import android.nfc.NfcAdapter;
 import android.nfc.NfcEvent;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.StrictMode;
@@ -2576,17 +2573,17 @@ public class BrowserApp extends GeckoApp
                 super.onActivityResult(requestCode, resultCode, data);
         }
     }
 
     private void showFirstrunPager() {
         if (mFirstrunPane == null) {
             final ViewStub firstrunPagerStub = (ViewStub) findViewById(R.id.firstrun_pager_stub);
             mFirstrunPane = (FirstrunPane) firstrunPagerStub.inflate();
-            mFirstrunPane.load(getSupportFragmentManager());
+            mFirstrunPane.load(getApplicationContext(), getSupportFragmentManager());
             mFirstrunPane.registerOnFinishListener(new FirstrunPane.OnFinishListener() {
                 @Override
                 public void onFinish() {
                     BrowserApp.this.mFirstrunPane = null;
                 }
             });
         }
 
--- a/mobile/android/base/TelemetryContract.java
+++ b/mobile/android/base/TelemetryContract.java
@@ -159,16 +159,19 @@ public interface TelemetryContract {
 
         // Action triggered from a notification in the Android notification bar.
         NOTIFICATION("notification"),
 
         // Action triggered from a pageaction in the URLBar.
         // Note: Only used in JavaScript for now, but here for completeness.
         PAGEACTION("pageaction"),
 
+        // Action triggered from one of a series of views, such as ViewPager.
+        PANEL("panel"),
+
         // Action triggered from a settings screen.
         SETTINGS("settings"),
 
         // Actions triggered from the share overlay.
         SHARE_OVERLAY("shareoverlay"),
 
         // Action triggered from a suggestion provided to the user.
         SUGGESTION("suggestion"),
@@ -202,16 +205,19 @@ public interface TelemetryContract {
      * Telemetry.startUISession() as the "sessionName" parameter.
      *
      * Please keep this list sorted.
      */
     public enum Session {
         // Awesomescreen (including frecency search) is active.
         AWESOMESCREEN("awesomescreen.1"),
 
+        // Used to tag experiments being run.
+        EXPERIMENT("experiment.1"),
+
         // Started the very first time we believe the application has been launched.
         FIRSTRUN("firstrun.1"),
 
         // Awesomescreen frecency search is active.
         FRECENCY("frecency.1"),
 
         // Started when a user enters a given home panel.
         // Session name is dynamic, encoded as "homepanel.1:<panel_id>"
--- a/mobile/android/base/firstrun/FirstrunPager.java
+++ b/mobile/android/base/firstrun/FirstrunPager.java
@@ -6,50 +6,81 @@
 package org.mozilla.gecko.firstrun;
 
 import android.content.Context;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.app.FragmentPagerAdapter;
 import android.support.v4.view.ViewPager;
 import android.util.AttributeSet;
+import android.util.Log;
 import com.nineoldandroids.animation.Animator;
 import com.nineoldandroids.animation.AnimatorSet;
 import com.nineoldandroids.animation.ObjectAnimator;
 import com.nineoldandroids.view.ViewHelper;
 
 import org.mozilla.gecko.RestrictedProfiles;
+import org.mozilla.gecko.Telemetry;
+import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.animation.TransitionsTracker;
 
 import java.util.List;
 
 public class FirstrunPager extends ViewPager {
+
     private Context context;
-    protected FirstrunPane.OnFinishListener listener;
+    protected FirstrunPanel.PagerNavigation pagerNavigation;
 
     public FirstrunPager(Context context) {
         this(context, null);
     }
 
     public FirstrunPager(Context context, AttributeSet attrs) {
         super(context, attrs);
         this.context = context;
     }
 
-    public void load(FragmentManager fm, FirstrunPane.OnFinishListener listener) {
-        final List<FirstrunPagerConfig.FirstrunPanel> panels;
+    public void load(Context appContext, FragmentManager fm, final FirstrunPane.OnFinishListener onFinishListener) {
+        final List<FirstrunPagerConfig.FirstrunPanelConfig> panels;
 
         if (RestrictedProfiles.isUserRestricted(context)) {
             panels = FirstrunPagerConfig.getRestricted();
         } else {
-            panels = FirstrunPagerConfig.getDefault();
+            panels = FirstrunPagerConfig.getDefault(appContext);
         }
 
         setAdapter(new ViewPagerAdapter(fm, panels));
-        this.listener = listener;
+        this.pagerNavigation = new FirstrunPanel.PagerNavigation() {
+            @Override
+            public void next() {
+                final int currentPage = FirstrunPager.this.getCurrentItem();
+                if (currentPage < FirstrunPager.this.getChildCount() - 1) {
+                    FirstrunPager.this.setCurrentItem(currentPage + 1);
+                }
+            }
+
+            @Override
+            public void finish() {
+                if (onFinishListener != null) {
+                    onFinishListener.onFinish();
+                }
+            }
+        };
+        addOnPageChangeListener(new OnPageChangeListener() {
+            @Override
+            public void onPageScrolled(int i, float v, int i1) {}
+
+            @Override
+            public void onPageSelected(int i) {
+                Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.PANEL, "onboarding." + i);
+            }
+
+            @Override
+            public void onPageScrollStateChanged(int i) {}
+        });
 
         animateLoad();
     }
 
     public void hide() {
         setAdapter(null);
     }
 
@@ -68,27 +99,33 @@ public class FirstrunPager extends ViewP
         set.playTogether(alphaAnimator, translateAnimator);
         set.setStartDelay(400);
         TransitionsTracker.track(set);
 
         set.start();
     }
 
     private class ViewPagerAdapter extends FragmentPagerAdapter {
-        private List<FirstrunPagerConfig.FirstrunPanel> panels;
+        private final List<FirstrunPagerConfig.FirstrunPanelConfig> panels;
+        private final Fragment[] fragments;
 
-        public ViewPagerAdapter(FragmentManager fm, List<FirstrunPagerConfig.FirstrunPanel> panels) {
+        public ViewPagerAdapter(FragmentManager fm, List<FirstrunPagerConfig.FirstrunPanelConfig> panels) {
             super(fm);
             this.panels = panels;
+            this.fragments = new Fragment[panels.size()];
         }
 
         @Override
         public Fragment getItem(int i) {
-            final Fragment fragment = Fragment.instantiate(context, panels.get(i).getClassname());
-            ((FirstrunPanel) fragment).setOnFinishListener(listener);
+            Fragment fragment = this.fragments[i];
+            if (fragment == null) {
+                fragment = Fragment.instantiate(context, panels.get(i).getClassname());
+                ((FirstrunPanel) fragment).setPagerNavigation(pagerNavigation);
+                fragments[i] = fragment;
+            }
             return fragment;
         }
 
         @Override
         public int getCount() {
             return panels.size();
         }
 
--- a/mobile/android/base/firstrun/FirstrunPagerConfig.java
+++ b/mobile/android/base/firstrun/FirstrunPagerConfig.java
@@ -1,36 +1,74 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.gecko.firstrun;
 
+import android.content.Context;
+import android.util.Log;
+import com.keepsafe.switchboard.SwitchBoard;
+import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.Telemetry;
+import org.mozilla.gecko.TelemetryContract;
+
 import java.util.LinkedList;
 import java.util.List;
 
 public class FirstrunPagerConfig {
-    public static List<FirstrunPanel> getDefault() {
-        final List<FirstrunPanel> panels = new LinkedList<>();
-        panels.add(new FirstrunPanel(WelcomePanel.class.getName(), WelcomePanel.TITLE_RES));
+    public static final String LOGTAG = "FirstrunPagerConfig";
+    public static final String ONBOARDING_A = "onboarding-a";
+    public static final String ONBOARDING_B = "onboarding-b";
+
+    public static List<FirstrunPanelConfig> getDefault(Context context) {
+        final List<FirstrunPanelConfig> panels = new LinkedList<>();
+        if (isInExperimentLocal(context, ONBOARDING_A)) {
+            panels.add(new FirstrunPanelConfig(WelcomePanel.class.getName(), WelcomePanel.TITLE_RES));
+            Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, ONBOARDING_A);
+        } else if (isInExperimentLocal(context, ONBOARDING_B)) {
+            // Strings used for first run, pulled from existing strings.
+            panels.add(new FirstrunPanelConfig(ImportPanel.class.getName(), ImportPanel.TITLE_RES));
+            panels.add(new FirstrunPanelConfig(SyncPanel.class.getName(), SyncPanel.TITLE_RES));
+            Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, ONBOARDING_B);
+        } else {
+            Log.d(LOGTAG, "Not in an experiment!");
+            panels.add(new FirstrunPanelConfig(WelcomePanel.class.getName(), WelcomePanel.TITLE_RES));
+        }
+
         return panels;
     }
 
-    public static List<FirstrunPanel> getRestricted() {
-        final List<FirstrunPanel> panels = new LinkedList<>();
-        panels.add(new FirstrunPanel(RestrictedWelcomePanel.class.getName(), RestrictedWelcomePanel.TITLE_RES));
+    /*
+     * Wrapper method for using local bucketing rather than server-side.
+     * This needs to match the server-side bucketing used on mozilla-switchboard.herokuapp.com.
+     */
+    private static boolean isInExperimentLocal(Context context, String name) {
+        if (AppConstants.MOZ_SWITCHBOARD) {
+            if (SwitchBoard.isInBucket(context, 0, 50)) {
+                return ONBOARDING_A.equals(name);
+            } else if (SwitchBoard.isInBucket(context, 50, 100)) {
+                return ONBOARDING_B.equals(name);
+            }
+        }
+        return false;
+    }
+
+    public static List<FirstrunPanelConfig> getRestricted() {
+        final List<FirstrunPanelConfig> panels = new LinkedList<>();
+        panels.add(new FirstrunPanelConfig(RestrictedWelcomePanel.class.getName(), RestrictedWelcomePanel.TITLE_RES));
         return panels;
     }
 
-    public static class FirstrunPanel {
+    public static class FirstrunPanelConfig {
         private String classname;
         private int titleRes;
 
-        public FirstrunPanel(String resource, int titleRes) {
+        public FirstrunPanelConfig(String resource, int titleRes) {
             this.classname= resource;
             this.titleRes = titleRes;
         }
 
         public String getClassname() {
             return this.classname;
         }
 
--- a/mobile/android/base/firstrun/FirstrunPane.java
+++ b/mobile/android/base/firstrun/FirstrunPane.java
@@ -10,16 +10,18 @@ import android.support.v4.app.FragmentMa
 import android.util.AttributeSet;
 
 import android.view.View;
 import android.widget.LinearLayout;
 import com.nineoldandroids.animation.Animator;
 import com.nineoldandroids.animation.AnimatorListenerAdapter;
 import com.nineoldandroids.animation.ObjectAnimator;
 import org.mozilla.gecko.R;
+import org.mozilla.gecko.Telemetry;
+import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.animation.TransitionsTracker;
 
 public class FirstrunPane extends LinearLayout {
     public static final String PREF_FIRSTRUN_ENABLED = "startpane_enabled";
 
     public static interface OnFinishListener {
         public void onFinish();
     }
@@ -30,20 +32,20 @@ public class FirstrunPane extends Linear
 
     public FirstrunPane(Context context) {
         this(context, null);
     }
     public FirstrunPane(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
-    public void load(FragmentManager fm) {
+    public void load(Context appContext, FragmentManager fm) {
         visible = true;
         pager = (FirstrunPager) findViewById(R.id.firstrun_pager);
-        pager.load(fm, new OnFinishListener() {
+        pager.load(appContext, fm, new OnFinishListener() {
             @Override
             public void onFinish() {
                 hide();
             }
         });
     }
 
     public boolean isVisible() {
@@ -52,16 +54,20 @@ public class FirstrunPane extends Linear
 
     public void hide() {
         visible = false;
         pager.hide();
         if (onFinishListener != null) {
             onFinishListener.onFinish();
         }
         animateHide();
+
+        // Stop all versions of firstrun A/B sessions.
+        Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, FirstrunPagerConfig.ONBOARDING_A);
+        Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, FirstrunPagerConfig.ONBOARDING_B);
     }
 
     private void animateHide() {
         final Animator alphaAnimator = ObjectAnimator.ofFloat(this, "alpha", 0);
         alphaAnimator.setDuration(150);
         alphaAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
--- a/mobile/android/base/firstrun/FirstrunPanel.java
+++ b/mobile/android/base/firstrun/FirstrunPanel.java
@@ -5,20 +5,30 @@
 
 package org.mozilla.gecko.firstrun;
 
 import android.support.v4.app.Fragment;
 
 public class FirstrunPanel extends Fragment {
 
     public static final int TITLE_RES = -1;
-    protected FirstrunPane.OnFinishListener onFinishListener;
+    public interface PagerNavigation {
+        void next();
+        void finish();
+    }
+    protected PagerNavigation pagerNavigation;
 
-    public void setOnFinishListener(FirstrunPane.OnFinishListener listener) {
-        this.onFinishListener = listener;
+    public void setPagerNavigation(PagerNavigation listener) {
+        this.pagerNavigation = listener;
+    }
+
+    protected void next() {
+        if (pagerNavigation != null) {
+            pagerNavigation.next();
+        }
     }
 
     protected void close() {
-        if (onFinishListener != null) {
-            onFinishListener.onFinish();
+        if (pagerNavigation != null) {
+            pagerNavigation.finish();
         }
     }
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/firstrun/ImportPanel.java
@@ -0,0 +1,159 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * 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/. */
+
+package org.mozilla.gecko.firstrun;
+
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.Telemetry;
+import org.mozilla.gecko.TelemetryContract;
+import org.mozilla.gecko.preferences.AndroidImport;
+import org.mozilla.gecko.util.ThreadUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class ImportPanel extends FirstrunPanel {
+    public static final String LOGTAG = "GeckoImportPanel";
+    public static final int TITLE_RES = R.string.firstrun_import_title;
+    private static final int AUTOADVANCE_DELAY_MS = 1500;
+
+    // These match the item positions in R.array.pref_import_android_entries.
+    private static int BOOKMARKS_INDEX = 0;
+    private static int HISTORY_INDEX = 1;
+
+    private ImageView confirmImage;
+    private Button choiceButton;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) {
+        final ViewGroup root = (ViewGroup) inflater.inflate(R.layout.firstrun_import_fragment, container, false);
+        choiceButton = (Button) root.findViewById(R.id.import_action_button);
+        confirmImage = (ImageView) root.findViewById(R.id.confirm_check);
+        choiceButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+                final List<Integer> checked = new ArrayList<>(Arrays.asList(0, 1));
+                Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "firstrun-import-action");
+                builder.setTitle(R.string.firstrun_import_action)
+                       .setMultiChoiceItems(R.array.pref_import_android_entries, makeBooleanArray(R.array.pref_import_android_defaults), new DialogInterface.OnMultiChoiceClickListener() {
+                           @Override
+                           public void onClick(DialogInterface dialogInterface, int index, boolean isChecked) {
+                               // Add telemetry for toggling checkboxes.
+                               Telemetry.sendUIEvent(TelemetryContract.Event.EDIT, TelemetryContract.Method.DIALOG, "firstrun-import-dialog-checkbox");
+                               if (isChecked && !checked.contains(index)) {
+                                   checked.add(index);
+                               } else if (!isChecked && checked.contains(index)) {
+                                   checked.remove(checked.indexOf(index));
+                               }
+                           }
+                       })
+                       .setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() {
+                           @Override
+                           public void onClick(DialogInterface dialog, int i) {
+                               Telemetry.sendUIEvent(TelemetryContract.Event.CANCEL, TelemetryContract.Method.BUTTON, "firstrun-import-dialog");
+                               dialog.dismiss();
+                           }
+                       })
+                       .setPositiveButton(R.string.firstrun_import_dialog_button, new DialogInterface.OnClickListener() {
+                           @Override
+                           public void onClick(DialogInterface dialog, int i) {
+                               final boolean importBookmarks = checked.contains(BOOKMARKS_INDEX);
+                               final boolean importHistory = checked.contains(HISTORY_INDEX);
+
+                               runImport(importBookmarks, importHistory);
+
+                               // Telemetry for what options are confirmed.
+                               final int importState = (importBookmarks ? 1 : 0) + (importHistory ? 2 : 0);
+                               Telemetry.sendUIEvent(TelemetryContract.Event.SAVE, TelemetryContract.Method.BUTTON, "firstrun-import-dialog-" + importState);
+                               dialog.dismiss();
+                           }
+                       });
+
+                builder.create().show();
+            }
+        });
+
+        root.findViewById(R.id.import_link).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "firstrun-import-next");
+                pagerNavigation.next();
+            }
+        });
+
+        return root;
+    }
+
+    private boolean[] makeBooleanArray(int resId) {
+        final String[] defaults = getResources().getStringArray(resId);
+        final boolean[] booleanArray = new boolean[defaults.length];
+        for (int i = 0; i < defaults.length; i++) {
+            booleanArray[i] = defaults[i].equals("true");
+        }
+        return booleanArray;
+    }
+
+    protected void runImport(final boolean doBookmarks, final boolean doHistory) {
+        Log.d(LOGTAG, "Importing Android history/bookmarks");
+        if (!doBookmarks && !doHistory) {
+            return;
+        }
+
+        final Context context = getActivity();
+        final String dialogTitle = context.getString(R.string.firstrun_import_progress_title);
+
+        final ProgressDialog dialog =
+                ProgressDialog.show(context,
+                        dialogTitle,
+                        context.getString(R.string.bookmarkhistory_import_wait),
+                        true);
+
+        final Runnable stopCallback = new Runnable() {
+            @Override
+            public void run() {
+                ThreadUtils.postToUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        confirmImage.setVisibility(View.VISIBLE);
+                        choiceButton.setVisibility(View.GONE);
+
+                        dialog.dismiss();
+
+                        ThreadUtils.postDelayedToUiThread(new Runnable() {
+                            @Override
+                            public void run() {
+                                next();
+                            }
+                        }, AUTOADVANCE_DELAY_MS);
+                    }
+                });
+            }
+        };
+
+        ThreadUtils.postToBackgroundThread(
+            // Constructing AndroidImport may need finding the profile,
+            // which hits disk, so it needs to go into a Runnable too.
+            new Runnable() {
+                @Override
+                public void run() {
+                    new AndroidImport(context, stopCallback, doBookmarks, doHistory).run();
+                }
+            }
+        );
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/firstrun/SyncPanel.java
@@ -0,0 +1,49 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * 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/. */
+
+package org.mozilla.gecko.firstrun;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.Telemetry;
+import org.mozilla.gecko.TelemetryContract;
+import org.mozilla.gecko.fxa.activities.FxAccountGetStartedActivity;
+
+public class SyncPanel extends FirstrunPanel {
+    // XXX: To simplify localization, this uses the pref_sync string. If this is used in the final product, add a new string to Nightly.
+    public static final int TITLE_RES = R.string.pref_sync;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) {
+        final ViewGroup root = (ViewGroup) inflater.inflate(R.layout.firstrun_sync_fragment, container, false);
+        // TODO: Update id names.
+        root.findViewById(R.id.welcome_account).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "firstrun-sync");
+
+                final Intent accountIntent = new Intent(getActivity(), FxAccountGetStartedActivity.class);
+                startActivity(accountIntent);
+
+                close();
+            }
+        });
+
+        root.findViewById(R.id.welcome_browse).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "firstrun-browser");
+                close();
+            }
+        });
+
+        return root;
+    }
+}
--- a/mobile/android/base/firstrun/WelcomePanel.java
+++ b/mobile/android/base/firstrun/WelcomePanel.java
@@ -5,33 +5,36 @@
 
 package org.mozilla.gecko.firstrun;
 
 import android.content.Intent;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.ImageView;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.fxa.FxAccountConstants;
+import org.mozilla.gecko.fxa.activities.FxAccountWebFlowActivity;
 
 public class WelcomePanel extends FirstrunPanel {
     public static final int TITLE_RES = R.string.firstrun_panel_title_welcome;
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) {
         final ViewGroup root = (ViewGroup) inflater.inflate(R.layout.firstrun_welcome_fragment, container, false);
         root.findViewById(R.id.welcome_account).setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                 Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "firstrun-sync");
 
                 final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_GET_STARTED);
+                intent.putExtra(FxAccountWebFlowActivity.EXTRA_ENDPOINT, FxAccountConstants.ENDPOINT_FIRSTRUN);
                 intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
                 startActivity(intent);
 
                 close();
             }
         });
 
         root.findViewById(R.id.welcome_browse).setOnClickListener(new View.OnClickListener() {
--- a/mobile/android/base/fxa/FxAccountConstants.java
+++ b/mobile/android/base/fxa/FxAccountConstants.java
@@ -95,9 +95,13 @@ public class FxAccountConstants {
    */
   public static final String ACCOUNT_STATE_CHANGED_ACTION = AppConstants.MOZ_ANDROID_SHARED_FXACCOUNT_TYPE + ".accounts.ACCOUNT_STATE_CHANGED_ACTION";
 
   public static final String ACTION_FXA_CONFIRM_ACCOUNT            = AppConstants.ANDROID_PACKAGE_NAME + ".ACTION_FXA_CONFIRM_ACCOUNT";
   public static final String ACTION_FXA_FINISH_MIGRATING           = AppConstants.ANDROID_PACKAGE_NAME + ".ACTION_FXA_FINISH_MIGRATING";
   public static final String ACTION_FXA_GET_STARTED                = AppConstants.ANDROID_PACKAGE_NAME + ".ACTION_FXA_GET_STARTED";
   public static final String ACTION_FXA_STATUS                     = AppConstants.ANDROID_PACKAGE_NAME + ".ACTION_FXA_STATUS";
   public static final String ACTION_FXA_UPDATE_CREDENTIALS         = AppConstants.ANDROID_PACKAGE_NAME + ".ACTION_FXA_UPDATE_CREDENTIALS";
+
+  public static final String ENDPOINT_PREFERENCES  = "preferences";
+  public static final String ENDPOINT_NOTIFICATION = "notification";
+  public static final String ENDPOINT_FIRSTRUN = "firstrun";
 }
--- a/mobile/android/base/fxa/activities/FxAccountConfirmAccountActivityWeb.java
+++ b/mobile/android/base/fxa/activities/FxAccountConfirmAccountActivityWeb.java
@@ -1,11 +1,11 @@
 /* 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/. */
 
 package org.mozilla.gecko.fxa.activities;
 
 public class FxAccountConfirmAccountActivityWeb extends FxAccountWebFlowActivity {
   public FxAccountConfirmAccountActivityWeb() {
-    super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST, "settings");
+    super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST, "manage");
   }
 }
--- a/mobile/android/base/fxa/activities/FxAccountGetStartedActivityWeb.java
+++ b/mobile/android/base/fxa/activities/FxAccountGetStartedActivityWeb.java
@@ -1,11 +1,11 @@
 /* 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/. */
 
 package org.mozilla.gecko.fxa.activities;
 
 public class FxAccountGetStartedActivityWeb extends FxAccountWebFlowActivity {
   public FxAccountGetStartedActivityWeb() {
-    super(CANNOT_RESUME_WHEN_ACCOUNTS_EXIST, "signin");
+    super(CANNOT_RESUME_WHEN_ACCOUNTS_EXIST, "signup");
   }
 }
--- a/mobile/android/base/fxa/activities/FxAccountStatusFragment.java
+++ b/mobile/android/base/fxa/activities/FxAccountStatusFragment.java
@@ -244,30 +244,32 @@ public class FxAccountStatusFragment
     if (preference == manageAccountPreference) {
       // There's no native equivalent, so no need to re-direct through an Intent filter.
       ActivityUtils.openURLInFennec(getActivity().getApplicationContext(), "about:accounts?action=manage");
       return true;
     }
 
     if (preference == needsPasswordPreference) {
       final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_UPDATE_CREDENTIALS);
+      intent.putExtra(FxAccountWebFlowActivity.EXTRA_ENDPOINT, FxAccountConstants.ENDPOINT_PREFERENCES);
       final Bundle extras = getExtrasForAccount();
       if (extras != null) {
         intent.putExtras(extras);
       }
       // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
       // the soft keyboard not being shown for the started activity. Why, Android, why?
       intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
       startActivity(intent);
 
       return true;
     }
 
     if (preference == needsFinishMigratingPreference) {
       final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_FINISH_MIGRATING);
+      intent.putExtra(FxAccountWebFlowActivity.EXTRA_ENDPOINT, FxAccountConstants.ENDPOINT_PREFERENCES);
       final Bundle extras = getExtrasForAccount();
       if (extras != null) {
         intent.putExtras(extras);
       }
       // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
       // the soft keyboard not being shown for the started activity. Why, Android, why?
       intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
       startActivity(intent);
@@ -279,16 +281,17 @@ public class FxAccountStatusFragment
       if (AppConstants.MOZ_ANDROID_NATIVE_ACCOUNT_UI) {
         FxAccountCodeResender.resendCode(getActivity().getApplicationContext(), fxAccount);
       }
 
       final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_CONFIRM_ACCOUNT);
       // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
       // the soft keyboard not being shown for the started activity. Why, Android, why?
       intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+      intent.putExtra(FxAccountWebFlowActivity.EXTRA_ENDPOINT, FxAccountConstants.ENDPOINT_PREFERENCES);
       startActivity(intent);
 
       return true;
     }
 
     if (preference == bookmarksPreference ||
         preference == historyPreference ||
         preference == passwordsPreference ||
--- a/mobile/android/base/fxa/activities/FxAccountWebFlowActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountWebFlowActivity.java
@@ -1,28 +1,35 @@
 /* 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/. */
 
 package org.mozilla.gecko.fxa.activities;
 
+import android.content.Intent;
 import android.os.Bundle;
 import org.mozilla.gecko.Locales;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
 
 /**
  * Activity which shows the status activity or passes through to web flow.
  */
 public class FxAccountWebFlowActivity extends FxAccountAbstractActivity {
     protected static final String LOG_TAG = FxAccountWebFlowActivity.class.getSimpleName();
 
     protected static final String ABOUT_ACCOUNTS = "about:accounts";
+
+    public static final String EXTRA_ENDPOINT = "entrypoint";
+
+    protected static final String[] EXTRAS_TO_PASSTHROUGH = new String[] {
+            EXTRA_ENDPOINT,
+    };
+
     private final String action;
     private final String extras;
 
     public FxAccountWebFlowActivity(int resume, String action) {
         this(resume, action, null);
     }
 
     public FxAccountWebFlowActivity(int resume, String action, String extras) {
@@ -44,18 +51,38 @@ public class FxAccountWebFlowActivity ex
         super.onCreate(icicle);
     }
 
     protected boolean redirectIfAppropriate() {
         final boolean redirected = super.redirectIfAppropriate();
         if (redirected) {
             return true;
         }
-        ActivityUtils.openURLInFennec(getApplicationContext(),
-                ABOUT_ACCOUNTS + "?action=" + action + extras);
+
+        final StringBuilder sb = new StringBuilder();
+        sb.append(ABOUT_ACCOUNTS);
+        sb.append("?action=");
+        sb.append(action);
+        sb.append(extras);
+
+        // Pass through a set of known string values from intent extras to about:accounts.
+        final Intent intent = getIntent();
+        if (intent != null) {
+            for (String key : EXTRAS_TO_PASSTHROUGH) {
+                final String value = intent.getStringExtra(key);
+                if (value != null) {
+                    sb.append("&");
+                    sb.append(key);
+                    sb.append("=");
+                    sb.append(value);
+                }
+            }
+        }
+
+        ActivityUtils.openURLInFennec(getApplicationContext(), sb.toString());
         return true;
     }
 
     @Override
     public void onResume() {
         super.onResume();
 
         // We are always redirected.
--- a/mobile/android/base/fxa/sync/FxAccountNotificationManager.java
+++ b/mobile/android/base/fxa/sync/FxAccountNotificationManager.java
@@ -11,16 +11,17 @@ import android.content.Intent;
 import android.support.v4.app.NotificationCompat;
 import android.support.v4.app.NotificationCompat.Builder;
 import org.mozilla.gecko.BrowserLocaleManager;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.common.telemetry.TelemetryWrapper;
 import org.mozilla.gecko.background.fxa.FxAccountUtils;
 import org.mozilla.gecko.fxa.FxAccountConstants;
+import org.mozilla.gecko.fxa.activities.FxAccountWebFlowActivity;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.login.State;
 import org.mozilla.gecko.fxa.login.State.Action;
 import org.mozilla.gecko.sync.telemetry.TelemetryContract;
 
 /**
  * Abstraction that manages notifications shown or hidden for a Firefox Account.
  * <p>
@@ -88,16 +89,19 @@ public class FxAccountNotificationManage
       title = context.getResources().getString(R.string.fxaccount_sync_finish_migrating_notification_title);
       text = context.getResources().getString(R.string.fxaccount_sync_finish_migrating_notification_text, state.email);
       notificationIntent = new Intent(FxAccountConstants.ACTION_FXA_FINISH_MIGRATING);
     } else {
       title = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_title);
       text = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_text, state.email);
       notificationIntent = new Intent(FxAccountConstants.ACTION_FXA_STATUS);
     }
+
+    notificationIntent.putExtra(FxAccountWebFlowActivity.EXTRA_ENDPOINT, FxAccountConstants.ENDPOINT_NOTIFICATION);
+
     Logger.info(LOG_TAG, "State " + state.getStateLabel() + " needs action; offering notification with title: " + title);
     FxAccountUtils.pii(LOG_TAG, "And text: " + text);
 
     final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
 
     final Builder builder = new NotificationCompat.Builder(context);
     builder
     .setContentTitle(title)
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -1,22 +1,32 @@
 <!-- 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/. -->
 
 
 <!ENTITY  no_space_to_start_error "There is not enough space available for &brandShortName; to start.">
 <!ENTITY  error_loading_file "An error occurred when trying to load files required to run &brandShortName;">
 
+<!ENTITY firstrun_panel_title_welcome "Welcome">
 <!ENTITY  onboard_start_message3 "Browse with &brandShortName;">
 <!ENTITY  onboard_start_subtext3 "Make your mobile Web browsing experience truly your own.">
 <!ENTITY  onboard_start_button_account "Sign in to &brandShortName;">
 <!ENTITY  onboard_start_button_browser "Start Browsing">
+<!ENTITY firstrun_button_next "Next">
+
 <!ENTITY  onboard_start_restricted1 "Stay safe and in control with this simplified version of &brandShortName;.">
 
+<!ENTITY firstrun_import_title "Import">
+<!ENTITY firstrun_import_message "Welcome to &brandShortName;">
+<!ENTITY firstrun_import_subtext "Import your bookmarks and history from another browser.">
+<!ENTITY firstrun_import_action "Transfer to &brandShortName;">
+<!ENTITY firstrun_import_dialog_button "Transfer">
+<!ENTITY firstrun_import_progress_title "Transferring">
+
 <!-- Localization note: These are used as the titles of different pages on the home screen.
      They are automatically converted to all caps by the Android platform. -->
 <!ENTITY  bookmarks_title "Bookmarks">
 <!ENTITY  history_title "History">
 <!ENTITY  reading_list_title "Reading List">
 <!ENTITY  recent_tabs_title "Recent Tabs">
 
 <!ENTITY  switch_to_tab "Switch to tab">
@@ -481,18 +491,16 @@ size. -->
 <!ENTITY button_cancel "Cancel">
 <!ENTITY button_yes "Yes">
 <!ENTITY button_no "No">
 <!ENTITY button_clear_data "Clear data">
 <!ENTITY button_set "Set">
 <!ENTITY button_clear "Clear">
 <!ENTITY button_copy "Copy">
 
-<!ENTITY firstrun_panel_title_welcome "Welcome">
-
 <!ENTITY home_top_sites_title "Top Sites">
 <!-- Localization note (home_top_sites_add): This string is used as placeholder
      text underneath empty thumbnails in the Top Sites page on about:home. -->
 <!ENTITY home_top_sites_add "Add a site">
 
 <!-- Localization note (home_title): This string should be kept in sync
      with the page title defined in aboutHome.dtd -->
 <!ENTITY home_title "&brandShortName; Home">
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -226,17 +226,19 @@ gbjar.sources += [
     'favicons/RemoteFavicon.java',
     'FilePicker.java',
     'FilePickerResultHandler.java',
     'FindInPageBar.java',
     'firstrun/FirstrunPager.java',
     'firstrun/FirstrunPagerConfig.java',
     'firstrun/FirstrunPane.java',
     'firstrun/FirstrunPanel.java',
+    'firstrun/ImportPanel.java',
     'firstrun/RestrictedWelcomePanel.java',
+    'firstrun/SyncPanel.java',
     'firstrun/WelcomePanel.java',
     'FormAssistPopup.java',
     'GeckoAccessibility.java',
     'GeckoActivity.java',
     'GeckoActivityStatus.java',
     'GeckoApp.java',
     'GeckoApplication.java',
     'GeckoAppShell.java',
@@ -493,16 +495,17 @@ gbjar.sources += [
     'toolbar/BrowserToolbarTablet.java',
     'toolbar/BrowserToolbarTabletBase.java',
     'toolbar/CanvasDelegate.java',
     'toolbar/ForwardButton.java',
     'toolbar/NavButton.java',
     'toolbar/PageActionLayout.java',
     'toolbar/PhoneTabsButton.java',
     'toolbar/ShapedButton.java',
+    'toolbar/ShapedButtonFrameLayout.java',
     'toolbar/SiteIdentityPopup.java',
     'toolbar/TabCounter.java',
     'toolbar/ToolbarDisplayLayout.java',
     'toolbar/ToolbarEditLayout.java',
     'toolbar/ToolbarEditText.java',
     'toolbar/ToolbarPrefs.java',
     'toolbar/ToolbarProgressView.java',
     'TouchEventInterceptor.java',
--- a/mobile/android/base/preferences/AndroidImport.java
+++ b/mobile/android/base/preferences/AndroidImport.java
@@ -18,17 +18,17 @@ import android.content.OperationApplicat
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.RemoteException;
 import android.provider.BaseColumns;
 import android.util.Log;
 
 import java.util.ArrayList;
 
-class AndroidImport implements Runnable {
+public class AndroidImport implements Runnable {
     /**
      * The Android M SDK removed several fields and methods from android.provider.Browser. This class is used as a
      * replacement to support building with the new SDK but at the same time still use these fields on lower Android
      * versions.
      */
     private static class LegacyBrowserProvider {
         public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks");
 
--- a/mobile/android/base/preferences/SyncPreference.java
+++ b/mobile/android/base/preferences/SyncPreference.java
@@ -8,16 +8,17 @@ package org.mozilla.gecko.preferences;
 import android.content.Context;
 import android.content.Intent;
 import android.preference.Preference;
 import android.util.AttributeSet;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.TelemetryContract.Method;
 import org.mozilla.gecko.fxa.FxAccountConstants;
+import org.mozilla.gecko.fxa.activities.FxAccountWebFlowActivity;
 import org.mozilla.gecko.sync.setup.SyncAccounts;
 import org.mozilla.gecko.sync.setup.activities.SetupSyncActivity;
 
 class SyncPreference extends Preference {
     private static final boolean DEFAULT_TO_FXA = true;
 
     private final Context mContext;
 
@@ -35,16 +36,17 @@ class SyncPreference extends Preference 
             return;
         }
         Intent intent = new Intent(mContext, SetupSyncActivity.class);
         mContext.startActivity(intent);
     }
 
     private void launchFxASetup() {
         final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_GET_STARTED);
+        intent.putExtra(FxAccountWebFlowActivity.EXTRA_ENDPOINT, FxAccountConstants.ENDPOINT_PREFERENCES);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
         mContext.startActivity(intent);
     }
 
     @Override
     protected void onClick() {
         // If we're not defaulting to FxA, just do what we've always done.
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..3a145553833ade6de1a97f462daed61f83a5bcde
GIT binary patch
literal 21493
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