Merge m-c to inbound, a=merge
authorWes Kocher <wkocher@mozilla.com>
Mon, 15 Aug 2016 14:53:49 -0700
changeset 309563 16b35e0c764465e7f0ee5108c65c02ce3d1d3175
parent 309562 d83cba382cfe97ad55afa86ecba18185f5473850 (current diff)
parent 309403 054d4856cea6150a6638e5daf7913713281af97d (diff)
child 309564 119a4b187938f44613da91a7ef1778fd63869d81
push id30565
push userkwierso@gmail.com
push dateWed, 17 Aug 2016 00:07:29 +0000
treeherdermozilla-central@1a0e253638fe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone51.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 inbound, a=merge
browser/components/extensions/test/browser/browser.ini
browser/components/sessionstore/SessionStore.jsm
devtools/client/styleeditor/styleeditor.css
dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver.html
layout/base/nsPresShell.cpp
testing/docker/desktop-test/bin/test.sh
testing/docker/desktop1604-test/bin/test.sh
widget/GfxInfoBase.cpp
widget/windows/GfxInfo.cpp
--- a/.eslintignore
+++ b/.eslintignore
@@ -59,17 +59,16 @@ b2g/locales/en-US/b2g-l10n.js
 browser/app/**
 browser/base/content/browser-social.js
 browser/base/content/nsContextMenu.js
 browser/base/content/sanitizeDialog.js
 browser/base/content/test/**
 browser/base/content/newtab/**
 browser/components/downloads/**
 browser/components/feeds/**
-browser/components/preferences/**
 browser/components/privatebrowsing/**
 browser/components/sessionstore/**
 browser/components/shell/**
 browser/components/tabview/**
 browser/components/translation/**
 browser/extensions/pdfjs/**
 browser/extensions/pocket/content/panels/js/vendor/**
 browser/locales/**
@@ -137,16 +136,17 @@ devtools/shared/apps/**
 devtools/shared/client/**
 devtools/shared/discovery/**
 devtools/shared/gcli/**
 !devtools/shared/gcli/templater.js
 devtools/shared/heapsnapshot/**
 devtools/shared/layout/**
 devtools/shared/locales/**
 devtools/shared/performance/**
+!devtools/shared/platform/**
 devtools/shared/qrcode/**
 devtools/shared/security/**
 devtools/shared/shims/**
 devtools/shared/tests/**
 !devtools/shared/tests/unit/test_csslexer.js
 devtools/shared/touch/**
 devtools/shared/transport/**
 !devtools/shared/transport/transport.js
@@ -233,17 +233,16 @@ toolkit/modules/tests/xpcshell/test_task
 toolkit/components/osfile/**
 
 # External code:
 toolkit/components/microformats/test/**
 toolkit/components/reader/Readability.js
 toolkit/components/reader/JSDOMParser.js
 
 # Uses preprocessing
-toolkit/content/widgets/videocontrols.xml
 toolkit/content/widgets/wizard.xml
 toolkit/components/jsdownloads/src/DownloadIntegration.jsm
 toolkit/components/search/nsSearchService.js
 toolkit/components/url-classifier/**
 toolkit/components/urlformatter/nsURLFormatter.js
 toolkit/identity/FirefoxAccounts.jsm
 toolkit/modules/AppConstants.jsm
 toolkit/mozapps/downloads/nsHelperAppDlg.js
--- a/b2g/chrome/content/content.css
+++ b/b2g/chrome/content/content.css
@@ -90,17 +90,17 @@ xul|thumb {
 
 xul|scrollbarbutton {
   background-image: none !important;
 }
 
 %ifndef MOZ_GRAPHENE
 /* -moz-touch-enabled? media elements */
 :-moz-any(video, audio) > xul|videocontrols {
-  -moz-binding: url("chrome://global/content/bindings/videocontrols.xml#touchControls");
+  -moz-binding: url("chrome://global/content/bindings/videocontrols.xml#touchControlsGonk");
 }
 
 select:not([size]):not([multiple]) > xul|scrollbar,
 select[size="1"] > xul|scrollbar,
 select:not([size]):not([multiple]) xul|scrollbarbutton,
 select[size="1"] xul|scrollbarbutton {
   display: block;
   margin-left: 0;
--- a/b2g/components/B2GPresentationDevicePrompt.js
+++ b/b2g/components/B2GPresentationDevicePrompt.js
@@ -4,17 +4,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 function debug(aMsg) {
   //dump("-*- B2GPresentationDevicePrompt: " + aMsg + "\n");
 }
 
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 const kB2GPRESENTATIONDEVICEPROMPT_CONTRACTID = "@mozilla.org/presentation-device/prompt;1";
 const kB2GPRESENTATIONDEVICEPROMPT_CID        = Components.ID("{4a300c26-e99b-4018-ab9b-c48cf9bc4de1}");
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
                                   "resource://gre/modules/SystemAppProxy.jsm");
--- a/browser/base/content/browser-fullZoom.js
+++ b/browser/base/content/browser-fullZoom.js
@@ -189,56 +189,56 @@ var FullZoom = {
     }
 
     // Ignore all pending async zoom accesses in the browser.  Pending accesses
     // that started before the location change will be prevented from applying
     // to the new location.
     this._ignorePendingZoomAccesses(browser);
 
     if (!aURI || (aIsTabSwitch && !this.siteSpecific)) {
-      this._notifyOnLocationChange();
+      this._notifyOnLocationChange(browser);
       return;
     }
 
     // Avoid the cps roundtrip and apply the default/global pref.
     if (aURI.spec == "about:blank") {
       this._applyPrefToZoom(undefined, browser,
-                            this._notifyOnLocationChange.bind(this));
+                            this._notifyOnLocationChange.bind(this, browser));
       return;
     }
 
     // Media documents should always start at 1, and are not affected by prefs.
     if (!aIsTabSwitch && browser.isSyntheticDocument) {
       ZoomManager.setZoomForBrowser(browser, 1);
       // _ignorePendingZoomAccesses already called above, so no need here.
-      this._notifyOnLocationChange();
+      this._notifyOnLocationChange(browser);
       return;
     }
 
     // See if the zoom pref is cached.
     let ctxt = this._loadContextFromBrowser(browser);
     let pref = this._cps2.getCachedByDomainAndName(aURI.spec, this.name, ctxt);
     if (pref) {
       this._applyPrefToZoom(pref.value, browser,
-                            this._notifyOnLocationChange.bind(this));
+                            this._notifyOnLocationChange.bind(this, browser));
       return;
     }
 
     // It's not cached, so we have to asynchronously fetch it.
     let value = undefined;
     let token = this._getBrowserToken(browser);
     this._cps2.getByDomainAndName(aURI.spec, this.name, ctxt, {
       handleResult: function (resultPref) { value = resultPref.value; },
       handleCompletion: function () {
         if (!token.isCurrent) {
-          this._notifyOnLocationChange();
+          this._notifyOnLocationChange(browser);
           return;
         }
         this._applyPrefToZoom(value, browser,
-                              this._notifyOnLocationChange.bind(this));
+                              this._notifyOnLocationChange.bind(this, browser));
       }.bind(this)
     });
   },
 
   // update state of zoom type menu item
 
   updateMenu: function FullZoom_updateMenu() {
     var menuItem = document.getElementById("toggle_zoom");
@@ -286,17 +286,17 @@ var FullZoom = {
    * @return A promise which resolves when the zoom reset has been applied.
    */
   reset: function FullZoom_reset(browser = gBrowser.selectedBrowser) {
     let token = this._getBrowserToken(browser);
     let result = this._getGlobalValue(browser).then(value => {
       if (token.isCurrent) {
         ZoomManager.setZoomForBrowser(browser, value === undefined ? 1 : value);
         this._ignorePendingZoomAccesses(browser);
-        Services.obs.notifyObservers(null, "browser-fullZoom:zoomReset", "");
+        Services.obs.notifyObservers(browser, "browser-fullZoom:zoomReset", "");
       }
     });
     this._removePref(browser);
     return result;
   },
 
   /**
    * Set the zoom level for a given browser.
@@ -354,17 +354,17 @@ var FullZoom = {
 
   /**
    * Saves the zoom level of the page in the given browser to the content
    * prefs store.
    *
    * @param browser  The zoom of this browser will be saved.  Required.
    */
   _applyZoomToPref: function FullZoom__applyZoomToPref(browser) {
-    Services.obs.notifyObservers(null, "browser-fullZoom:zoomChange", "");
+    Services.obs.notifyObservers(browser, "browser-fullZoom:zoomChange", "");
     if (!this.siteSpecific ||
         gInPrintPreviewMode ||
         browser.isSyntheticDocument)
       return;
 
     this._cps2.set(browser.currentURI.spec, this.name,
                    ZoomManager.getZoomForBrowser(browser),
                    this._loadContextFromBrowser(browser), {
@@ -375,17 +375,17 @@ var FullZoom = {
   },
 
   /**
    * Removes from the content prefs store the zoom level of the given browser.
    *
    * @param browser  The zoom of this browser will be removed.  Required.
    */
   _removePref: function FullZoom__removePref(browser) {
-    Services.obs.notifyObservers(null, "browser-fullZoom:zoomReset", "");
+    Services.obs.notifyObservers(browser, "browser-fullZoom:zoomReset", "");
     if (browser.isSyntheticDocument)
       return;
     let ctxt = this._loadContextFromBrowser(browser);
     this._cps2.removeByDomainAndName(browser.currentURI.spec, this.name, ctxt, {
       handleCompletion: function () {
         this._isNextContentPrefChangeInternal = true;
       }.bind(this),
     });
@@ -512,19 +512,19 @@ var FullZoom = {
   },
 
   /**
    * Asynchronously broadcasts "browser-fullZoom:location-change" so that
    * listeners can be notified when the zoom levels on those pages change.
    * The notification is always asynchronous so that observers are guaranteed a
    * consistent behavior.
    */
-  _notifyOnLocationChange: function FullZoom__notifyOnLocationChange() {
+  _notifyOnLocationChange: function FullZoom__notifyOnLocationChange(browser) {
     this._executeSoon(function () {
-      Services.obs.notifyObservers(null, "browser-fullZoom:location-change", "");
+      Services.obs.notifyObservers(browser, "browser-fullZoom:location-change", "");
     });
   },
 
   _executeSoon: function FullZoom__executeSoon(callback) {
     if (!callback)
       return;
     Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
   },
--- a/browser/base/content/browser-gestureSupport.js
+++ b/browser/base/content/browser-gestureSupport.js
@@ -709,32 +709,30 @@ var gHistorySwipeAnimation = {
 
       // The current page is pushed to the right (LTR) or left (RTL),
       // the intention is to go back.
       // If there is a page to go back to, it should show in the background.
       this._positionBox(this._curBox, aVal / tempDampValue);
 
       // The forward page should be pushed offscreen all the way to the right.
       this._positionBox(this._nextBox, 1);
-    } else {
+    } else if (this._canGoForward) {
       // The intention is to go forward. If there is a page to go forward to,
       // it should slide in from the right (LTR) or left (RTL).
       // Otherwise, the current page should slide to the left (LTR) or
       // right (RTL) and the backdrop should appear in the background.
       // For the backdrop to be visible in that case, the previous page needs
       // to be hidden (if it exists).
-      if (this._canGoForward) {
-        this._nextBox.collapsed = false;
-        let offset = this.isLTR ? 1 : -1;
-        this._positionBox(this._curBox, 0);
-        this._positionBox(this._nextBox, offset + aVal);
-      } else {
-        this._prevBox.collapsed = true;
-        this._positionBox(this._curBox, aVal / dampValue);
-      }
+      this._nextBox.collapsed = false;
+      let offset = this.isLTR ? 1 : -1;
+      this._positionBox(this._curBox, 0);
+      this._positionBox(this._nextBox, offset + aVal);
+    } else {
+      this._prevBox.collapsed = true;
+      this._positionBox(this._curBox, aVal / dampValue);
     }
   },
 
   _getCurrentHistoryIndex: function() {
     return SessionStore.getSessionHistory(gBrowser.selectedTab).index;
   },
 
   /**
--- a/browser/base/content/browser-tabsintitlebar.js
+++ b/browser/base/content/browser-tabsintitlebar.js
@@ -50,21 +50,19 @@ var TabsInTitlebar = {
   },
 
   allowedBy: function (condition, allow) {
     if (allow) {
       if (condition in this._disallowed) {
         delete this._disallowed[condition];
         this._update(true);
       }
-    } else {
-      if (!(condition in this._disallowed)) {
-        this._disallowed[condition] = null;
-        this._update(true);
-      }
+    } else if (!(condition in this._disallowed)) {
+      this._disallowed[condition] = null;
+      this._update(true);
     }
   },
 
   updateAppearance: function updateAppearance(aForce) {
     this._update(aForce);
   },
 
   get enabled() {
@@ -293,21 +291,21 @@ function updateTitlebarDisplay() {
       let isCustomizing = document.documentElement.hasAttribute("customizing");
       let hasLWTheme = document.documentElement.hasAttribute("lwtheme");
       let isPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
       if ((!hasLWTheme || isCustomizing) && !isPrivate) {
         document.documentElement.removeAttribute("chromemargin");
       }
       document.documentElement.setAttribute("drawtitle", "true");
     }
-  } else { // not OS X
-    if (TabsInTitlebar.enabled)
-      document.documentElement.setAttribute("chromemargin", "0,2,2,2");
-    else
-      document.documentElement.removeAttribute("chromemargin");
+  } else if (TabsInTitlebar.enabled) {
+    // not OS X
+    document.documentElement.setAttribute("chromemargin", "0,2,2,2");
+  } else {
+    document.documentElement.removeAttribute("chromemargin");
   }
 }
 
 function onTitlebarMaxClick() {
   if (window.windowState == window.STATE_MAXIMIZED)
     window.restore();
   else
     window.maximize();
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -4103,20 +4103,18 @@ function updateCharacterEncodingMenuStat
   let charsetMenu = document.getElementById("charsetMenu");
   // gBrowser is null on Mac when the menubar shows in the context of
   // non-browser windows. The above elements may be null depending on
   // what parts of the menubar are present. E.g. no app menu on Mac.
   if (gBrowser && gBrowser.selectedBrowser.mayEnableCharacterEncodingMenu) {
     if (charsetMenu) {
       charsetMenu.removeAttribute("disabled");
     }
-  } else {
-    if (charsetMenu) {
-      charsetMenu.setAttribute("disabled", "true");
-    }
+  } else if (charsetMenu) {
+    charsetMenu.setAttribute("disabled", "true");
   }
 }
 
 var XULBrowserWindow = {
   // Stored Status, Link and Loading values
   status: "",
   defaultStatus: "",
   overLink: "",
@@ -7822,19 +7820,18 @@ var MousePosTracker = {
     if (hover == listener._hover)
       return;
 
     listener._hover = hover;
 
     if (hover) {
       if (listener.onMouseEnter)
         listener.onMouseEnter();
-    } else {
-      if (listener.onMouseLeave)
-        listener.onMouseLeave();
+    } else if (listener.onMouseLeave) {
+      listener.onMouseLeave();
     }
   }
 };
 
 function BrowserOpenNewTabOrWindow(event) {
   if (event.shiftKey) {
     OpenBrowserWindow();
   } else {
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -777,16 +777,20 @@
                        class="urlbar-icon"
                        hidden="true"
                        tooltiptext="&pageReportIcon.tooltip;"
                        onclick="gPopupBlockerObserver.onReportButtonClick(event);"/>
                 <image id="reader-mode-button"
                        class="urlbar-icon"
                        hidden="true"
                        onclick="ReaderParent.buttonClick(event);"/>
+                <toolbarbutton id="urlbar-zoom-button"
+                       onclick="FullZoom.reset();"
+                       tooltiptext="&urlbar.zoomReset.tooltip;"
+                       hidden="true"/>
               </hbox>
               <hbox id="userContext-icons" hidden="true">
                 <label id="userContext-label"/>
                 <image id="userContext-indicator"/>
               </hbox>
               <toolbarbutton id="urlbar-go-button"
                              class="chromeclass-toolbar-additional"
                              onclick="gURLBar.handleCommand(event);"
--- a/browser/base/content/safeMode.js
+++ b/browser/base/content/safeMode.js
@@ -70,16 +70,14 @@ function onLoad() {
     document.getElementById("autoSafeMode").hidden = false;
     document.getElementById("safeMode").hidden = true;
     if (ResetProfile.resetSupported()) {
       document.getElementById("resetProfile").hidden = false;
     } else {
       // Hide the reset button is it's not supported.
       document.documentElement.getButton("extra1").hidden = true;
     }
-  } else {
-    if (!ResetProfile.resetSupported()) {
-      // Hide the reset button and text if it's not supported.
-      document.documentElement.getButton("extra1").hidden = true;
-      document.getElementById("resetProfileInstead").hidden = true;
-    }
+  } else if (!ResetProfile.resetSupported()) {
+    // Hide the reset button and text if it's not supported.
+    document.documentElement.getButton("extra1").hidden = true;
+    document.getElementById("resetProfileInstead").hidden = true;
   }
 }
--- a/browser/base/content/test/urlbar/browser_urlbarOneOffs.js
+++ b/browser/base/content/test/urlbar/browser_urlbarOneOffs.js
@@ -149,19 +149,19 @@ add_task(function* searchWith() {
 
   assertState(0, -1, typedValue);
 
   let item = gURLBar.popup.richlistbox.firstChild;
   Assert.equal(item._actionText.textContent,
                "Search with " + Services.search.currentEngine.name,
                "Sanity check: first result's action text");
 
-  // Tab to the first one-off.  Now the first result and the first one-off
+  // Alt+Down to the first one-off.  Now the first result and the first one-off
   // should both be selected.
-  EventUtils.synthesizeKey("VK_TAB", {})
+  EventUtils.synthesizeKey("VK_DOWN", { altKey: true })
   assertState(0, 0, typedValue);
 
   let engineName = gURLBar.popup.oneOffSearchButtons.selectedButton.engine.name;
   Assert.notEqual(engineName, Services.search.currentEngine.name,
                   "Sanity check: First one-off engine should not be " +
                   "the current engine");
   Assert.equal(item._actionText.textContent,
                "Search with " + engineName,
@@ -191,18 +191,18 @@ add_task(function* oneOffClick() {
 add_task(function* oneOffReturn() {
   gBrowser.selectedTab = gBrowser.addTab();
 
   let typedValue = "foo";
   yield promiseAutocompleteResultPopup(typedValue, window, true);
 
   assertState(0, -1, typedValue);
 
-  // Tab to select the first one-off.
-  EventUtils.synthesizeKey("VK_TAB", {})
+  // Alt+Down to select the first one-off.
+  EventUtils.synthesizeKey("VK_DOWN", { altKey: true })
   assertState(0, 0, typedValue);
 
   let resultsPromise = promiseSearchResultsLoaded();
   EventUtils.synthesizeKey("VK_RETURN", {})
   yield resultsPromise;
 
   gBrowser.removeTab(gBrowser.selectedTab);
 });
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -361,18 +361,17 @@ file, You can obtain one at http://mozil
         <body><![CDATA[
           let isMouseEvent = event instanceof MouseEvent;
           if (isMouseEvent && event.button == 2) {
             // Do nothing for right clicks.
             return;
           }
 
           // Do the command of the selected one-off if it's not an engine.
-          let selectedOneOff =
-            this.popup.oneOffSearchButtons.visuallySelectedButton;
+          let selectedOneOff = this.popup.oneOffSearchButtons.selectedButton;
           if (selectedOneOff && !selectedOneOff.engine) {
             selectedOneOff.doCommand();
             return;
           }
 
           let where = openUILinkWhere;
           if (!where) {
             if (isMouseEvent) {
@@ -410,17 +409,17 @@ file, You can obtain one at http://mozil
                     gBrowser.removeTab(prevTab);
                   }
                   return;
                 }
                 break;
               case "searchengine":
                 if (selectedOneOff && selectedOneOff.engine) {
                   // Replace the engine with the selected one-off engine.
-                  action.params.engineName = engine.name;
+                  action.params.engineName = selectedOneOff.engine.name;
                 }
                 [url, postData] = this._recordSearchEngineLoad(
                   action.params.engineName,
                   action.params.searchSuggestion || action.params.searchQuery,
                   event,
                   where,
                   openUILinkParams
                 );
@@ -430,22 +429,17 @@ file, You can obtain one at http://mozil
                           matchLastLocationChange, mayInheritPrincipal);
             return;
           }
 
           // If there's a selected one-off button and the input value is a
           // search query (or "keyword" in URI-fixup terminology), then load a
           // search using the one-off's engine.
           if (selectedOneOff && selectedOneOff.engine) {
-            // `url` (which is this.value) may be an autofilled string.  Search
-            // only with the portion that the user typed, if any, by preferring
-            // the autocomplete controller's searchString.
-            let value = this._searchStringOnHandleEnter ||
-                        this.mController.searchString ||
-                        url;
+            let value = this.oneOffSearchQuery;
             let fixup;
             try {
               fixup = Services.uriFixup.getFixupURIInfo(
                 value,
                 Services.uriFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP
               );
             } catch (ex) {}
             if (fixup && fixup.keywordProviderName) {
@@ -466,16 +460,27 @@ file, You can obtain one at http://mozil
                 gBrowser.selectedBrowser.lastLocationChange;
               this._loadURL(url, postData, where, openUILinkParams,
                             matchLastLocationChange, mayInheritPrincipal);
             }
           });
         ]]></body>
       </method>
 
+      <property name="oneOffSearchQuery">
+        <getter><![CDATA[
+          // this.textValue may be an autofilled string.  Search only with the
+          // portion that the user typed, if any, by preferring the autocomplete
+          // controller's searchString (including _searchStringOnHandleEnter).
+          return this._searchStringOnHandleEnter ||
+                 this.mController.searchString ||
+                 this.textValue;
+        ]]></getter>
+      </property>
+
       <method name="_loadURL">
         <parameter name="url"/>
         <parameter name="postData"/>
         <parameter name="openUILinkWhere"/>
         <parameter name="openUILinkParams"/>
         <parameter name="matchLastLocationChange"/>
         <parameter name="mayInheritPrincipal"/>
         <body><![CDATA[
@@ -1287,16 +1292,17 @@ file, You can obtain one at http://mozil
       <xul:richlistbox anonid="richlistbox" class="autocomplete-richlistbox"
                        flex="1"/>
       <xul:hbox anonid="footer">
         <children/>
         <xul:vbox anonid="one-off-search-buttons"
                   class="search-one-offs"
                   compact="true"
                   includecurrentengine="true"
+                  disabletab="true"
                   flex="1"/>
       </xul:hbox>
     </content>
 
     <implementation>
       <field name="_maxResults">0</field>
 
       <field name="_bundle" readonly="true">
@@ -1633,17 +1639,17 @@ file, You can obtain one at http://mozil
               resolve();
             };
             this.addEventListener("transitionend", onTransitionEnd, true);
           });
           ]]>
         </body>
       </method>
 
-      <method name="_visuallySelectedOneOffChanged">
+      <method name="_selectedOneOffChanged">
         <body><![CDATA[
           // Update all searchengine result items to use the newly selected
           // engine.
           for (let item of this.richlistbox.childNodes) {
             if (item.collapsed) {
               break;
             }
             let url = item.getAttribute("url");
@@ -1688,18 +1694,18 @@ file, You can obtain one at http://mozil
         <getter><![CDATA[
           // When building the popup, autocomplete reuses an item at index i if
           // that item's url attribute matches the controller's value at index
           // i, but only if overrideSearchEngineName matches the engine in the
           // url attribute.  To absolutely avoid reusing items that shouldn't be
           // reused, always return a non-null name here by falling back to the
           // current engine.
           let engine =
-            (this.oneOffSearchButtons.visuallySelectedButton &&
-             this.oneOffSearchButtons.visuallySelectedButton.engine) ||
+            (this.oneOffSearchButtons.selectedButton &&
+             this.oneOffSearchButtons.selectedButton.engine) ||
              Services.search.currentEngine;
           return engine ? engine.name : null;
         ]]></getter>
       </property>
 
       <method name="createResultLabel">
         <parameter name="item"/>
         <parameter name="proposedLabel"/>
@@ -1764,18 +1770,18 @@ file, You can obtain one at http://mozil
             }
           ]]>
         </body>
       </method>
 
     </implementation>
     <handlers>
 
-      <handler event="OneOffsVisuallySelectedButtonChanged"><![CDATA[
-        this._visuallySelectedOneOffChanged();
+      <handler event="SelectedOneOffButtonChanged"><![CDATA[
+        this._selectedOneOffChanged();
       ]]></handler>
 
       <handler event="mousedown"><![CDATA[
         // Required to make the xul:label.text-link elements in the search
         // suggestions notification work correctly when clicked on Linux.
         // This is copied from the mousedown handler in
         // browser-search-autocomplete-result-popup, which apparently had a
         // similar problem.
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -4320,41 +4320,36 @@ OverflowableToolbar.prototype = {
       // Let's try to move stuff back:
       else if (!nowInBar) {
         this._moveItemsBackToTheirOrigin(true);
       }
       // If it's in the toolbar now, then we don't care. An overflow event may
       // fire afterwards; that's ok!
     }
     // If it used to be overflowed...
-    else {
+    else if (!nowOverflowed) {
       // ... and isn't anymore, let's remove our bookkeeping:
-      if (!nowOverflowed) {
-        this._collapsed.delete(aNode.id);
-        aNode.removeAttribute("cui-anchorid");
-        aNode.removeAttribute("overflowedItem");
-        CustomizableUIInternal.notifyListeners("onWidgetUnderflow", aNode, this._target);
-
-        if (!this._collapsed.size) {
-          this._toolbar.removeAttribute("overflowing");
-          CustomizableUI.removeListener(this);
-        }
+      this._collapsed.delete(aNode.id);
+      aNode.removeAttribute("cui-anchorid");
+      aNode.removeAttribute("overflowedItem");
+      CustomizableUIInternal.notifyListeners("onWidgetUnderflow", aNode, this._target);
+
+      if (!this._collapsed.size) {
+        this._toolbar.removeAttribute("overflowing");
+        CustomizableUI.removeListener(this);
       }
+    } else if (aNode.previousSibling) {
       // but if it still is, it must have changed places. Bookkeep:
-      else {
-        if (aNode.previousSibling) {
-          let prevId = aNode.previousSibling.id;
-          let minSize = this._collapsed.get(prevId);
-          this._collapsed.set(aNode.id, minSize);
-        } else {
-          // If it's now the first item in the overflow list,
-          // maybe we can return it:
-          this._moveItemsBackToTheirOrigin();
-        }
-      }
+      let prevId = aNode.previousSibling.id;
+      let minSize = this._collapsed.get(prevId);
+      this._collapsed.set(aNode.id, minSize);
+    } else {
+      // If it's now the first item in the overflow list,
+      // maybe we can return it:
+      this._moveItemsBackToTheirOrigin();
     }
   },
 
   findOverflowedInsertionPoints: function(aNode) {
     let newNodeCanOverflow = aNode.getAttribute("overflows") != "false";
     let areaId = this._toolbar.id;
     let placements = gPlacements.get(areaId);
     let nodeIndex = placements.indexOf(aNode.id);
--- a/browser/components/extensions/test/browser/browser.ini
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -75,16 +75,17 @@ support-files =
 [browser_ext_tabs_query.js]
 [browser_ext_tabs_reload.js]
 [browser_ext_tabs_reload_bypass_cache.js]
 [browser_ext_tabs_sendMessage.js]
 [browser_ext_tabs_update.js]
 [browser_ext_tabs_zoom.js]
 [browser_ext_tabs_update_url.js]
 [browser_ext_topwindowid.js]
+[browser_ext_webNavigation_frameId0.js]
 [browser_ext_webNavigation_getFrames.js]
 [browser_ext_webNavigation_urlbar_transitions.js]
 [browser_ext_windows.js]
 [browser_ext_windows_create.js]
 tags = fullscreen
 [browser_ext_windows_create_tabId.js]
 [browser_ext_windows_events.js]
 [browser_ext_windows_size.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_webNavigation_frameId0.js
@@ -0,0 +1,47 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* webNavigation_getFrameId_of_existing_main_frame() {
+  // Whether the frame ID in the extension API is 0 is determined by a map that
+  // is maintained by |Frames| in ExtensionManagement.jsm. This map is filled
+  // using data from content processes. But if ExtensionManagement.jsm is not
+  // imported, then the "Extension:TopWindowID" message gets lost.
+  // As a result, if the state is not synchronized again, the webNavigation API
+  // will mistakenly report a non-zero frame ID for top-level frames.
+  //
+  // If you want to be absolutely sure that the frame ID is correct, don't open
+  // tabs before starting an extension, or explicitly load the module in the
+  // main process:
+  // Cu.import("resource://gre/modules/ExtensionManagement.jsm", {});
+  //
+  // Or simply run the test again.
+  const BASE = "http://mochi.test:8888/browser/browser/components/extensions/test/browser/";
+  const DUMMY_URL = BASE + "file_dummy.html";
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, DUMMY_URL, true);
+
+  function background(DUMMY_URL) {
+    browser.tabs.query({active: true, currentWindow: true}).then(tabs => {
+      return browser.webNavigation.getAllFrames({tabId: tabs[0].id});
+    }).then(frames => {
+      browser.test.assertEq(1, frames.length, "The dummy page has one frame");
+      browser.test.assertEq(0, frames[0].frameId, "Main frame's ID must be 0");
+      browser.test.assertEq(DUMMY_URL, frames[0].url, "Main frame URL must match");
+      browser.test.notifyPass("frameId checked");
+    });
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["webNavigation"],
+    },
+
+    background: `(${background})(${JSON.stringify(DUMMY_URL)});`,
+  });
+
+  yield extension.startup();
+  yield extension.awaitFinish("frameId checked");
+  yield extension.unload();
+
+  yield BrowserTestUtils.removeTab(tab);
+});
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -56,16 +56,17 @@ XPCOMUtils.defineLazyServiceGetter(this,
   ["SelfSupportBackend", "resource:///modules/SelfSupportBackend.jsm"],
   ["SessionStore", "resource:///modules/sessionstore/SessionStore.jsm"],
   ["ShellService", "resource:///modules/ShellService.jsm"],
   ["SimpleServiceDiscovery", "resource://gre/modules/SimpleServiceDiscovery.jsm"],
   ["TabCrashHandler", "resource:///modules/ContentCrashHandlers.jsm"],
   ["TabGroupsMigrator", "resource:///modules/TabGroupsMigrator.jsm"],
   ["Task", "resource://gre/modules/Task.jsm"],
   ["UITour", "resource:///modules/UITour.jsm"],
+  ["URLBarZoom", "resource:///modules/URLBarZoom.jsm"],
   ["WebChannel", "resource://gre/modules/WebChannel.jsm"],
   ["WindowsRegistry", "resource://gre/modules/WindowsRegistry.jsm"],
   ["webrtcUI", "resource:///modules/webrtcUI.jsm"],
 ].forEach(([name, resource]) => XPCOMUtils.defineLazyModuleGetter(this, name, resource));
 
 if (AppConstants.MOZ_CRASHREPORTER) {
   XPCOMUtils.defineLazyModuleGetter(this, "PluginCrashReporter",
                                     "resource:///modules/ContentCrashHandlers.jsm");
@@ -685,16 +686,17 @@ BrowserGlue.prototype = {
 
     ContentClick.init();
     RemotePrompt.init();
     Feeds.init();
     ContentPrefServiceParent.init();
 
     LoginManagerParent.init();
     ReaderParent.init();
+    URLBarZoom.init();
 
     SelfSupportBackend.init();
 
     // Ensure we keep track of places/pw-mananager undo by init'ing this early.
     Cu.import("resource:///modules/AutoMigrate.jsm");
 
     if (!AppConstants.RELEASE_BUILD) {
       let themeName = gBrowserBundle.GetStringFromName("deveditionTheme.name");
--- a/browser/components/places/content/browserPlacesViews.js
+++ b/browser/components/places/content/browserPlacesViews.js
@@ -476,20 +476,19 @@ PlacesViewBase.prototype = {
         aStatus == Ci.mozILivemark.STATUS_FAILED) {
       // Status has changed, update the cached status menuitem.
       let stringId = aStatus == Ci.mozILivemark.STATUS_LOADING ?
                        "bookmarksLivemarkLoading" : "bookmarksLivemarkFailed";
       statusMenuitem.setAttribute("label", PlacesUIUtils.getString(stringId));
       if (aPopup._startMarker.nextSibling != statusMenuitem)
         aPopup.insertBefore(statusMenuitem, aPopup._startMarker.nextSibling);
     }
-    else {
+    else if (aPopup._statusMenuitem.parentNode == aPopup) {
       // The livemark has finished loading.
-      if (aPopup._statusMenuitem.parentNode == aPopup)
-        aPopup.removeChild(aPopup._statusMenuitem);
+      aPopup.removeChild(aPopup._statusMenuitem);
     }
   },
 
   toggleCutNode: function PVB_toggleCutNode(aPlacesNode, aValue) {
     let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
 
     // We may get the popup for menus, but we need the menu itself.
     if (elt.localName == "menupopup")
--- a/browser/components/places/content/editBookmarkOverlay.js
+++ b/browser/components/places/content/editBookmarkOverlay.js
@@ -974,19 +974,18 @@ var gEditItemOverlay = {
       let curTagIndex = tags.indexOf(tagCheckbox.label);
       let tagsSelector = this._element("tagsSelector");
       tagsSelector.selectedItem = tagCheckbox;
 
       if (tagCheckbox.checked) {
         if (curTagIndex == -1)
           tags.push(tagCheckbox.label);
       }
-      else {
-        if (curTagIndex != -1)
-          tags.splice(curTagIndex, 1);
+      else if (curTagIndex != -1) {
+        tags.splice(curTagIndex, 1);
       }
       this._element("tagsField").value = tags.join(", ");
       this._updateTags();
       break;
     case "unload":
       this.uninitPanel(false);
       break;
     }
--- a/browser/components/places/tests/browser/browser_drag_bookmarks_on_toolbar.js
+++ b/browser/components/places/tests/browser/browser_drag_bookmarks_on_toolbar.js
@@ -227,23 +227,21 @@ var gTests = [
 function nextTest() {
   if (gTests.length) {
     var test = gTests.shift();
     waitForFocus(function() {
       info("Start of test: " + test.desc);
       test.run();
     });
   }
-  else {
+  else if (wasCollapsed) {
     // Collapse the personal toolbar if needed.
-    if (wasCollapsed) {
-      promiseSetToolbarVisibility(toolbar, false).then(finish);
-    } else {
-      finish();
-    }
+    promiseSetToolbarVisibility(toolbar, false).then(finish);
+  } else {
+    finish();
   }
 }
 
 var toolbar = document.getElementById("PersonalToolbar");
 var wasCollapsed = toolbar.collapsed;
 
 function test() {
   waitForExplicitFinish();
--- a/browser/components/preferences/connection.js
+++ b/browser/components/preferences/connection.js
@@ -41,28 +41,28 @@ var gConnectionsDialog = {
         var backupServerURLPref = document.getElementById("network.proxy.backup." + proxyPrefs[i]);
         var backupPortPref = document.getElementById("network.proxy.backup." + proxyPrefs[i] + "_port");
         backupServerURLPref.value = backupServerURLPref.value || proxyServerURLPref.value;
         backupPortPref.value = backupPortPref.value || proxyPortPref.value;
         proxyServerURLPref.value = httpProxyURLPref.value;
         proxyPortPref.value = httpProxyPortPref.value;
       }
     }
-    
+
     this.sanitizeNoProxiesPref();
-    
+
     return true;
   },
 
   checkForSystemProxy: function ()
   {
     if ("@mozilla.org/system-proxy-settings;1" in Components.classes)
       document.getElementById("systemPref").removeAttribute("hidden");
   },
-  
+
   proxyTypeChanged: function ()
   {
     var proxyTypePref = document.getElementById("network.proxy.type");
 
     // Update http
     var httpProxyURLPref = document.getElementById("network.proxy.http");
     httpProxyURLPref.disabled = proxyTypePref.value != 1;
     var httpProxyPortPref = document.getElementById("network.proxy.http_port");
@@ -78,27 +78,27 @@ var gConnectionsDialog = {
     var noProxiesPref = document.getElementById("network.proxy.no_proxies_on");
     noProxiesPref.disabled = proxyTypePref.value != 1;
 
     var autoconfigURLPref = document.getElementById("network.proxy.autoconfig_url");
     autoconfigURLPref.disabled = proxyTypePref.value != 2;
 
     this.updateReloadButton();
   },
-  
+
   updateDNSPref: function ()
   {
     var socksVersionPref = document.getElementById("network.proxy.socks_version");
     var socksDNSPref = document.getElementById("network.proxy.socks_remote_dns");
     var proxyTypePref = document.getElementById("network.proxy.type");
     var isDefinitelySocks4 = !socksVersionPref.disabled && socksVersionPref.value == 4;
     socksDNSPref.disabled = (isDefinitelySocks4 || proxyTypePref.value == 0);
     return undefined;
   },
-  
+
   updateReloadButton: function ()
   {
     // Disable the "Reload PAC" button if the selected proxy type is not PAC or
     // if the current value of the PAC textbox does not match the value stored
     // in prefs.  Likewise, disable the reload button if PAC is not configured
     // in prefs.
 
     var typedURL = document.getElementById("networkProxyAutoconfigURL").value;
@@ -110,33 +110,33 @@ var gConnectionsDialog = {
     var pacURL = prefs.getCharPref("network.proxy.autoconfig_url");
     var proxyType = prefs.getIntPref("network.proxy.type");
 
     var disableReloadPref =
         document.getElementById("pref.advanced.proxies.disable_button.reload");
     disableReloadPref.disabled =
         (proxyTypeCur != 2 || proxyType != 2 || typedURL != pacURL);
   },
-  
+
   readProxyType: function ()
   {
     this.proxyTypeChanged();
     return undefined;
   },
-  
+
   updateProtocolPrefs: function ()
   {
     var proxyTypePref = document.getElementById("network.proxy.type");
     var shareProxiesPref = document.getElementById("network.proxy.share_proxy_settings");
     var proxyPrefs = ["ssl", "ftp", "socks"];
     for (var i = 0; i < proxyPrefs.length; ++i) {
       var proxyServerURLPref = document.getElementById("network.proxy." + proxyPrefs[i]);
       var proxyPortPref = document.getElementById("network.proxy." + proxyPrefs[i] + "_port");
-      
-      // Restore previous per-proxy custom settings, if present. 
+
+      // Restore previous per-proxy custom settings, if present.
       if (!shareProxiesPref.value) {
         var backupServerURLPref = document.getElementById("network.proxy.backup." + proxyPrefs[i]);
         var backupPortPref = document.getElementById("network.proxy.backup." + proxyPrefs[i] + "_port");
         if (backupServerURLPref.hasUserValue) {
           proxyServerURLPref.value = backupServerURLPref.value;
           backupServerURLPref.reset();
         }
         if (backupPortPref.hasUserValue) {
@@ -150,64 +150,64 @@ var gConnectionsDialog = {
       proxyServerURLPref.disabled = proxyTypePref.value != 1 || shareProxiesPref.value;
       proxyPortPref.disabled = proxyServerURLPref.disabled;
     }
     var socksVersionPref = document.getElementById("network.proxy.socks_version");
     socksVersionPref.disabled = proxyTypePref.value != 1 || shareProxiesPref.value;
     this.updateDNSPref();
     return undefined;
   },
-  
+
   readProxyProtocolPref: function (aProtocol, aIsPort)
   {
     var shareProxiesPref = document.getElementById("network.proxy.share_proxy_settings");
     if (shareProxiesPref.value) {
-      var pref = document.getElementById("network.proxy.http" + (aIsPort ? "_port" : ""));    
+      var pref = document.getElementById("network.proxy.http" + (aIsPort ? "_port" : ""));
       return pref.value;
     }
-    
+
     var backupPref = document.getElementById("network.proxy.backup." + aProtocol + (aIsPort ? "_port" : ""));
     return backupPref.hasUserValue ? backupPref.value : undefined;
   },
 
   reloadPAC: function ()
   {
     Components.classes["@mozilla.org/network/protocol-proxy-service;1"].
         getService().reloadPAC();
   },
-  
+
   doAutoconfigURLFixup: function ()
   {
     var autoURL = document.getElementById("networkProxyAutoconfigURL");
     var autoURLPref = document.getElementById("network.proxy.autoconfig_url");
     var URIFixup = Components.classes["@mozilla.org/docshell/urifixup;1"]
                              .getService(Components.interfaces.nsIURIFixup);
     try {
       autoURLPref.value = autoURL.value = URIFixup.createFixupURI(autoURL.value, 0).spec;
-    } catch(ex) {}
+    } catch (ex) {}
   },
 
   sanitizeNoProxiesPref: function()
   {
     var noProxiesPref = document.getElementById("network.proxy.no_proxies_on");
     // replace substrings of ; and \n with commas if they're neither immediately
     // preceded nor followed by a valid separator character
     noProxiesPref.value = noProxiesPref.value.replace(/([^, \n;])[;\n]+(?![,\n;])/g, '$1,');
-    // replace any remaining ; and \n since some may follow commas, etc. 
+    // replace any remaining ; and \n since some may follow commas, etc.
     noProxiesPref.value = noProxiesPref.value.replace(/[;\n]/g, '');
   },
-  
+
   readHTTPProxyServer: function ()
   {
     var shareProxiesPref = document.getElementById("network.proxy.share_proxy_settings");
     if (shareProxiesPref.value)
       this.updateProtocolPrefs();
     return undefined;
   },
-  
+
   readHTTPProxyPort: function ()
   {
     var shareProxiesPref = document.getElementById("network.proxy.share_proxy_settings");
     if (shareProxiesPref.value)
       this.updateProtocolPrefs();
     return undefined;
   }
 };
--- a/browser/components/preferences/cookies.js
+++ b/browser/components/preferences/cookies.js
@@ -56,19 +56,18 @@ var gCookiesWindow = {
       this._tree.view.selection.select(0);
 
     if (aInitialLoad) {
       if ("arguments" in window &&
           window.arguments[0] &&
           window.arguments[0].filterString)
         this.setFilter(window.arguments[0].filterString);
     }
-    else {
-      if (document.getElementById("filter").value != "")
-        this.filter();
+    else if (document.getElementById("filter").value != "") {
+      this.filter();
     }
 
     this._updateRemoveAllButton();
 
     this._saveState();
   },
 
   _cookieEquals: function (aCookieA, aCookieB, aStrippedHost) {
@@ -240,19 +239,19 @@ var gCookiesWindow = {
         var cacheEntry = { 'start' : i, 'count' : count };
         var cacheStart = count;
 
         if (currHost.open) {
           if (count < aIndex && aIndex <= (count + currHost.cookies.length)) {
             // We are looking for an entry within this host's children,
             // enumerate them looking for the index.
             ++count;
-            for (var i = 0; i < currHost.cookies.length; ++i) {
+            for (var j = 0; j < currHost.cookies.length; ++j) {
               if (count == aIndex) {
-                var cookie = currHost.cookies[i];
+                var cookie = currHost.cookies[j];
                 cookie.parentIndex = hostIndex;
                 return cookie;
               }
               ++count;
             }
           }
           else {
             // A host entry was open, but we weren't looking for an index
@@ -260,51 +259,51 @@ var gCookiesWindow = {
             // entry's children. We need to add one to increment for the
             // host value too.
             count += currHost.cookies.length + 1;
           }
         }
         else
           ++count;
 
-        for (var j = cacheStart; j < count; j++)
-          this._cacheItems[j] = cacheEntry;
+        for (var k = cacheStart; k < count; k++)
+          this._cacheItems[k] = cacheEntry;
         this._cacheValid = count - 1;
       }
       return null;
     },
 
     _removeItemAtIndex: function (aIndex, aCount) {
-      var removeCount = aCount === undefined ? 1 : aCount;
+      let removeCount = aCount === undefined ? 1 : aCount;
       if (this._filtered) {
         // remove the cookies from the unfiltered set so that they
         // don't reappear when the filter is changed. See bug 410863.
-        for (var i = aIndex; i < aIndex + removeCount; ++i) {
-          var item = this._filterSet[i];
-          var parent = gCookiesWindow._hosts[item.rawHost];
-          for (var j = 0; j < parent.cookies.length; ++j) {
+        for (let i = aIndex; i < aIndex + removeCount; ++i) {
+          let item = this._filterSet[i];
+          let parent = gCookiesWindow._hosts[item.rawHost];
+          for (let j = 0; j < parent.cookies.length; ++j) {
             if (item == parent.cookies[j]) {
               parent.cookies.splice(j, 1);
               break;
             }
           }
         }
         this._filterSet.splice(aIndex, removeCount);
         return;
       }
 
-      var item = this._getItemAtIndex(aIndex);
+      let item = this._getItemAtIndex(aIndex);
       if (!item) return;
       this._invalidateCache(aIndex - 1);
       if (item.container) {
         gCookiesWindow._hosts[item.rawHost] = null;
       } else {
-        var parent = this._getItemAtIndex(item.parentIndex);
-        for (var i = 0; i < parent.cookies.length; ++i) {
-          var cookie = parent.cookies[i];
+        let parent = this._getItemAtIndex(item.parentIndex);
+        for (let i = 0; i < parent.cookies.length; ++i) {
+          let cookie = parent.cookies[i];
           if (item.rawHost == cookie.rawHost &&
               item.name == cookie.name &&
               item.path == cookie.path &&
               ChromeUtils.isOriginAttributesEqual(item.originAttributes,
                                                   cookie.originAttributes)) {
             parent.cookies.splice(i, removeCount);
           }
         }
@@ -320,21 +319,20 @@ var gCookiesWindow = {
         var item = this._getItemAtIndex(aIndex);
         if (!item)
           return "";
         if (aColumn.id == "domainCol")
           return item.rawHost;
         else if (aColumn.id == "nameCol")
           return item.name;
       }
-      else {
-        if (aColumn.id == "domainCol")
-          return this._filterSet[aIndex].rawHost;
-        else if (aColumn.id == "nameCol")
-          return this._filterSet[aIndex].name;
+      else if (aColumn.id == "domainCol") {
+        return this._filterSet[aIndex].rawHost;
+      } else if (aColumn.id == "nameCol") {
+        return this._filterSet[aIndex].name;
       }
       return "";
     },
 
     _selection: null,
     get selection () { return this._selection; },
     set selection (val) { this._selection = val; return val; },
     getRowProperties: function (aIndex) { return ""; },
@@ -393,21 +391,19 @@ var gCookiesWindow = {
           if (item.container) {
             for (var i = aIndex + 1; i < this.rowCount; ++i) {
               var subsequent = this._getItemAtIndex(i);
               if (subsequent.container)
                 return true;
             }
             return false;
           }
-          else {
-            var parent = this._getItemAtIndex(item.parentIndex);
-            if (parent && parent.container)
-              return aIndex < item.parentIndex + parent.cookies.length;
-          }
+          var parent = this._getItemAtIndex(item.parentIndex);
+          if (parent && parent.container)
+            return aIndex < item.parentIndex + parent.cookies.length;
         }
       }
       return aIndex < this.rowCount - 1;
     },
     hasPreviousSibling: function (aIndex) {
       if (!this._filtered) {
         var item = this._getItemAtIndex(aIndex);
         if (!item) return false;
@@ -706,17 +702,17 @@ var gCookiesWindow = {
       }
       this._view._rowCount += rowCountImpact;
       tbo.rowCountChanged(ci, rowCountImpact);
       if (invalidateRow != -1)
         tbo.invalidateRow(invalidateRow);
     }
     else {
       var rangeCount = seln.getRangeCount();
-      // Traverse backwards through selections to avoid messing 
+      // Traverse backwards through selections to avoid messing
       // up the indices when they are deleted.
       // See bug 388079.
       for (var i = rangeCount - 1; i >= 0; --i) {
         var min = {}; var max = {};
         seln.getRangeAt(i, min, max);
         nextSelected = min.value;
         for (var j = min.value; j <= max.value; ++j) {
           deleteItems.push(this._view._getItemAtIndex(j));
@@ -756,22 +752,22 @@ var gCookiesWindow = {
     else {
       this._cm.removeAll();
     }
     this._updateRemoveAllButton();
     this.focusFilterBox();
   },
 
   onCookieKeyPress: function (aEvent) {
-    if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE
-#ifdef XP_MACOSX
-        || aEvent.keyCode == KeyEvent.DOM_VK_BACK_SPACE
-#endif
-       )
+    if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE) {
       this.deleteCookie();
+    } else if (AppConstants.platform == "macosx" &&
+               aEvent.keyCode == KeyEvent.DOM_VK_BACK_SPACE) {
+      this.deleteCookie();
+    }
   },
 
   _lastSortProperty : "",
   _lastSortAscending: false,
   sort: function (aProperty) {
     var ascending = (aProperty == this._lastSortProperty) ? !this._lastSortAscending : true;
     // Sort the Non-Filtered Host Collections
     if (aProperty == "rawHost") {
--- a/browser/components/preferences/fonts.js
+++ b/browser/components/preferences/fonts.js
@@ -35,68 +35,63 @@ var gFontsDialog = {
       if (!preference) {
         preference = document.createElement("preference");
         var name = prefs[i].format.replace(/%LANG%/, aLanguageGroup);
         preference.id = name;
         preference.setAttribute("name", name);
         preference.setAttribute("type", prefs[i].type);
         preferences.appendChild(preference);
       }
-      
+
       if (!prefs[i].element)
         continue;
-        
+
       var element = document.getElementById(prefs[i].element);
       if (element) {
         element.setAttribute("preference", preference.id);
-      
+
         if (prefs[i].fonttype)
           FontBuilder.buildFontList(aLanguageGroup, prefs[i].fonttype, element);
 
         preference.setElementValue(element);
       }
     }
   },
-  
+
   readFontLanguageGroup: function ()
   {
     var languagePref = document.getElementById("font.language.group");
     this._selectLanguageGroup(languagePref.value);
     return undefined;
   },
-  
+
   readUseDocumentFonts: function ()
   {
     var preference = document.getElementById("browser.display.use_document_fonts");
     return preference.value == 1;
   },
-  
+
   writeUseDocumentFonts: function ()
   {
     var useDocumentFonts = document.getElementById("useDocumentFonts");
     return useDocumentFonts.checked ? 1 : 0;
   },
 
   onBeforeAccept: function ()
   {
-    // Only care in in-content prefs
-    if (!window.frameElement) {
-      return true;
-    }
-
     let preferences = document.querySelectorAll("preference[id*='font.minimum-size']");
     // It would be good if we could avoid touching languages the pref pages won't use, but
     // unfortunately the language group APIs (deducing language groups from language codes)
     // are C++ - only. So we just check all the things the user touched:
     // Don't care about anything up to 24px, or if this value is the same as set previously:
     preferences = Array.filter(preferences, prefEl => {
       return prefEl.value > 24 && prefEl.value != prefEl.valueFromPreferences;
     });
     if (!preferences.length) {
-      return;
+      return true;
     }
 
     let strings = document.getElementById("bundlePreferences");
     let title = strings.getString("veryLargeMinimumFontTitle");
     let confirmLabel = strings.getString("acceptVeryLargeMinimumFont");
     let warningMessage = strings.getString("veryLargeMinimumFontWarning");
     let {Services} = Components.utils.import("resource://gre/modules/Services.jsm", {});
     let flags = Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL |
--- a/browser/components/preferences/in-content/advanced.js
+++ b/browser/components/preferences/in-content/advanced.js
@@ -25,86 +25,86 @@ var gAdvancedPane = {
 
     this._inited = true;
     var advancedPrefs = document.getElementById("advancedPrefs");
 
     var preference = document.getElementById("browser.preferences.advanced.selectedTabIndex");
     if (preference.value !== null)
         advancedPrefs.selectedIndex = preference.value;
 
-#ifdef MOZ_UPDATER
-    let onUnload = function () {
-      window.removeEventListener("unload", onUnload, false);
-      Services.prefs.removeObserver("app.update.", this);
-    }.bind(this);
-    window.addEventListener("unload", onUnload, false);
-    Services.prefs.addObserver("app.update.", this, false);
-    this.updateReadPrefs();
-#endif
+    if (AppConstants.MOZ_UPDATER) {
+      let onUnload = function () {
+        window.removeEventListener("unload", onUnload, false);
+        Services.prefs.removeObserver("app.update.", this);
+      }.bind(this);
+      window.addEventListener("unload", onUnload, false);
+      Services.prefs.addObserver("app.update.", this, false);
+      this.updateReadPrefs();
+    }
     this.updateOfflineApps();
-#ifdef MOZ_CRASHREPORTER
-    this.initSubmitCrashes();
-#endif
+    if (AppConstants.MOZ_CRASHREPORTER) {
+      this.initSubmitCrashes();
+    }
     this.initTelemetry();
-#ifdef MOZ_TELEMETRY_REPORTING
-    this.initSubmitHealthReport();
-#endif
+    if (AppConstants.MOZ_TELEMETRY_REPORTING) {
+      this.initSubmitHealthReport();
+    }
     this.updateOnScreenKeyboardVisibility();
     this.updateCacheSizeInputField();
     this.updateActualCacheSize();
     this.updateActualAppCacheSize();
 
     setEventListener("layers.acceleration.disabled", "change",
                      gAdvancedPane.updateHardwareAcceleration);
     setEventListener("advancedPrefs", "select",
                      gAdvancedPane.tabSelectionChanged);
-#ifdef MOZ_TELEMETRY_REPORTING
-    setEventListener("submitHealthReportBox", "command",
-                     gAdvancedPane.updateSubmitHealthReport);
-#endif
-#ifdef MOZ_CRASHREPORTER
-    setEventListener("submitCrashesBox", "command",
-                     gAdvancedPane.updateSubmitCrashes);
-#endif
+    if (AppConstants.MOZ_TELEMETRY_REPORTING) {
+      setEventListener("submitHealthReportBox", "command",
+                       gAdvancedPane.updateSubmitHealthReport);
+    }
+    if (AppConstants.MOZ_CRASHREPORTER) {
+      setEventListener("submitCrashesBox", "command",
+                       gAdvancedPane.updateSubmitCrashes);
+    }
     setEventListener("connectionSettings", "command",
                      gAdvancedPane.showConnections);
     setEventListener("clearCacheButton", "command",
                      gAdvancedPane.clearCache);
     setEventListener("clearOfflineAppCacheButton", "command",
                      gAdvancedPane.clearOfflineAppCache);
     setEventListener("offlineNotifyExceptions", "command",
                      gAdvancedPane.showOfflineExceptions);
     setEventListener("offlineAppsList", "select",
                      gAdvancedPane.offlineAppSelected);
     let bundlePrefs = document.getElementById("bundlePreferences");
     document.getElementById("offlineAppsList")
             .style.height = bundlePrefs.getString("offlineAppsList.height");
     setEventListener("offlineAppsListRemove", "command",
                      gAdvancedPane.removeOfflineApp);
-#ifdef MOZ_UPDATER
-    setEventListener("updateRadioGroup", "command",
-                     gAdvancedPane.updateWritePrefs);
-    setEventListener("showUpdateHistory", "command",
-                     gAdvancedPane.showUpdates);
-#endif
+    if (AppConstants.MOZ_UPDATER) {
+      setEventListener("updateRadioGroup", "command",
+                       gAdvancedPane.updateWritePrefs);
+      setEventListener("showUpdateHistory", "command",
+                       gAdvancedPane.showUpdates);
+    }
     setEventListener("viewCertificatesButton", "command",
                      gAdvancedPane.showCertificates);
     setEventListener("viewSecurityDevicesButton", "command",
                      gAdvancedPane.showSecurityDevices);
     setEventListener("cacheSize", "change",
                      gAdvancedPane.updateCacheSizePref);
 
-#ifdef MOZ_WIDGET_GTK
-    // GTK tabbox' allow the scroll wheel to change the selected tab,
-    // but we don't want this behavior for the in-content preferences.
-    let tabsElement = document.getElementById("tabsElement");
-    tabsElement.addEventListener("DOMMouseScroll", event => {
-      event.stopPropagation();
-    }, true);
-#endif
+    if (AppConstants.MOZ_WIDGET_GTK) {
+      // GTK tabbox' allow the scroll wheel to change the selected tab,
+      // but we don't want this behavior for the in-content preferences.
+      let tabsElement = document.getElementById("tabsElement");
+      tabsElement.addEventListener("DOMMouseScroll", event => {
+        event.stopPropagation();
+      }, true);
+    }
   },
 
   /**
    * Stores the identity of the current tab in preferences so that the selected
    * tab can be persisted between openings of the preferences window.
    */
   tabSelectionChanged: function ()
   {
@@ -169,17 +169,23 @@ var gAdvancedPane = {
   /**
    * Returns the value of the spellchecking preference represented by UI,
    * preserving the preference's "hidden" value if the preference is
    * unchanged and represents a value not strictly allowed in UI.
    */
   writeCheckSpelling: function ()
   {
     var checkbox = document.getElementById("checkSpelling");
-    return checkbox.checked ? (this._storedSpellCheck == 2 ? 2 : 1) : 0;
+    if (checkbox.checked) {
+      if (this._storedSpellCheck == 2) {
+        return 2;
+      }
+      return 1;
+    }
+    return 0;
   },
 
   /**
    * security.OCSP.enabled is an integer value for legacy reasons.
    * A value of 1 means OCSP is enabled. Any other value means it is disabled.
    */
   readEnableOCSP: function ()
   {
@@ -201,21 +207,21 @@ var gAdvancedPane = {
   },
 
   /**
    * When the user toggles the layers.acceleration.disabled pref,
    * sync its new value to the gfx.direct2d.disabled pref too.
    */
   updateHardwareAcceleration: function()
   {
-#ifdef XP_WIN
-    var fromPref = document.getElementById("layers.acceleration.disabled");
-    var toPref = document.getElementById("gfx.direct2d.disabled");
-    toPref.value = fromPref.value;
-#endif
+    if (AppConstants.platform = "win") {
+      var fromPref = document.getElementById("layers.acceleration.disabled");
+      var toPref = document.getElementById("gfx.direct2d.disabled");
+      toPref.value = fromPref.value;
+    }
   },
 
   // DATA CHOICES TAB
 
   /**
    * Set up or hide the Learn More links for various data collection options
    */
   _setupLearnMoreLink: function (pref, element) {
@@ -263,66 +269,68 @@ var gAdvancedPane = {
 
   /**
    * The preference/checkbox is configured in XUL.
    *
    * In all cases, set up the Learn More link sanely.
    */
   initTelemetry: function ()
   {
-#ifdef MOZ_TELEMETRY_REPORTING
-    this._setupLearnMoreLink("toolkit.telemetry.infoURL", "telemetryLearnMore");
-#endif
+    if (AppConstants.MOZ_TELEMETRY_REPORTING) {
+      this._setupLearnMoreLink("toolkit.telemetry.infoURL", "telemetryLearnMore");
+    }
   },
 
   /**
    * Set the status of the telemetry controls based on the input argument.
    * @param {Boolean} aEnabled False disables the controls, true enables them.
    */
   setTelemetrySectionEnabled: function (aEnabled)
   {
-#ifdef MOZ_TELEMETRY_REPORTING
-    // If FHR is disabled, additional data sharing should be disabled as well.
-    let disabled = !aEnabled;
-    document.getElementById("submitTelemetryBox").disabled = disabled;
-    if (disabled) {
-      // If we disable FHR, untick the telemetry checkbox.
-      Services.prefs.setBoolPref("toolkit.telemetry.enabled", false);
+    if (AppConstants.MOZ_TELEMETRY_REPORTING) {
+      // If FHR is disabled, additional data sharing should be disabled as well.
+      let disabled = !aEnabled;
+      document.getElementById("submitTelemetryBox").disabled = disabled;
+      if (disabled) {
+        // If we disable FHR, untick the telemetry checkbox.
+        Services.prefs.setBoolPref("toolkit.telemetry.enabled", false);
+      }
+      document.getElementById("telemetryDataDesc").disabled = disabled;
     }
-    document.getElementById("telemetryDataDesc").disabled = disabled;
-#endif
   },
 
-#ifdef MOZ_TELEMETRY_REPORTING
   /**
    * Initialize the health report service reference and checkbox.
    */
   initSubmitHealthReport: function () {
-    this._setupLearnMoreLink("datareporting.healthreport.infoURL", "FHRLearnMore");
+    if (AppConstants.MOZ_TELEMETRY_REPORTING) {
+      this._setupLearnMoreLink("datareporting.healthreport.infoURL", "FHRLearnMore");
 
-    let checkbox = document.getElementById("submitHealthReportBox");
+      let checkbox = document.getElementById("submitHealthReportBox");
 
-    if (Services.prefs.prefIsLocked(PREF_UPLOAD_ENABLED)) {
-      checkbox.setAttribute("disabled", "true");
-      return;
+      if (Services.prefs.prefIsLocked(PREF_UPLOAD_ENABLED)) {
+        checkbox.setAttribute("disabled", "true");
+        return;
+      }
+
+      checkbox.checked = Services.prefs.getBoolPref(PREF_UPLOAD_ENABLED);
+      this.setTelemetrySectionEnabled(checkbox.checked);
     }
-
-    checkbox.checked = Services.prefs.getBoolPref(PREF_UPLOAD_ENABLED);
-    this.setTelemetrySectionEnabled(checkbox.checked);
   },
 
   /**
    * Update the health report preference with state from checkbox.
    */
   updateSubmitHealthReport: function () {
-    let checkbox = document.getElementById("submitHealthReportBox");
-    Services.prefs.setBoolPref(PREF_UPLOAD_ENABLED, checkbox.checked);
-    this.setTelemetrySectionEnabled(checkbox.checked);
+    if (AppConstants.MOZ_TELEMETRY_REPORTING) {
+      let checkbox = document.getElementById("submitHealthReportBox");
+      Services.prefs.setBoolPref(PREF_UPLOAD_ENABLED, checkbox.checked);
+      this.setTelemetrySectionEnabled(checkbox.checked);
+    }
   },
-#endif
 
   updateOnScreenKeyboardVisibility() {
     if (AppConstants.platform == "win") {
       let minVersion = Services.prefs.getBoolPref("ui.osk.require_win10") ? 10 : 6.2;
       if (Services.vc.compare(Services.sysinfo.getProperty("version"), minVersion) >= 0) {
         document.getElementById("useOnScreenKeyboard").hidden = false;
       }
     }
@@ -458,17 +466,17 @@ var gAdvancedPane = {
    * Clears the cache.
    */
   clearCache: function ()
   {
     try {
       var cache = Components.classes["@mozilla.org/netwerk/cache-storage-service;1"]
                             .getService(Components.interfaces.nsICacheStorageService);
       cache.clear();
-    } catch(ex) {}
+    } catch (ex) {}
     this.updateActualCacheSize();
   },
 
   /**
    * Clears the application cache.
    */
   clearOfflineAppCache: function ()
   {
@@ -644,17 +652,16 @@ var gAdvancedPane = {
    * false if the user should be asked what he wants to do when an update is
    * available
    * extensions.update.enabled
    * - true if updates to extensions and themes are enabled, false otherwise
    * browser.search.update
    * - true if updates to search engines are enabled, false otherwise
    */
 
-#ifdef MOZ_UPDATER
   /**
    * Selects the item of the radiogroup based on the pref values and locked
    * states.
    *
    * UI state matrix for update preference conditions
    *
    * UI Components:                              Preferences
    * Radiogroup                                  i   = app.update.enabled
@@ -664,88 +671,93 @@ var gAdvancedPane = {
    * Element           pref  value  locked  disabled
    * radiogroup        i     t/f    f       false
    *                   i     t/f    *t*     *true*
    *                   ii    t/f    f       false
    *                   ii    t/f    *t*     *true*
    */
   updateReadPrefs: function ()
   {
-    var enabledPref = document.getElementById("app.update.enabled");
-    var autoPref = document.getElementById("app.update.auto");
-    var radiogroup = document.getElementById("updateRadioGroup");
+    if (AppConstants.MOZ_UPDATER) {
+      var enabledPref = document.getElementById("app.update.enabled");
+      var autoPref = document.getElementById("app.update.auto");
+      var radiogroup = document.getElementById("updateRadioGroup");
 
-    if (!enabledPref.value)   // Don't care for autoPref.value in this case.
-      radiogroup.value="manual";    // 3. Never check for updates.
-    else if (autoPref.value)  // enabledPref.value && autoPref.value
-      radiogroup.value="auto";      // 1. Automatically install updates
-    else                      // enabledPref.value && !autoPref.value
-      radiogroup.value="checkOnly"; // 2. Check, but let me choose
+      if (!enabledPref.value)   // Don't care for autoPref.value in this case.
+        radiogroup.value="manual";    // 3. Never check for updates.
+      else if (autoPref.value)  // enabledPref.value && autoPref.value
+        radiogroup.value="auto";      // 1. Automatically install updates
+      else                      // enabledPref.value && !autoPref.value
+        radiogroup.value="checkOnly"; // 2. Check, but let me choose
 
-    var canCheck = Components.classes["@mozilla.org/updates/update-service;1"].
-                     getService(Components.interfaces.nsIApplicationUpdateService).
-                     canCheckForUpdates;
-    // canCheck is false if the enabledPref is false and locked,
-    // or the binary platform or OS version is not known.
-    // A locked pref is sufficient to disable the radiogroup.
-    radiogroup.disabled = !canCheck || enabledPref.locked || autoPref.locked;
+      var canCheck = Components.classes["@mozilla.org/updates/update-service;1"].
+                       getService(Components.interfaces.nsIApplicationUpdateService).
+                       canCheckForUpdates;
+      // canCheck is false if the enabledPref is false and locked,
+      // or the binary platform or OS version is not known.
+      // A locked pref is sufficient to disable the radiogroup.
+      radiogroup.disabled = !canCheck || enabledPref.locked || autoPref.locked;
 
-#ifdef MOZ_MAINTENANCE_SERVICE
-    // Check to see if the maintenance service is installed.
-    // If it is don't show the preference at all.
-    var installed;
-    try {
-      var wrk = Components.classes["@mozilla.org/windows-registry-key;1"]
-                .createInstance(Components.interfaces.nsIWindowsRegKey);
-      wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE,
-               "SOFTWARE\\Mozilla\\MaintenanceService",
-               wrk.ACCESS_READ | wrk.WOW64_64);
-      installed = wrk.readIntValue("Installed");
-      wrk.close();
-    } catch(e) {
+      if (AppConstants.MOZ_MAINTENANCE_SERVICE) {
+        // Check to see if the maintenance service is installed.
+        // If it is don't show the preference at all.
+        var installed;
+        try {
+          var wrk = Components.classes["@mozilla.org/windows-registry-key;1"]
+                    .createInstance(Components.interfaces.nsIWindowsRegKey);
+          wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE,
+                   "SOFTWARE\\Mozilla\\MaintenanceService",
+                   wrk.ACCESS_READ | wrk.WOW64_64);
+          installed = wrk.readIntValue("Installed");
+          wrk.close();
+        } catch (e) {
+        }
+        if (installed != 1) {
+          document.getElementById("useService").hidden = true;
+        }
+      }
     }
-    if (installed != 1) {
-      document.getElementById("useService").hidden = true;
-    }
-#endif
   },
 
   /**
    * Sets the pref values based on the selected item of the radiogroup.
    */
   updateWritePrefs: function ()
   {
-    var enabledPref = document.getElementById("app.update.enabled");
-    var autoPref = document.getElementById("app.update.auto");
-    var radiogroup = document.getElementById("updateRadioGroup");
-    switch (radiogroup.value) {
-      case "auto":      // 1. Automatically install updates for Desktop only
-        enabledPref.value = true;
-        autoPref.value = true;
-        break;
-      case "checkOnly": // 2. Check, but let me choose
-        enabledPref.value = true;
-        autoPref.value = false;
-        break;
-      case "manual":    // 3. Never check for updates.
-        enabledPref.value = false;
-        autoPref.value = false;
+    if (AppConstants.MOZ_UPDATER) {
+      var enabledPref = document.getElementById("app.update.enabled");
+      var autoPref = document.getElementById("app.update.auto");
+      var radiogroup = document.getElementById("updateRadioGroup");
+      switch (radiogroup.value) {
+        case "auto":      // 1. Automatically install updates for Desktop only
+          enabledPref.value = true;
+          autoPref.value = true;
+          break;
+        case "checkOnly": // 2. Check, but let me choose
+          enabledPref.value = true;
+          autoPref.value = false;
+          break;
+        case "manual":    // 3. Never check for updates.
+          enabledPref.value = false;
+          autoPref.value = false;
+      }
     }
   },
 
   /**
    * Displays the history of installed updates.
    */
   showUpdates: function ()
   {
-    var prompter = Components.classes["@mozilla.org/updates/update-prompt;1"]
-                             .createInstance(Components.interfaces.nsIUpdatePrompt);
-    prompter.showUpdateHistory(window);
+    if (AppConstants.MOZ_UPDATER) {
+      var prompter = Components.classes["@mozilla.org/updates/update-prompt;1"]
+                               .createInstance(Components.interfaces.nsIUpdatePrompt);
+      prompter.showUpdateHistory(window);
+    }
   },
-#endif
 
   // ENCRYPTION TAB
 
   /*
    * Preferences:
    *
    * security.default_personal_cert
    * - a string:
@@ -771,18 +783,18 @@ var gAdvancedPane = {
    */
   showSecurityDevices: function ()
   {
     openDialog("chrome://pippki/content/device_manager.xul",
                "mozilla:devicemanager",
                "modal=yes", null);
   },
 
-#ifdef MOZ_UPDATER
   observe: function (aSubject, aTopic, aData) {
-    switch(aTopic) {
-      case "nsPref:changed":
-        this.updateReadPrefs();
-        break;
+    if (AppConstants.MOZ_UPDATER) {
+      switch (aTopic) {
+        case "nsPref:changed":
+          this.updateReadPrefs();
+          break;
+      }
     }
   },
-#endif
 };
--- a/browser/components/preferences/in-content/applications.js
+++ b/browser/components/preferences/in-content/applications.js
@@ -75,47 +75,47 @@ const ICON_URL_APP = AppConstants.platfo
 // For CSS. Can be one of "ask", "save", "plugin" or "feed". If absent, the icon URL
 // was set by us to a custom handler icon and CSS should not try to override it.
 const APP_ICON_ATTR_NAME = "appHandlerIcon";
 
 //****************************************************************************//
 // Utilities
 
 function getFileDisplayName(file) {
-#ifdef XP_WIN
-  if (file instanceof Ci.nsILocalFileWin) {
-    try {
-      return file.getVersionInfoField("FileDescription");
-    } catch (e) {}
+  if (AppConstants.platform == "win") {
+    if (file instanceof Ci.nsILocalFileWin) {
+      try {
+        return file.getVersionInfoField("FileDescription");
+      } catch (e) {}
+    }
   }
-#endif
-#ifdef XP_MACOSX
-  if (file instanceof Ci.nsILocalFileMac) {
-    try {
-      return file.bundleDisplayName;
-    } catch (e) {}
+  if (AppConstants.platform == "macosx") {
+    if (file instanceof Ci.nsILocalFileMac) {
+      try {
+        return file.bundleDisplayName;
+      } catch (e) {}
+    }
   }
-#endif
   return file.leafName;
 }
 
 function getLocalHandlerApp(aFile) {
   var localHandlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
                         createInstance(Ci.nsILocalHandlerApp);
   localHandlerApp.name = getFileDisplayName(aFile);
   localHandlerApp.executable = aFile;
 
   return localHandlerApp;
 }
 
 /**
  * An enumeration of items in a JS array.
  *
  * FIXME: use ArrayConverter once it lands (bug 380839).
- * 
+ *
  * @constructor
  */
 function ArrayEnumerator(aItems) {
   this._index = 0;
   this._contents = aItems;
 }
 
 ArrayEnumerator.prototype = {
@@ -266,18 +266,17 @@ HandlerInfoWrapper.prototype = {
     // fall back to saving to disk, which is the default action in nsMIMEInfo.
     // Note: "save to disk" is an invalid value for protocol info objects,
     // but the alwaysAskBeforeHandling getter will detect that situation
     // and always return true in that case to override this invalid value.
     if (this.wrappedHandlerInfo.preferredAction == Ci.nsIHandlerInfo.useHelperApp &&
         !gApplicationsPane.isValidHandlerApp(this.preferredApplicationHandler)) {
       if (this.wrappedHandlerInfo.hasDefaultHandler)
         return Ci.nsIHandlerInfo.useSystemDefault;
-      else
-        return Ci.nsIHandlerInfo.saveToDisk;
+      return Ci.nsIHandlerInfo.saveToDisk;
     }
 
     return this.wrappedHandlerInfo.preferredAction;
   },
 
   set preferredAction(aNewValue) {
     // If the action is to use the plugin,
     // we must set the preferred action to "save to disk".
@@ -331,17 +330,17 @@ HandlerInfoWrapper.prototype = {
   // XXX Plugin objects contain an array of MimeType objects with "suffixes"
   // properties; if this object has an associated plugin, shouldn't we check
   // those properties for an extension?
   get primaryExtension() {
     try {
       if (this.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo &&
           this.wrappedHandlerInfo.primaryExtension)
         return this.wrappedHandlerInfo.primaryExtension
-    } catch(ex) {}
+    } catch (ex) {}
 
     return null;
   },
 
 
   //**************************************************************************//
   // Plugin Handling
 
@@ -450,20 +449,20 @@ HandlerInfoWrapper.prototype = {
 
 //****************************************************************************//
 // Feed Handler Info
 
 /**
  * This object implements nsIHandlerInfo for the feed types.  It's a separate
  * object because we currently store handling information for the feed type
  * in a set of preferences rather than the nsIHandlerService-managed datastore.
- * 
+ *
  * This object inherits from HandlerInfoWrapper in order to get functionality
  * that isn't special to the feed type.
- * 
+ *
  * XXX Should we inherit from HandlerInfoWrapper?  After all, we override
  * most of that wrapper's properties and methods, and we have to dance around
  * the fact that the wrapper expects to have a wrappedHandlerInfo, which we
  * don't provide.
  */
 
 function FeedHandlerInfo(aMIMEType) {
   HandlerInfoWrapper.call(this, aMIMEType, null);
@@ -474,23 +473,17 @@ FeedHandlerInfo.prototype = {
 
   //**************************************************************************//
   // Convenience Utils
 
   _converterSvc:
     Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
     getService(Ci.nsIWebContentConverterService),
 
-  _shellSvc:
-#ifdef HAVE_SHELL_SERVICE
-    getShellService(),
-#else
-    null,
-#endif
-
+  _shellSvc: AppConstants.HAVE_SHELL_SERVICE ? getShellService() : null,
 
   //**************************************************************************//
   // nsIHandlerInfo
 
   get description() {
     return this.element("bundlePreferences").getString(this._appPrefLabel);
   },
 
@@ -602,24 +595,24 @@ FeedHandlerInfo.prototype = {
   },
 
   __defaultApplicationHandler: undefined,
   get _defaultApplicationHandler() {
     if (typeof this.__defaultApplicationHandler != "undefined")
       return this.__defaultApplicationHandler;
 
     var defaultFeedReader = null;
-#ifdef HAVE_SHELL_SERVICE
-    try {
-      defaultFeedReader = this._shellSvc.defaultFeedReader;
+    if (AppConstants.HAVE_SHELL_SERVICE) {
+      try {
+        defaultFeedReader = this._shellSvc.defaultFeedReader;
+      }
+      catch (ex) {
+        // no default reader or _shellSvc is null
+      }
     }
-    catch(ex) {
-      // no default reader or _shellSvc is null
-    }
-#endif
 
     if (defaultFeedReader) {
       let handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
                        createInstance(Ci.nsIHandlerApp);
       handlerApp.name = getFileDisplayName(defaultFeedReader);
       handlerApp.QueryInterface(Ci.nsILocalHandlerApp);
       handlerApp.executable = defaultFeedReader;
 
@@ -628,25 +621,25 @@ FeedHandlerInfo.prototype = {
     else {
       this.__defaultApplicationHandler = null;
     }
 
     return this.__defaultApplicationHandler;
   },
 
   get hasDefaultHandler() {
-#ifdef HAVE_SHELL_SERVICE
-    try {
-      if (this._shellSvc.defaultFeedReader)
-        return true;
+    if (AppConstants.HAVE_SHELL_SERVICE) {
+      try {
+        if (this._shellSvc.defaultFeedReader)
+          return true;
+      }
+      catch (ex) {
+        // no default reader or _shellSvc is null
+      }
     }
-    catch(ex) {
-      // no default reader or _shellSvc is null
-    }
-#endif
 
     return false;
   },
 
   get defaultDescription() {
     if (this.hasDefaultHandler)
       return this._defaultApplicationHandler.name;
 
@@ -772,39 +765,39 @@ FeedHandlerInfo.prototype = {
   get smallIcon() {
     return this._smallIcon;
   }
 
 };
 
 var feedHandlerInfo = {
   __proto__: new FeedHandlerInfo(TYPE_MAYBE_FEED),
-  _prefSelectedApp: PREF_FEED_SELECTED_APP, 
-  _prefSelectedWeb: PREF_FEED_SELECTED_WEB, 
-  _prefSelectedAction: PREF_FEED_SELECTED_ACTION, 
+  _prefSelectedApp: PREF_FEED_SELECTED_APP,
+  _prefSelectedWeb: PREF_FEED_SELECTED_WEB,
+  _prefSelectedAction: PREF_FEED_SELECTED_ACTION,
   _prefSelectedReader: PREF_FEED_SELECTED_READER,
   _smallIcon: "chrome://browser/skin/feeds/feedIcon16.png",
   _appPrefLabel: "webFeed"
 }
 
 var videoFeedHandlerInfo = {
   __proto__: new FeedHandlerInfo(TYPE_MAYBE_VIDEO_FEED),
-  _prefSelectedApp: PREF_VIDEO_FEED_SELECTED_APP, 
-  _prefSelectedWeb: PREF_VIDEO_FEED_SELECTED_WEB, 
-  _prefSelectedAction: PREF_VIDEO_FEED_SELECTED_ACTION, 
+  _prefSelectedApp: PREF_VIDEO_FEED_SELECTED_APP,
+  _prefSelectedWeb: PREF_VIDEO_FEED_SELECTED_WEB,
+  _prefSelectedAction: PREF_VIDEO_FEED_SELECTED_ACTION,
   _prefSelectedReader: PREF_VIDEO_FEED_SELECTED_READER,
   _smallIcon: "chrome://browser/skin/feeds/videoFeedIcon16.png",
   _appPrefLabel: "videoPodcastFeed"
 }
 
 var audioFeedHandlerInfo = {
   __proto__: new FeedHandlerInfo(TYPE_MAYBE_AUDIO_FEED),
-  _prefSelectedApp: PREF_AUDIO_FEED_SELECTED_APP, 
-  _prefSelectedWeb: PREF_AUDIO_FEED_SELECTED_WEB, 
-  _prefSelectedAction: PREF_AUDIO_FEED_SELECTED_ACTION, 
+  _prefSelectedApp: PREF_AUDIO_FEED_SELECTED_APP,
+  _prefSelectedWeb: PREF_AUDIO_FEED_SELECTED_WEB,
+  _prefSelectedAction: PREF_AUDIO_FEED_SELECTED_ACTION,
   _prefSelectedReader: PREF_AUDIO_FEED_SELECTED_READER,
   _smallIcon: "chrome://browser/skin/feeds/audioFeedIcon16.png",
   _appPrefLabel: "audioPodcastFeed"
 }
 
 /**
  * InternalHandlerInfoWrapper provides a basic mechanism to create an internal
  * mime type handler that can be enabled/disabled in the applications preference
@@ -848,17 +841,17 @@ var pdfHandlerInfo = {
 
 //****************************************************************************//
 // Prefpane Controller
 
 var gApplicationsPane = {
   // The set of types the app knows how to handle.  A hash of HandlerInfoWrapper
   // objects, indexed by type.
   _handledTypes: {},
-  
+
   // The list of types we can show, sorted by the sort column/direction.
   // An array of HandlerInfoWrapper objects.  We build this list when we first
   // load the data and then rebuild it when users change a pref that affects
   // what types we can show or change the sort column/direction.
   // Note: this isn't necessarily the list of types we *will* show; if the user
   // provides a filter string, we'll only show the subset of types in this list
   // that match that string.
   _visibleTypes: [],
@@ -949,17 +942,17 @@ var gApplicationsPane = {
     if (document.getElementById("actionColumn").hasAttribute("sortDirection")) {
       this._sortColumn = document.getElementById("actionColumn");
       // The typeColumn element always has a sortDirection attribute,
       // either because it was persisted or because the default value
       // from the xul file was used.  If we are sorting on the other
       // column, we should remove it.
       document.getElementById("typeColumn").removeAttribute("sortDirection");
     }
-    else 
+    else
       this._sortColumn = document.getElementById("typeColumn");
 
     // Load the data and build the list of handlers.
     // By doing this in a timeout, we let the preferences dialog resize itself
     // to an appropriate size before we add a bunch of items to the list.
     // Otherwise, if there are many items, and the Applications prefpane
     // is the one that gets displayed when the user first opens the dialog,
     // the dialog might stretch too much in an attempt to fit them all in.
@@ -1251,18 +1244,17 @@ var gApplicationsPane = {
   _describePreferredAction: function(aHandlerInfo) {
     // alwaysAskBeforeHandling overrides the preferred action, so if that flag
     // is set, then describe that behavior instead.  For most types, this is
     // the "alwaysAsk" string, but for the feed type we show something special.
     if (aHandlerInfo.alwaysAskBeforeHandling) {
       if (isFeedType(aHandlerInfo.type))
         return this._prefsBundle.getFormattedString("previewInApp",
                                                     [this._brandShortName]);
-      else
-        return this._prefsBundle.getString("alwaysAsk");
+      return this._prefsBundle.getString("alwaysAsk");
     }
 
     switch (aHandlerInfo.preferredAction) {
       case Ci.nsIHandlerInfo.saveToDisk:
         return this._prefsBundle.getString("saveFile");
 
       case Ci.nsIHandlerInfo.useHelperApp:
         var preferredApp = aHandlerInfo.preferredApplicationHandler;
@@ -1301,16 +1293,18 @@ var gApplicationsPane = {
       case Ci.nsIHandlerInfo.useSystemDefault:
         return this._prefsBundle.getFormattedString("useDefault",
                                                     [aHandlerInfo.defaultDescription]);
 
       case kActionUsePlugin:
         return this._prefsBundle.getFormattedString("usePluginIn",
                                                     [aHandlerInfo.pluginName,
                                                      this._brandShortName]);
+      default:
+        throw new Error(`Unexpected preferredAction: ${aHandlerInfo.preferredAction}`);
     }
   },
 
   _selectLastSelectedType: function() {
     // If the list is disabled by the pref.downloads.disable_button.edit_actions
     // preference being locked, then don't select the type, as that would cause
     // it to appear selected, with a different background and an actions menu
     // that makes it seem like you can choose an action for the type.
@@ -1347,31 +1341,31 @@ var gApplicationsPane = {
 
     if (aHandlerApp instanceof Ci.nsIWebContentHandlerInfo)
       return aHandlerApp.uri;
 
     return false;
   },
 
   _isValidHandlerExecutable: function(aExecutable) {
+    let leafName;
+    if (AppConstants.platform == "win") {
+      leafName = `${AppConstants.MOZ_APP_NAME}.exe`;
+    } else if (AppConstants.platform == "macosx") {
+      leafName = AppConstants.MOZ_MACBUNDLE_NAME;
+    } else {
+      leafName = `${AppConstants.MOZ_APP_NAME}-bin`;
+    }
     return aExecutable &&
            aExecutable.exists() &&
            aExecutable.isExecutable() &&
 // XXXben - we need to compare this with the running instance executable
 //          just don't know how to do that via script...
 // XXXmano TBD: can probably add this to nsIShellService
-#ifdef XP_WIN
-#expand    aExecutable.leafName != "__MOZ_APP_NAME__.exe";
-#else
-#ifdef XP_MACOSX
-#expand    aExecutable.leafName != "__MOZ_MACBUNDLE_NAME__";
-#else
-#expand    aExecutable.leafName != "__MOZ_APP_NAME__-bin";
-#endif
-#endif
+           aExecutable.leafName != leafName;
   },
 
   /**
    * Rebuild the actions menu for the selected entry.  Gets called by
    * the richlistitem constructor when an entry in the list gets selected.
    */
   rebuildActionsMenu: function() {
     var typeItem = this._list.selectedItem;
@@ -1381,17 +1375,17 @@ var gApplicationsPane = {
     var menuPopup = menu.menupopup;
 
     // Clear out existing items.
     while (menuPopup.hasChildNodes())
       menuPopup.removeChild(menuPopup.lastChild);
 
     // Add the "Preview in Firefox" option for optional internal handlers.
     if (handlerInfo instanceof InternalHandlerInfoWrapper) {
-      var internalMenuItem = document.createElement("menuitem");
+      let internalMenuItem = document.createElement("menuitem");
       internalMenuItem.setAttribute("action", Ci.nsIHandlerInfo.handleInternally);
       let label = this._prefsBundle.getFormattedString("previewInApp",
                                                        [this._brandShortName]);
       internalMenuItem.setAttribute("label", label);
       internalMenuItem.setAttribute("tooltiptext", label);
       internalMenuItem.setAttribute(APP_ICON_ATTR_NAME, "ask");
       menuPopup.appendChild(internalMenuItem);
     }
@@ -1423,17 +1417,17 @@ var gApplicationsPane = {
       saveMenuItem.setAttribute("label", label);
       saveMenuItem.setAttribute("tooltiptext", label);
       saveMenuItem.setAttribute(APP_ICON_ATTR_NAME, "save");
       menuPopup.appendChild(saveMenuItem);
     }
 
     // If this is the feed type, add a Live Bookmarks item.
     if (isFeedType(handlerInfo.type)) {
-      var internalMenuItem = document.createElement("menuitem");
+      let internalMenuItem = document.createElement("menuitem");
       internalMenuItem.setAttribute("action", Ci.nsIHandlerInfo.handleInternally);
       let label = this._prefsBundle.getFormattedString("addLiveBookmarksInApp",
                                                        [this._brandShortName]);
       internalMenuItem.setAttribute("label", label);
       internalMenuItem.setAttribute("tooltiptext", label);
       internalMenuItem.setAttribute(APP_ICON_ATTR_NAME, "feed");
       menuPopup.appendChild(internalMenuItem);
     }
@@ -1495,23 +1489,23 @@ var gApplicationsPane = {
       pluginMenuItem.setAttribute("label", label);
       pluginMenuItem.setAttribute("tooltiptext", label);
       pluginMenuItem.setAttribute(APP_ICON_ATTR_NAME, "plugin");
       menuPopup.appendChild(pluginMenuItem);
     }
 
     // Create a menu item for selecting a local application.
     let canOpenWithOtherApp = true;
-#ifdef XP_WIN
-    // On Windows, selecting an application to open another application
-    // would be meaningless so we special case executables.
-    let executableType = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService)
-                                                  .getTypeFromExtension("exe");
-    canOpenWithOtherApp = handlerInfo.type != executableType;
-#endif
+    if (AppConstants.platform == "win") {
+      // On Windows, selecting an application to open another application
+      // would be meaningless so we special case executables.
+      let executableType = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService)
+                                                    .getTypeFromExtension("exe");
+      canOpenWithOtherApp = handlerInfo.type != executableType;
+    }
     if (canOpenWithOtherApp)
     {
       let menuItem = document.createElement("menuitem");
       menuItem.className = "choose-app-item";
       menuItem.addEventListener("command", function(e) {
         gApplicationsPane.chooseApp(e);
       });
       let label = this._prefsBundle.getString("useOtherApp");
@@ -1543,17 +1537,17 @@ var gApplicationsPane = {
       case Ci.nsIHandlerInfo.handleInternally:
         menu.selectedItem = internalMenuItem;
         break;
       case Ci.nsIHandlerInfo.useSystemDefault:
         menu.selectedItem = defaultMenuItem;
         break;
       case Ci.nsIHandlerInfo.useHelperApp:
         if (preferredApp)
-          menu.selectedItem = 
+          menu.selectedItem =
             possibleAppMenuItems.filter(v => v.handlerApp.equals(preferredApp))[0];
         break;
       case kActionUsePlugin:
         menu.selectedItem = pluginMenuItem;
         break;
       case Ci.nsIHandlerInfo.saveToDisk:
         menu.selectedItem = saveMenuItem;
         break;
@@ -1744,72 +1738,71 @@ var gApplicationsPane = {
             actionsMenu.selectedIndex = i;
             this.onSelectAction(menuItem);
             break;
           }
         }
       }
     }.bind(this);
 
-#ifdef XP_WIN
-    var params = {};
-    var handlerInfo = this._handledTypes[this._list.selectedItem.type];
-
-    if (isFeedType(handlerInfo.type)) {
-      // MIME info will be null, create a temp object.
-      params.mimeInfo = this._mimeSvc.getFromTypeAndExtension(handlerInfo.type, 
-                                                 handlerInfo.primaryExtension);
-    } else {
-      params.mimeInfo = handlerInfo.wrappedHandlerInfo;
-    }
+    if (AppConstants.platform == "win") {
+      var params = {};
+      var handlerInfo = this._handledTypes[this._list.selectedItem.type];
 
-    params.title         = this._prefsBundle.getString("fpTitleChooseApp");
-    params.description   = handlerInfo.description;
-    params.filename      = null;
-    params.handlerApp    = null;
-
-    let onAppSelected = () => {
-      if (this.isValidHandlerApp(params.handlerApp)) {
-        handlerApp = params.handlerApp;
-
-        // Add the app to the type's list of possible handlers.
-        handlerInfo.addPossibleApplicationHandler(handlerApp);
+      if (isFeedType(handlerInfo.type)) {
+        // MIME info will be null, create a temp object.
+        params.mimeInfo = this._mimeSvc.getFromTypeAndExtension(handlerInfo.type,
+                                                   handlerInfo.primaryExtension);
+      } else {
+        params.mimeInfo = handlerInfo.wrappedHandlerInfo;
       }
 
-      chooseAppCallback(handlerApp);
-    };
-
-    gSubDialog.open("chrome://global/content/appPicker.xul",
-                    null, params, onAppSelected);
+      params.title         = this._prefsBundle.getString("fpTitleChooseApp");
+      params.description   = handlerInfo.description;
+      params.filename      = null;
+      params.handlerApp    = null;
 
-#else
-    let winTitle = this._prefsBundle.getString("fpTitleChooseApp");
-    let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
-    let fpCallback = function fpCallback_done(aResult) {
-      if (aResult == Ci.nsIFilePicker.returnOK && fp.file &&
-          this._isValidHandlerExecutable(fp.file)) {
-        handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
-                     createInstance(Ci.nsILocalHandlerApp);
-        handlerApp.name = getFileDisplayName(fp.file);
-        handlerApp.executable = fp.file;
+      let onAppSelected = () => {
+        if (this.isValidHandlerApp(params.handlerApp)) {
+          handlerApp = params.handlerApp;
 
-        // Add the app to the type's list of possible handlers.
-        let handlerInfo = this._handledTypes[this._list.selectedItem.type];
-        handlerInfo.addPossibleApplicationHandler(handlerApp);
+          // Add the app to the type's list of possible handlers.
+          handlerInfo.addPossibleApplicationHandler(handlerApp);
+        }
 
         chooseAppCallback(handlerApp);
-      }
-    }.bind(this);
+      };
+
+      gSubDialog.open("chrome://global/content/appPicker.xul",
+                      null, params, onAppSelected);
+    } else {
+      let winTitle = this._prefsBundle.getString("fpTitleChooseApp");
+      let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+      let fpCallback = function fpCallback_done(aResult) {
+        if (aResult == Ci.nsIFilePicker.returnOK && fp.file &&
+            this._isValidHandlerExecutable(fp.file)) {
+          handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
+                       createInstance(Ci.nsILocalHandlerApp);
+          handlerApp.name = getFileDisplayName(fp.file);
+          handlerApp.executable = fp.file;
 
-    // Prompt the user to pick an app.  If they pick one, and it's a valid
-    // selection, then add it to the list of possible handlers.
-    fp.init(window, winTitle, Ci.nsIFilePicker.modeOpen);
-    fp.appendFilters(Ci.nsIFilePicker.filterApps);
-    fp.open(fpCallback);
-#endif
+          // Add the app to the type's list of possible handlers.
+          let handlerInfo = this._handledTypes[this._list.selectedItem.type];
+          handlerInfo.addPossibleApplicationHandler(handlerApp);
+
+          chooseAppCallback(handlerApp);
+        }
+      }.bind(this);
+
+      // Prompt the user to pick an app.  If they pick one, and it's a valid
+      // selection, then add it to the list of possible handlers.
+      fp.init(window, winTitle, Ci.nsIFilePicker.modeOpen);
+      fp.appendFilters(Ci.nsIFilePicker.filterApps);
+      fp.open(fpCallback);
+    }
   },
 
   // Mark which item in the list was last selected so we can reselect it
   // when we rebuild the list or when the user returns to the prefpane.
   onSelectionChanged: function() {
     if (this._list.selectedItem)
       this._list.setAttribute("lastSelectedType",
                               this._list.selectedItem.getAttribute("type"));
@@ -1853,17 +1846,17 @@ var gApplicationsPane = {
     switch (aHandlerInfo.preferredAction) {
       case Ci.nsIHandlerInfo.useSystemDefault:
         return this._getIconURLForSystemDefault(aHandlerInfo);
 
       case Ci.nsIHandlerInfo.useHelperApp:
         let preferredApp = aHandlerInfo.preferredApplicationHandler;
         if (this.isValidHandlerApp(preferredApp))
           return this._getIconURLForHandlerApp(preferredApp);
-        break;
+        // Explicit fall-through
 
       // This should never happen, but if preferredAction is set to some weird
       // value, then fall back to the generic application icon.
       default:
         return ICON_URL_APP;
     }
   },
 
@@ -1914,17 +1907,17 @@ var gApplicationsPane = {
 
       if (wrappedHandlerInfo instanceof Ci.nsIMIMEInfo &&
           wrappedHandlerInfo instanceof Ci.nsIPropertyBag) {
         try {
           let url = wrappedHandlerInfo.getProperty("defaultApplicationIconURL");
           if (url)
             return url + "?size=16";
         }
-        catch(ex) {}
+        catch (ex) {}
       }
     }
 
     // If this isn't a MIME type object on an OS that supports retrieving
     // the icon, or if we couldn't retrieve the icon for some other reason,
     // then use a generic icon.
     return ICON_URL_APP;
   }
--- a/browser/components/preferences/in-content/jar.mn
+++ b/browser/components/preferences/in-content/jar.mn
@@ -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/.
 
 browser.jar:
    content/browser/preferences/in-content/preferences.js
 *  content/browser/preferences/in-content/preferences.xul
    content/browser/preferences/in-content/subdialogs.js
 
-*  content/browser/preferences/in-content/main.js
+   content/browser/preferences/in-content/main.js
    content/browser/preferences/in-content/privacy.js
-*  content/browser/preferences/in-content/advanced.js
-*  content/browser/preferences/in-content/applications.js
+   content/browser/preferences/in-content/advanced.js
+   content/browser/preferences/in-content/applications.js
    content/browser/preferences/in-content/content.js
    content/browser/preferences/in-content/sync.js
    content/browser/preferences/in-content/security.js
    content/browser/preferences/in-content/search.js
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -6,230 +6,231 @@ Components.utils.import("resource://gre/
 Components.utils.import("resource://gre/modules/FileUtils.jsm");
 Components.utils.import("resource://gre/modules/Task.jsm");
 Components.utils.import("resource:///modules/ShellService.jsm");
 Components.utils.import("resource:///modules/TransientPrefs.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
 
-#ifdef E10S_TESTING_ONLY
-XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
-                                  "resource://gre/modules/UpdateUtils.jsm");
-#endif
+if (AppConstants.E10S_TESTING_ONLY) {
+  XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+                                    "resource://gre/modules/UpdateUtils.jsm");
+}
 
 var gMainPane = {
   /**
    * Initialization of this.
    */
   init: function ()
   {
     function setEventListener(aId, aEventType, aCallback)
     {
       document.getElementById(aId)
               .addEventListener(aEventType, aCallback.bind(gMainPane));
     }
 
-#ifdef HAVE_SHELL_SERVICE
-    this.updateSetDefaultBrowser();
-#ifdef XP_WIN
-    // In Windows 8 we launch the control panel since it's the only
-    // way to get all file type association prefs. So we don't know
-    // when the user will select the default.  We refresh here periodically
-    // in case the default changes. On other Windows OS's defaults can also
-    // be set while the prefs are open.
-    window.setInterval(this.updateSetDefaultBrowser.bind(this), 1000);
-#endif
-#endif
+    if (AppConstants.HAVE_SHELL_SERVICE) {
+      this.updateSetDefaultBrowser();
+      if (AppConstants.platform == "win") {
+        // In Windows 8 we launch the control panel since it's the only
+        // way to get all file type association prefs. So we don't know
+        // when the user will select the default.  We refresh here periodically
+        // in case the default changes. On other Windows OS's defaults can also
+        // be set while the prefs are open.
+        window.setInterval(this.updateSetDefaultBrowser.bind(this), 1000);
+      }
+    }
 
     // set up the "use current page" label-changing listener
     this._updateUseCurrentButton();
     window.addEventListener("focus", this._updateUseCurrentButton.bind(this), false);
 
     this.updateBrowserStartupLastSession();
 
-#ifdef XP_WIN
-    // Functionality for "Show tabs in taskbar" on Windows 7 and up.
-    try {
-      let sysInfo = Cc["@mozilla.org/system-info;1"].
-                    getService(Ci.nsIPropertyBag2);
-      let ver = parseFloat(sysInfo.getProperty("version"));
-      let showTabsInTaskbar = document.getElementById("showTabsInTaskbar");
-      showTabsInTaskbar.hidden = ver < 6.1;
-    } catch (ex) {}
-#endif
+    if (AppConstants.platform == "win") {
+      // Functionality for "Show tabs in taskbar" on Windows 7 and up.
+      try {
+        let sysInfo = Cc["@mozilla.org/system-info;1"].
+                      getService(Ci.nsIPropertyBag2);
+        let ver = parseFloat(sysInfo.getProperty("version"));
+        let showTabsInTaskbar = document.getElementById("showTabsInTaskbar");
+        showTabsInTaskbar.hidden = ver < 6.1;
+      } catch (ex) {}
+    }
 
     // The "closing multiple tabs" and "opening multiple tabs might slow down
     // &brandShortName;" warnings provide options for not showing these
     // warnings again. When the user disabled them, we provide checkboxes to
     // re-enable the warnings.
     if (!TransientPrefs.prefShouldBeVisible("browser.tabs.warnOnClose"))
       document.getElementById("warnCloseMultiple").hidden = true;
     if (!TransientPrefs.prefShouldBeVisible("browser.tabs.warnOnOpen"))
       document.getElementById("warnOpenMany").hidden = true;
 
     setEventListener("browser.privatebrowsing.autostart", "change",
                      gMainPane.updateBrowserStartupLastSession);
     setEventListener("browser.download.dir", "change",
                      gMainPane.displayDownloadDirPref);
-#ifdef HAVE_SHELL_SERVICE
-    setEventListener("setDefaultButton", "command",
-                     gMainPane.setDefaultBrowser);
-#endif
+    if (AppConstants.HAVE_SHELL_SERVICE) {
+      setEventListener("setDefaultButton", "command",
+                       gMainPane.setDefaultBrowser);
+    }
     setEventListener("useCurrent", "command",
                      gMainPane.setHomePageToCurrent);
     setEventListener("useBookmark", "command",
                      gMainPane.setHomePageToBookmark);
     setEventListener("restoreDefaultHomePage", "command",
                      gMainPane.restoreDefaultHomePage);
     setEventListener("chooseFolder", "command",
                      gMainPane.chooseFolder);
 
-#ifdef E10S_TESTING_ONLY
-    setEventListener("e10sAutoStart", "command",
-                     gMainPane.enableE10SChange);
-    let e10sCheckbox = document.getElementById("e10sAutoStart");
+    if (AppConstants.E10S_TESTING_ONLY) {
+      setEventListener("e10sAutoStart", "command",
+                       gMainPane.enableE10SChange);
+      let e10sCheckbox = document.getElementById("e10sAutoStart");
 
-    let e10sPref = document.getElementById("browser.tabs.remote.autostart");
-    let e10sTempPref = document.getElementById("e10sTempPref");
-    let e10sForceEnable = document.getElementById("e10sForceEnable");
+      let e10sPref = document.getElementById("browser.tabs.remote.autostart");
+      let e10sTempPref = document.getElementById("e10sTempPref");
+      let e10sForceEnable = document.getElementById("e10sForceEnable");
 
-    let preffedOn = e10sPref.value || e10sTempPref.value || e10sForceEnable.value;
+      let preffedOn = e10sPref.value || e10sTempPref.value || e10sForceEnable.value;
 
-    if (preffedOn) {
-      // The checkbox is checked if e10s is preffed on and enabled.
-      e10sCheckbox.checked = Services.appinfo.browserTabsRemoteAutostart;
+      if (preffedOn) {
+        // The checkbox is checked if e10s is preffed on and enabled.
+        e10sCheckbox.checked = Services.appinfo.browserTabsRemoteAutostart;
 
-      // but if it's force disabled, then the checkbox is disabled.
-      e10sCheckbox.disabled = !Services.appinfo.browserTabsRemoteAutostart;
+        // but if it's force disabled, then the checkbox is disabled.
+        e10sCheckbox.disabled = !Services.appinfo.browserTabsRemoteAutostart;
+      }
     }
 
-#endif
-
-#ifdef MOZ_DEV_EDITION
-    let uAppData = OS.Constants.Path.userApplicationDataDir;
-    let ignoreSeparateProfile = OS.Path.join(uAppData, "ignore-dev-edition-profile");
+    if (AppConstants.MOZ_DEV_EDITION) {
+      let uAppData = OS.Constants.Path.userApplicationDataDir;
+      let ignoreSeparateProfile = OS.Path.join(uAppData, "ignore-dev-edition-profile");
 
-    setEventListener("separateProfileMode", "command", gMainPane.separateProfileModeChange);
-    let separateProfileModeCheckbox = document.getElementById("separateProfileMode");
-    setEventListener("getStarted", "click", gMainPane.onGetStarted);
+      setEventListener("separateProfileMode", "command", gMainPane.separateProfileModeChange);
+      let separateProfileModeCheckbox = document.getElementById("separateProfileMode");
+      setEventListener("getStarted", "click", gMainPane.onGetStarted);
 
-    OS.File.stat(ignoreSeparateProfile).then(() => separateProfileModeCheckbox.checked = false,
-                                             () => separateProfileModeCheckbox.checked = true);
-#endif
+      OS.File.stat(ignoreSeparateProfile).then(() => separateProfileModeCheckbox.checked = false,
+                                               () => separateProfileModeCheckbox.checked = true);
+    }
 
     // Notify observers that the UI is now ready
     Components.classes["@mozilla.org/observer-service;1"]
               .getService(Components.interfaces.nsIObserverService)
               .notifyObservers(window, "main-pane-loaded", null);
   },
 
-#ifdef E10S_TESTING_ONLY
   enableE10SChange: function ()
   {
-    let e10sCheckbox = document.getElementById("e10sAutoStart");
-    let e10sPref = document.getElementById("browser.tabs.remote.autostart");
-    let e10sTempPref = document.getElementById("e10sTempPref");
-
-    let prefsToChange;
-    if (e10sCheckbox.checked) {
-      // Enabling e10s autostart
-      prefsToChange = [e10sPref];
-    } else {
-      // Disabling e10s autostart
-      prefsToChange = [e10sPref];
-      if (e10sTempPref.value) {
-       prefsToChange.push(e10sTempPref);
-      }
-    }
-
-    let buttonIndex = confirmRestartPrompt(e10sCheckbox.checked, 0,
-                                           true, false);
-    if (buttonIndex == CONFIRM_RESTART_PROMPT_RESTART_NOW) {
-      const Cc = Components.classes, Ci = Components.interfaces;
-      let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
-                         .createInstance(Ci.nsISupportsPRBool);
-      Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
-                                   "restart");
-      if (!cancelQuit.data) {
-        for (let prefToChange of prefsToChange) {
-          prefToChange.value = e10sCheckbox.checked;
-        }
-
-        Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit |  Ci.nsIAppStartup.eRestart);
-      }
-    }
+    if (AppConstants.E10S_TESTING_ONLY) {
+      let e10sCheckbox = document.getElementById("e10sAutoStart");
+      let e10sPref = document.getElementById("browser.tabs.remote.autostart");
+      let e10sTempPref = document.getElementById("e10sTempPref");
 
-    // Revert the checkbox in case we didn't quit
-    e10sCheckbox.checked = e10sPref.value || e10sTempPref.value;
-  },
-#endif
-
-#ifdef MOZ_DEV_EDITION
-  separateProfileModeChange: function ()
-  {
-    function quitApp() {
-      Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit |  Ci.nsIAppStartup.eRestartNotSameProfile);
-    }
-    function revertCheckbox(error) {
-      separateProfileModeCheckbox.checked = !separateProfileModeCheckbox.checked;
-      if (error) {
-        Cu.reportError("Failed to toggle separate profile mode: " + error);
+      let prefsToChange;
+      if (e10sCheckbox.checked) {
+        // Enabling e10s autostart
+        prefsToChange = [e10sPref];
+      } else {
+        // Disabling e10s autostart
+        prefsToChange = [e10sPref];
+        if (e10sTempPref.value) {
+         prefsToChange.push(e10sTempPref);
+        }
       }
-    }
-    function createOrRemoveSpecialDevEditionFile(onSuccess) {
-      let uAppData = OS.Constants.Path.userApplicationDataDir;
-      let ignoreSeparateProfile = OS.Path.join(uAppData, "ignore-dev-edition-profile");
 
-      if (separateProfileModeCheckbox.checked) {
-        OS.File.remove(ignoreSeparateProfile).then(onSuccess, revertCheckbox);
-      } else {
-        OS.File.writeAtomic(ignoreSeparateProfile, new Uint8Array()).then(onSuccess, revertCheckbox);
-      }
-    }
-
-    let separateProfileModeCheckbox = document.getElementById("separateProfileMode");
-    let button_index = confirmRestartPrompt(separateProfileModeCheckbox.checked,
-                                            0, false, true);
-    switch (button_index) {
-      case CONFIRM_RESTART_PROMPT_CANCEL:
-        revertCheckbox();
-        return;
-      case CONFIRM_RESTART_PROMPT_RESTART_NOW:
+      let buttonIndex = confirmRestartPrompt(e10sCheckbox.checked, 0,
+                                             true, false);
+      if (buttonIndex == CONFIRM_RESTART_PROMPT_RESTART_NOW) {
         const Cc = Components.classes, Ci = Components.interfaces;
         let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
                            .createInstance(Ci.nsISupportsPRBool);
         Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
-                                      "restart");
+                                     "restart");
         if (!cancelQuit.data) {
-          createOrRemoveSpecialDevEditionFile(quitApp);
-          return;
+          for (let prefToChange of prefsToChange) {
+            prefToChange.value = e10sCheckbox.checked;
+          }
+
+          Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit |  Ci.nsIAppStartup.eRestart);
         }
+      }
+
+      // Revert the checkbox in case we didn't quit
+      e10sCheckbox.checked = e10sPref.value || e10sTempPref.value;
+    }
+  },
+
+  separateProfileModeChange: function ()
+  {
+    if (AppConstants.MOZ_DEV_EDITION) {
+      function quitApp() {
+        Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit |  Ci.nsIAppStartup.eRestartNotSameProfile);
+      }
+      function revertCheckbox(error) {
+        separateProfileModeCheckbox.checked = !separateProfileModeCheckbox.checked;
+        if (error) {
+          Cu.reportError("Failed to toggle separate profile mode: " + error);
+        }
+      }
+      function createOrRemoveSpecialDevEditionFile(onSuccess) {
+        let uAppData = OS.Constants.Path.userApplicationDataDir;
+        let ignoreSeparateProfile = OS.Path.join(uAppData, "ignore-dev-edition-profile");
 
-        // Revert the checkbox in case we didn't quit
-        revertCheckbox();
-        return;
-      case CONFIRM_RESTART_PROMPT_RESTART_LATER:
-        createOrRemoveSpecialDevEditionFile();
-        return;
+        if (separateProfileModeCheckbox.checked) {
+          OS.File.remove(ignoreSeparateProfile).then(onSuccess, revertCheckbox);
+        } else {
+          OS.File.writeAtomic(ignoreSeparateProfile, new Uint8Array()).then(onSuccess, revertCheckbox);
+        }
+      }
+
+      let separateProfileModeCheckbox = document.getElementById("separateProfileMode");
+      let button_index = confirmRestartPrompt(separateProfileModeCheckbox.checked,
+                                              0, false, true);
+      switch (button_index) {
+        case CONFIRM_RESTART_PROMPT_CANCEL:
+          revertCheckbox();
+          return;
+        case CONFIRM_RESTART_PROMPT_RESTART_NOW:
+          const Cc = Components.classes, Ci = Components.interfaces;
+          let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
+                             .createInstance(Ci.nsISupportsPRBool);
+          Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
+                                        "restart");
+          if (!cancelQuit.data) {
+            createOrRemoveSpecialDevEditionFile(quitApp);
+            return;
+          }
+
+          // Revert the checkbox in case we didn't quit
+          revertCheckbox();
+          return;
+        case CONFIRM_RESTART_PROMPT_RESTART_LATER:
+          createOrRemoveSpecialDevEditionFile();
+          return;
+      }
     }
   },
 
   onGetStarted: function (aEvent) {
-    const Cc = Components.classes, Ci = Components.interfaces;
-    let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
-                .getService(Ci.nsIWindowMediator);
-    let win = wm.getMostRecentWindow("navigator:browser");
+    if (AppConstants.MOZ_DEV_EDITION) {
+      const Cc = Components.classes, Ci = Components.interfaces;
+      let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
+                  .getService(Ci.nsIWindowMediator);
+      let win = wm.getMostRecentWindow("navigator:browser");
 
-    if (win) {
-      let accountsTab = win.gBrowser.addTab("about:accounts?action=signin&entrypoint=dev-edition-setup");
-      win.gBrowser.selectedTab = accountsTab;
+      if (win) {
+        let accountsTab = win.gBrowser.addTab("about:accounts?action=signin&entrypoint=dev-edition-setup");
+        win.gBrowser.selectedTab = accountsTab;
+      }
     }
   },
-#endif
 
   // HOME PAGE
 
   /*
    * Preferences:
    *
    * browser.startup.homepage
    * - the user's home page, as a string; if the home page is a set of tabs,
@@ -329,18 +330,18 @@ var gMainPane = {
     let tabs = this._getTabsForHomePage();
 
     if (tabs.length > 1)
       useCurrent.label = useCurrent.getAttribute("label2");
     else
       useCurrent.label = useCurrent.getAttribute("label1");
 
     // In this case, the button's disabled state is set by preferences.xml.
-    if (document.getElementById
-        ("pref.browser.homepage.disable_button.current_page").locked)
+    let prefName = "pref.browser.homepage.disable_button.current_page";
+    if (document.getElementById(prefName).locked)
       return;
 
     useCurrent.disabled = !tabs.length
   },
 
   _getTabsForHomePage: function ()
   {
     var win;
@@ -658,63 +659,63 @@ var gMainPane = {
    * Determines where a link which opens a new window will open.
    *
    * @returns 2 if such links should be opened in new windows,
    *          3 if such links should be opened in new tabs
    */
   writeLinkTarget: function() {
     var linkTargeting = document.getElementById("linkTargeting");
     return linkTargeting.checked ? 3 : 2;
-  }
-
-#ifdef HAVE_SHELL_SERVICE
-  ,
+  },
   /*
    * Preferences:
    *
    * browser.shell.checkDefault
    * - true if a default-browser check (and prompt to make it so if necessary)
    *   occurs at startup, false otherwise
    */
 
   /**
    * Show button for setting browser as default browser or information that
    * browser is already the default browser.
    */
   updateSetDefaultBrowser: function()
   {
-    let shellSvc = getShellService();
-    let defaultBrowserBox = document.getElementById("defaultBrowserBox");
-    if (!shellSvc) {
-      defaultBrowserBox.hidden = true;
-      return;
+    if (AppConstants.HAVE_SHELL_SERVICE) {
+      let shellSvc = getShellService();
+      let defaultBrowserBox = document.getElementById("defaultBrowserBox");
+      if (!shellSvc) {
+        defaultBrowserBox.hidden = true;
+        return;
+      }
+      let setDefaultPane = document.getElementById("setDefaultPane");
+      let isDefault = shellSvc.isDefaultBrowser(false, true);
+      setDefaultPane.selectedIndex = isDefault ? 1 : 0;
+      let alwaysCheck = document.getElementById("alwaysCheckDefault");
+      alwaysCheck.disabled = alwaysCheck.disabled ||
+                             isDefault && alwaysCheck.checked;
     }
-    let setDefaultPane = document.getElementById("setDefaultPane");
-    let isDefault = shellSvc.isDefaultBrowser(false, true);
-    setDefaultPane.selectedIndex = isDefault ? 1 : 0;
-    let alwaysCheck = document.getElementById("alwaysCheckDefault");
-    alwaysCheck.disabled = alwaysCheck.disabled ||
-                           isDefault && alwaysCheck.checked;
   },
 
   /**
    * Set browser as the operating system default browser.
    */
   setDefaultBrowser: function()
   {
-    let alwaysCheckPref = document.getElementById("browser.shell.checkDefaultBrowser");
-    alwaysCheckPref.value = true;
+    if (AppConstants.HAVE_SHELL_SERVICE) {
+      let alwaysCheckPref = document.getElementById("browser.shell.checkDefaultBrowser");
+      alwaysCheckPref.value = true;
 
-    let shellSvc = getShellService();
-    if (!shellSvc)
-      return;
-    try {
-      shellSvc.setDefaultBrowser(true, false);
-    } catch (ex) {
-      Cu.reportError(ex);
-      return;
+      let shellSvc = getShellService();
+      if (!shellSvc)
+        return;
+      try {
+        shellSvc.setDefaultBrowser(true, false);
+      } catch (ex) {
+        Cu.reportError(ex);
+        return;
+      }
+
+      let selectedIndex = shellSvc.isDefaultBrowser(false, true) ? 1 : 0;
+      document.getElementById("setDefaultPane").selectedIndex = selectedIndex;
     }
-
-    let selectedIndex = shellSvc.isDefaultBrowser(false, true) ? 1 : 0;
-    document.getElementById("setDefaultPane").selectedIndex = selectedIndex;
-  }
-#endif
+  },
 };
--- a/browser/components/preferences/in-content/preferences.js
+++ b/browser/components/preferences/in-content/preferences.js
@@ -216,17 +216,17 @@ function confirmRestartPrompt(aRestartTo
   // Set up the third (index 2) button:
   let button2Text = null;
   if (aWantRestartLaterButton) {
     button2Text = bundle.getString("restartLater");
     buttonFlags += (Services.prompt.BUTTON_POS_2 *
                     Services.prompt.BUTTON_TITLE_IS_STRING);
   }
 
-  switch(aDefaultButtonIndex) {
+  switch (aDefaultButtonIndex) {
     case 0:
       buttonFlags += Services.prompt.BUTTON_POS_0_DEFAULT;
       break;
     case 1:
       buttonFlags += Services.prompt.BUTTON_POS_1_DEFAULT;
       break;
     case 2:
       buttonFlags += Services.prompt.BUTTON_POS_2_DEFAULT;
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -139,19 +139,23 @@ var gPrivacyPane = {
    * Selects the right item of the Tracking Protection radiogroup.
    */
   trackingProtectionReadPrefs() {
     let enabledPref = document.getElementById("privacy.trackingprotection.enabled");
     let pbmPref = document.getElementById("privacy.trackingprotection.pbmode.enabled");
     let radiogroup = document.getElementById("trackingProtectionRadioGroup");
 
     // Global enable takes precedence over enabled in Private Browsing.
-    radiogroup.value = enabledPref.value ? "always" :
-                       pbmPref.value ? "private" :
-                       "never";
+    if (enabledPref.value) {
+      radiogroup.value = "always";
+    } else if (pbmPref.value) {
+      radiogroup.value = "private";
+    } else {
+      radiogroup.value = "never";
+    }
   },
 
   /**
    * Sets the pref values based on the selected item of the radiogroup.
    */
   trackingProtectionWritePrefs() {
     let enabledPref = document.getElementById("privacy.trackingprotection.enabled");
     let pbmPref = document.getElementById("privacy.trackingprotection.pbmode.enabled");
--- a/browser/components/preferences/in-content/search.js
+++ b/browser/components/preferences/in-content/search.js
@@ -101,21 +101,26 @@ var gSearchPane = {
       if (e.name == currentEngine)
         list.selectedItem = item;
     });
   },
 
   handleEvent: function(aEvent) {
     switch (aEvent.type) {
       case "click":
-        if (aEvent.target.id != "engineChildren" && aEvent.target.id != "removeEngineButton") {
+        if (aEvent.target.id != "engineChildren" &&
+            !aEvent.target.classList.contains("searchEngineAction")) {
           let engineList = document.getElementById("engineList");
           // We don't want to toggle off selection while editing keyword
-          // so proceed only when the input field is hidden
-          if (engineList.inputField.hidden) {
+          // so proceed only when the input field is hidden.
+          // We need to check that engineList.view is defined here
+          // because the "click" event listener is on <window> and the
+          // view might have been destroyed if the pane has been navigated
+          // away from.
+          if (engineList.inputField.hidden && engineList.view) {
             let selection = engineList.view.selection;
             if (selection.count > 0) {
               selection.toggleSelect(selection.currentIndex);
             }
             engineList.blur();
           }
         }
         if (aEvent.target.id == "addEngines" && aEvent.button == 0) {
@@ -525,19 +530,18 @@ EngineView.prototype = {
   drop: function(dropIndex, orientation, dataTransfer) {
     var sourceIndex = this.getSourceIndexFromDrag(dataTransfer);
     var sourceEngine = this._engineStore.engines[sourceIndex];
 
     const nsITreeView = Components.interfaces.nsITreeView;
     if (dropIndex > sourceIndex) {
       if (orientation == nsITreeView.DROP_BEFORE)
         dropIndex--;
-    } else {
-      if (orientation == nsITreeView.DROP_AFTER)
-        dropIndex++;
+    } else if (orientation == nsITreeView.DROP_AFTER) {
+      dropIndex++;
     }
 
     this._engineStore.moveEngine(sourceEngine, dropIndex);
     gSearchPane.showRestoreDefaults(true);
     gSearchPane.buildDefaultEngineDropDown();
 
     // Redraw, and adjust selection
     this.invalidate();
--- a/browser/components/preferences/in-content/search.xul
+++ b/browser/components/preferences/in-content/search.xul
@@ -75,16 +75,17 @@
 
       <hbox>
         <button id="restoreDefaultSearchEngines"
                 label="&restoreDefaultSearchEngines.label;"
                 accesskey="&restoreDefaultSearchEngines.accesskey;"
                 />
         <spacer flex="1"/>
         <button id="removeEngineButton"
+                class="searchEngineAction"
                 label="&removeEngine.label;"
                 accesskey="&removeEngine.accesskey;"
                 disabled="true"
                 />
       </hbox>
 
       <separator class="thin"/>
 
--- a/browser/components/preferences/in-content/security.js
+++ b/browser/components/preferences/in-content/security.js
@@ -110,21 +110,20 @@ var gSecurityPane = {
   {
     var pref = document.getElementById("signon.rememberSignons");
     var excepts = document.getElementById("passwordExceptions");
 
     if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
       document.getElementById("savePasswords").disabled = true;
       excepts.disabled = true;
       return false;
-    } else {
-      excepts.disabled = !pref.value;
-      // don't override pref value in UI
-      return undefined;
     }
+    excepts.disabled = !pref.value;
+    // don't override pref value in UI
+    return undefined;
   },
 
   /**
    * Displays a dialog in which the user can view and modify the list of sites
    * where passwords are never saved.
    */
   showPasswordExceptions: function ()
   {
--- a/browser/components/preferences/in-content/sync.js
+++ b/browser/components/preferences/in-content/sync.js
@@ -359,16 +359,17 @@ var gSyncPane = {
 
         // If the account is verified the next promise in the chain will
         // fetch profile data.
         return data.verified;
       }).then(isVerified => {
         if (isVerified) {
           return fxAccounts.getSignedInUserProfile();
         }
+        return null;
       }).then(data => {
         let fxaLoginStatus = document.getElementById("fxaLoginStatus");
         if (data && profileInfoEnabled) {
           if (data.displayName) {
             fxaLoginStatus.setAttribute("hasName", true);
             displayNameLabel.hidden = false;
             displayNameLabel.textContent = data.displayName;
           } else {
@@ -417,17 +418,17 @@ var gSyncPane = {
       document.getElementById("syncComputerName").value = Weave.Service.clientsEngine.localName;
       document.getElementById("tosPP-normal").hidden = this._usingCustomServer;
     }
   },
 
   startOver: function (showDialog) {
     if (showDialog) {
       let flags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING +
-                  Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL + 
+                  Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL +
                   Services.prompt.BUTTON_POS_1_DEFAULT;
       let buttonChoice =
         Services.prompt.confirmEx(window,
                                   this._stringBundle.GetStringFromName("syncUnlink.title"),
                                   this._stringBundle.GetStringFromName("syncUnlink.label"),
                                   flags,
                                   this._stringBundle.GetStringFromName("syncUnlinkConfirm.label"),
                                   null, null, null, {});
@@ -543,23 +544,19 @@ var gSyncPane = {
   reSignIn: function() {
     this._openAboutAccounts("reauth");
   },
 
 
   clickOrSpaceOrEnterPressed: function(event) {
     // Note: charCode is deprecated, but 'char' not yet implemented.
     // Replace charCode with char when implemented, see Bug 680830
-    if ((event.type == "click" && event.button == 0) ||    // button 0 = 'main button', typically left click.
-        (event.type == "keypress" &&
-        (event.charCode == KeyEvent.DOM_VK_SPACE || event.keyCode == KeyEvent.DOM_VK_RETURN))) {
-      return true;
-    } else {
-      return false;
-    }
+    return ((event.type == "click" && event.button == 0) ||
+            (event.type == "keypress" &&
+             (event.charCode == KeyEvent.DOM_VK_SPACE || event.keyCode == KeyEvent.DOM_VK_RETURN)));
   },
 
   openChangeProfileImage: function(event) {
     if (this.clickOrSpaceOrEnterPressed(event)) {
       fxAccounts.promiseAccountsChangeProfileURI(this._getEntryPoint(), "avatar")
           .then(url => {
         this.openContentInBrowser(url, {
           replaceQueryString: true
@@ -646,21 +643,21 @@ var gSyncPane = {
     fxAccounts.signOut().then(() => {
       this.updateWeavePrefs();
     });
   },
 
   openAddDevice: function () {
     if (!Weave.Utils.ensureMPUnlocked())
       return;
-    
+
     let win = Services.wm.getMostRecentWindow("Sync:AddDevice");
     if (win)
       win.focus();
-    else 
+    else
       window.openDialog("chrome://browser/content/sync/addDevice.xul",
                         "syncAddDevice", "centerscreen,chrome,resizable=no");
   },
 
   resetSync: function () {
     this.openSetup("reset");
   },
 
--- a/browser/components/preferences/in-content/tests/browser_basic_rebuild_fonts_test.js
+++ b/browser/components/preferences/in-content/tests/browser_basic_rebuild_fonts_test.js
@@ -1,15 +1,15 @@
 Services.prefs.setBoolPref("browser.preferences.instantApply", true);
 
 registerCleanupFunction(function() {
   Services.prefs.clearUserPref("browser.preferences.instantApply");
 });
 
-add_task(function() {
+add_task(function*() {
   yield openPreferencesViaOpenPreferencesAPI("paneContent", null, {leaveOpen: true});
   let doc = gBrowser.contentDocument;
   var langGroup = Services.prefs.getComplexValue("font.language.group", Ci.nsIPrefLocalizedString).data
   is(doc.getElementById("font.language.group").value, langGroup,
      "Language group should be set correctly.");
 
   let defaultFontType = Services.prefs.getCharPref("font.default." + langGroup);
   let fontFamily = Services.prefs.getCharPref("font.name." + defaultFontType + "." + langGroup);
--- a/browser/components/preferences/in-content/tests/browser_bug1018066_resetScrollPosition.js
+++ b/browser/components/preferences/in-content/tests/browser_bug1018066_resetScrollPosition.js
@@ -1,24 +1,24 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-var originalWindowHeight;
-registerCleanupFunction(function() {
-  window.resizeTo(window.outerWidth, originalWindowHeight);
-  while (gBrowser.tabs[1])
-    gBrowser.removeTab(gBrowser.tabs[1]);
-});
-
-add_task(function() {
-  originalWindowHeight = window.outerHeight;
-  window.resizeTo(window.outerWidth, 300);
-  let prefs = yield openPreferencesViaOpenPreferencesAPI("paneApplications", undefined, {leaveOpen: true});
-  is(prefs.selectedPane, "paneApplications", "Applications pane was selected");
-  let mainContent = gBrowser.contentDocument.querySelector(".main-content");
-  mainContent.scrollTop = 50;
-  is(mainContent.scrollTop, 50, "main-content should be scrolled 50 pixels");
-
-  gBrowser.contentWindow.gotoPref("paneGeneral");
-  is(mainContent.scrollTop, 0,
-     "Switching to a different category should reset the scroll position");
-});
-
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var originalWindowHeight;
+registerCleanupFunction(function() {
+  window.resizeTo(window.outerWidth, originalWindowHeight);
+  while (gBrowser.tabs[1])
+    gBrowser.removeTab(gBrowser.tabs[1]);
+});
+
+add_task(function*() {
+  originalWindowHeight = window.outerHeight;
+  window.resizeTo(window.outerWidth, 300);
+  let prefs = yield openPreferencesViaOpenPreferencesAPI("paneApplications", undefined, {leaveOpen: true});
+  is(prefs.selectedPane, "paneApplications", "Applications pane was selected");
+  let mainContent = gBrowser.contentDocument.querySelector(".main-content");
+  mainContent.scrollTop = 50;
+  is(mainContent.scrollTop, 50, "main-content should be scrolled 50 pixels");
+
+  gBrowser.contentWindow.gotoPref("paneGeneral");
+  is(mainContent.scrollTop, 0,
+     "Switching to a different category should reset the scroll position");
+});
+
--- a/browser/components/preferences/in-content/tests/browser_bug1020245_openPreferences_to_paneContent.js
+++ b/browser/components/preferences/in-content/tests/browser_bug1020245_openPreferences_to_paneContent.js
@@ -2,17 +2,17 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 Services.prefs.setBoolPref("browser.preferences.instantApply", true);
 
 registerCleanupFunction(function() {
   Services.prefs.clearUserPref("browser.preferences.instantApply");
 });
 
-add_task(function() {
+add_task(function*() {
   let prefs = yield openPreferencesViaOpenPreferencesAPI("paneContent");
   is(prefs.selectedPane, "paneContent", "Content pane was selected");
   prefs = yield openPreferencesViaOpenPreferencesAPI("advanced", "updateTab");
   is(prefs.selectedPane, "paneAdvanced", "Advanced pane was selected");
   is(prefs.selectedAdvancedTab, "updateTab", "The update tab within the advanced prefs should be selected");
   prefs = yield openPreferencesViaHash("privacy");
   is(prefs.selectedPane, "panePrivacy", "Privacy pane is selected when hash is 'privacy'");
   prefs = yield openPreferencesViaOpenPreferencesAPI("nonexistant-category");
--- a/browser/components/preferences/in-content/tests/browser_bug705422.js
+++ b/browser/components/preferences/in-content/tests/browser_bug705422.js
@@ -55,23 +55,23 @@ function isDisabled(win, expectation) {
 }
 
 function runTest(win, searchTerm, cookies, matches) {
     var cm =  Components.classes["@mozilla.org/cookiemanager;1"]
                         .getService(Components.interfaces.nsICookieManager);
 
 
     // number of cookies should match injected cookies
-    var cnt = 0,
-        enumerator = cm.enumerator;
-    while (enumerator.hasMoreElements()) {
-        cnt++;
-        enumerator.getNext();
+    var injectedCookies = 0,
+        injectedEnumerator = cm.enumerator;
+    while (injectedEnumerator.hasMoreElements()) {
+        injectedCookies++;
+        injectedEnumerator.getNext();
     }
-    is(cnt, cookies, "Number of cookies match injected cookies");
+    is(injectedCookies, cookies, "Number of cookies match injected cookies");
 
     // "delete all cookies" should be enabled
     isDisabled(win, false);
 
     // filter cookies and count matches
     win.gCookiesWindow.setFilter(searchTerm);
     is(win.gCookiesWindow._view.rowCount, matches, "Correct number of cookies shown after filter is applied");
 
@@ -121,23 +121,23 @@ function runTest(win, searchTerm, cookie
     // "delete all cookies" should be enabled
     isDisabled(win, false);
 
     // delete all cookies and count should be 0
     EventUtils.synthesizeMouseAtCenter(deleteAllButton, {}, win);
     is(win.gCookiesWindow._view.rowCount, 0, "Deleted all cookies");
 
     // check that datastore is also at 0
-    var cnt = 0,
-        enumerator = cm.enumerator;
-    while (enumerator.hasMoreElements()) {
-        cnt++;
-        enumerator.getNext();
+    var remainingCookies = 0,
+        remainingEnumerator = cm.enumerator;
+    while (remainingEnumerator.hasMoreElements()) {
+        remainingCookies++;
+        remainingEnumerator.getNext();
     }
-    is(cnt, 0, "Zero cookies remain");
+    is(remainingCookies, 0, "Zero cookies remain");
 
     // "delete all cookies" should be disabled
     isDisabled(win, true);
 
     // clean up
     win.close();
     finish();
 }
--- a/browser/components/preferences/in-content/tests/browser_connection_bug388287.js
+++ b/browser/components/preferences/in-content/tests/browser_connection_bug388287.js
@@ -35,17 +35,17 @@ function test() {
       if (proxyType == "http") {
         continue;
       }
       Services.prefs.clearUserPref("network.proxy.backup." + proxyType);
       Services.prefs.clearUserPref("network.proxy.backup." + proxyType + "_port");
     }
     try {
       Services.ww.unregisterNotification(observer);
-    } catch(e) {
+    } catch (e) {
       // Do nothing, if the test was successful the above line should fail silently.
     }
   });
 
   // this observer is registered after the pref tab loads
   let observer = {
     observe: function(aSubject, aTopic, aData) {
       if (aTopic == "domwindowopened") {
--- a/browser/components/preferences/in-content/tests/browser_defaultbrowser_alwayscheck.js
+++ b/browser/components/preferences/in-content/tests/browser_defaultbrowser_alwayscheck.js
@@ -1,103 +1,103 @@
-"use strict";
-
-const CHECK_DEFAULT_INITIAL = Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser");
-
-add_task(function* clicking_make_default_checks_alwaysCheck_checkbox() {
-  yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:preferences");
-
-  yield test_with_mock_shellservice({isDefault: false}, function*() {
-    let setDefaultPane = content.document.getElementById("setDefaultPane");
-    Assert.equal(setDefaultPane.selectedIndex, "0",
-      "The 'make default' pane should be visible when not default");
-    let alwaysCheck = content.document.getElementById("alwaysCheckDefault");
-    Assert.ok(!alwaysCheck.checked, "Always Check is unchecked by default");
-    Assert.ok(!Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"),
-      "alwaysCheck pref should be false by default in test runs");
-
-    let setDefaultButton = content.document.getElementById("setDefaultButton");
-    setDefaultButton.click();
-    content.window.gMainPane.updateSetDefaultBrowser();
-
-    yield ContentTaskUtils.waitForCondition(() => alwaysCheck.checked,
-      "'Always Check' checkbox should get checked after clicking the 'Set Default' button");
-
-    Assert.ok(alwaysCheck.checked,
-      "Clicking 'Make Default' checks the 'Always Check' checkbox");
-    Assert.ok(Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"),
-      "Checking the checkbox should set the pref to true");
-    Assert.ok(alwaysCheck.disabled,
-      "'Always Check' checkbox is locked with default browser and alwaysCheck=true");
-    Assert.equal(setDefaultPane.selectedIndex, "1",
-      "The 'make default' pane should not be visible when default");
-    Assert.ok(Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"),
-      "checkDefaultBrowser pref is now enabled");
-  });
-
-  gBrowser.removeCurrentTab();
-  Services.prefs.clearUserPref("browser.shell.checkDefaultBrowser");
-});
-
-add_task(function* clicking_make_default_checks_alwaysCheck_checkbox() {
-  Services.prefs.lockPref("browser.shell.checkDefaultBrowser");
-  yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:preferences");
-
-  yield test_with_mock_shellservice({isDefault: false}, function*() {
-    let setDefaultPane = content.document.getElementById("setDefaultPane");
-    Assert.equal(setDefaultPane.selectedIndex, "0",
-      "The 'make default' pane should be visible when not default");
-    let alwaysCheck = content.document.getElementById("alwaysCheckDefault");
-    Assert.ok(alwaysCheck.disabled, "Always Check is disabled when locked");
-    Assert.ok(alwaysCheck.checked,
-      "Always Check is checked because defaultPref is true and pref is locked");
-    Assert.ok(Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"),
-      "alwaysCheck pref should ship with 'true' by default");
-
-    let setDefaultButton = content.document.getElementById("setDefaultButton");
-    setDefaultButton.click();
-    content.window.gMainPane.updateSetDefaultBrowser();
-
-    yield ContentTaskUtils.waitForCondition(() => setDefaultPane.selectedIndex == "1",
-      "Browser is now default");
-
-    Assert.ok(alwaysCheck.checked,
-      "'Always Check' is still checked because it's locked");
-    Assert.ok(alwaysCheck.disabled,
-      "'Always Check is disabled because it's locked");
-    Assert.ok(Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"),
-      "The pref is locked and so doesn't get changed");
-  });
-
-  Services.prefs.unlockPref("browser.shell.checkDefaultBrowser");
-  gBrowser.removeCurrentTab();
-});
-
-registerCleanupFunction(function() {
-  Services.prefs.unlockPref("browser.shell.checkDefaultBrowser");
-  Services.prefs.setBoolPref("browser.shell.checkDefaultBrowser", CHECK_DEFAULT_INITIAL);
-});
-
-function* test_with_mock_shellservice(options, testFn) {
-  yield ContentTask.spawn(gBrowser.selectedBrowser, options, function*(options) {
-    let doc = content.document;
-    let win = doc.defaultView;
-    win.oldShellService = win.getShellService();
-    let mockShellService = {
-      _isDefault: false,
-      isDefaultBrowser() {
-        return this._isDefault;
-      },
-      setDefaultBrowser() {
-        this._isDefault = true;
-      },
-    };
-    win.getShellService = function() {
-      return mockShellService;
-    }
-    mockShellService._isDefault = options.isDefault;
-    win.gMainPane.updateSetDefaultBrowser();
-  });
-
-  yield ContentTask.spawn(gBrowser.selectedBrowser, null, testFn);
-
-  Services.prefs.setBoolPref("browser.shell.checkDefaultBrowser", CHECK_DEFAULT_INITIAL);
-}
+"use strict";
+
+const CHECK_DEFAULT_INITIAL = Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser");
+
+add_task(function* clicking_make_default_checks_alwaysCheck_checkbox() {
+  yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:preferences");
+
+  yield test_with_mock_shellservice({isDefault: false}, function*() {
+    let setDefaultPane = content.document.getElementById("setDefaultPane");
+    Assert.equal(setDefaultPane.selectedIndex, "0",
+      "The 'make default' pane should be visible when not default");
+    let alwaysCheck = content.document.getElementById("alwaysCheckDefault");
+    Assert.ok(!alwaysCheck.checked, "Always Check is unchecked by default");
+    Assert.ok(!Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"),
+      "alwaysCheck pref should be false by default in test runs");
+
+    let setDefaultButton = content.document.getElementById("setDefaultButton");
+    setDefaultButton.click();
+    content.window.gMainPane.updateSetDefaultBrowser();
+
+    yield ContentTaskUtils.waitForCondition(() => alwaysCheck.checked,
+      "'Always Check' checkbox should get checked after clicking the 'Set Default' button");
+
+    Assert.ok(alwaysCheck.checked,
+      "Clicking 'Make Default' checks the 'Always Check' checkbox");
+    Assert.ok(Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"),
+      "Checking the checkbox should set the pref to true");
+    Assert.ok(alwaysCheck.disabled,
+      "'Always Check' checkbox is locked with default browser and alwaysCheck=true");
+    Assert.equal(setDefaultPane.selectedIndex, "1",
+      "The 'make default' pane should not be visible when default");
+    Assert.ok(Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"),
+      "checkDefaultBrowser pref is now enabled");
+  });
+
+  gBrowser.removeCurrentTab();
+  Services.prefs.clearUserPref("browser.shell.checkDefaultBrowser");
+});
+
+add_task(function* clicking_make_default_checks_alwaysCheck_checkbox() {
+  Services.prefs.lockPref("browser.shell.checkDefaultBrowser");
+  yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:preferences");
+
+  yield test_with_mock_shellservice({isDefault: false}, function*() {
+    let setDefaultPane = content.document.getElementById("setDefaultPane");
+    Assert.equal(setDefaultPane.selectedIndex, "0",
+      "The 'make default' pane should be visible when not default");
+    let alwaysCheck = content.document.getElementById("alwaysCheckDefault");
+    Assert.ok(alwaysCheck.disabled, "Always Check is disabled when locked");
+    Assert.ok(alwaysCheck.checked,
+      "Always Check is checked because defaultPref is true and pref is locked");
+    Assert.ok(Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"),
+      "alwaysCheck pref should ship with 'true' by default");
+
+    let setDefaultButton = content.document.getElementById("setDefaultButton");
+    setDefaultButton.click();
+    content.window.gMainPane.updateSetDefaultBrowser();
+
+    yield ContentTaskUtils.waitForCondition(() => setDefaultPane.selectedIndex == "1",
+      "Browser is now default");
+
+    Assert.ok(alwaysCheck.checked,
+      "'Always Check' is still checked because it's locked");
+    Assert.ok(alwaysCheck.disabled,
+      "'Always Check is disabled because it's locked");
+    Assert.ok(Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"),
+      "The pref is locked and so doesn't get changed");
+  });
+
+  Services.prefs.unlockPref("browser.shell.checkDefaultBrowser");
+  gBrowser.removeCurrentTab();
+});
+
+registerCleanupFunction(function() {
+  Services.prefs.unlockPref("browser.shell.checkDefaultBrowser");
+  Services.prefs.setBoolPref("browser.shell.checkDefaultBrowser", CHECK_DEFAULT_INITIAL);
+});
+
+function* test_with_mock_shellservice(options, testFn) {
+  yield ContentTask.spawn(gBrowser.selectedBrowser, options, function*(options) {
+    let doc = content.document;
+    let win = doc.defaultView;
+    win.oldShellService = win.getShellService();
+    let mockShellService = {
+      _isDefault: false,
+      isDefaultBrowser() {
+        return this._isDefault;
+      },
+      setDefaultBrowser() {
+        this._isDefault = true;
+      },
+    };
+    win.getShellService = function() {
+      return mockShellService;
+    }
+    mockShellService._isDefault = options.isDefault;
+    win.gMainPane.updateSetDefaultBrowser();
+  });
+
+  yield ContentTask.spawn(gBrowser.selectedBrowser, null, testFn);
+
+  Services.prefs.setBoolPref("browser.shell.checkDefaultBrowser", CHECK_DEFAULT_INITIAL);
+}
--- a/browser/components/preferences/in-content/tests/browser_homepages_filter_aboutpreferences.js
+++ b/browser/components/preferences/in-content/tests/browser_homepages_filter_aboutpreferences.js
@@ -1,20 +1,20 @@
-add_task(function() {
-  is(gBrowser.currentURI.spec, "about:blank", "Test starts with about:blank open");
-  yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home");
-  yield openPreferencesViaOpenPreferencesAPI("paneGeneral", null, {leaveOpen: true});
-  let doc = gBrowser.contentDocument;
-  is(gBrowser.currentURI.spec, "about:preferences#general",
-     "#general should be in the URI for about:preferences");
-  let oldHomepagePref = Services.prefs.getCharPref("browser.startup.homepage");
-
-  let useCurrent = doc.getElementById("useCurrent");
-  useCurrent.click();
-
-  is(gBrowser.tabs.length, 3, "Three tabs should be open");
-  is(Services.prefs.getCharPref("browser.startup.homepage"), "about:blank|about:home",
-     "about:blank and about:home should be the only homepages set");
-
-  Services.prefs.setCharPref("browser.startup.homepage", oldHomepagePref);
-  yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
-  yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
-});
+add_task(function*() {
+  is(gBrowser.currentURI.spec, "about:blank", "Test starts with about:blank open");
+  yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home");
+  yield openPreferencesViaOpenPreferencesAPI("paneGeneral", null, {leaveOpen: true});
+  let doc = gBrowser.contentDocument;
+  is(gBrowser.currentURI.spec, "about:preferences#general",
+     "#general should be in the URI for about:preferences");
+  let oldHomepagePref = Services.prefs.getCharPref("browser.startup.homepage");
+
+  let useCurrent = doc.getElementById("useCurrent");
+  useCurrent.click();
+
+  is(gBrowser.tabs.length, 3, "Three tabs should be open");
+  is(Services.prefs.getCharPref("browser.startup.homepage"), "about:blank|about:home",
+     "about:blank and about:home should be the only homepages set");
+
+  Services.prefs.setCharPref("browser.startup.homepage", oldHomepagePref);
+  yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+  yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
--- a/browser/components/preferences/in-content/tests/browser_notifications_do_not_disturb.js
+++ b/browser/components/preferences/in-content/tests/browser_notifications_do_not_disturb.js
@@ -1,44 +1,44 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-
-registerCleanupFunction(function() {
-  while (gBrowser.tabs[1])
-    gBrowser.removeTab(gBrowser.tabs[1]);
-});
-
-add_task(function() {
-  let prefs = yield openPreferencesViaOpenPreferencesAPI("paneContent", undefined, {leaveOpen: true});
-  is(prefs.selectedPane, "paneContent", "Content pane was selected");
-
-  let doc = gBrowser.contentDocument;
-  let notificationsDoNotDisturbRow = doc.getElementById("notificationsDoNotDisturbRow");
-  if (notificationsDoNotDisturbRow.hidden) {
-    todo(false, "Do not disturb is not available on this platform");
-    return;
-  }
-
-  let alertService;
-  try {
-    alertService = Cc["@mozilla.org/alerts-service;1"]
-                     .getService(Ci.nsIAlertsService)
-                     .QueryInterface(Ci.nsIAlertsDoNotDisturb);
-  } catch (ex) {
-    ok(true, "Do not disturb is not available on this platform: " + ex.message);
-    return;
-  }
-
-  let checkbox = doc.getElementById("notificationsDoNotDisturb");
-  ok(!checkbox.checked, "Checkbox should not be checked by default");
-  ok(!alertService.manualDoNotDisturb, "Do not disturb should be off by default");
-
-  let checkboxChanged = waitForEvent(checkbox, "command")
-  checkbox.click();
-  yield checkboxChanged;
-  ok(alertService.manualDoNotDisturb, "Do not disturb should be enabled when checked");
-
-  checkboxChanged = waitForEvent(checkbox, "command")
-  checkbox.click();
-  yield checkboxChanged;
-  ok(!alertService.manualDoNotDisturb, "Do not disturb should be disabled when unchecked");
-});
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+
+registerCleanupFunction(function() {
+  while (gBrowser.tabs[1])
+    gBrowser.removeTab(gBrowser.tabs[1]);
+});
+
+add_task(function*() {
+  let prefs = yield openPreferencesViaOpenPreferencesAPI("paneContent", undefined, {leaveOpen: true});
+  is(prefs.selectedPane, "paneContent", "Content pane was selected");
+
+  let doc = gBrowser.contentDocument;
+  let notificationsDoNotDisturbRow = doc.getElementById("notificationsDoNotDisturbRow");
+  if (notificationsDoNotDisturbRow.hidden) {
+    todo(false, "Do not disturb is not available on this platform");
+    return;
+  }
+
+  let alertService;
+  try {
+    alertService = Cc["@mozilla.org/alerts-service;1"]
+                     .getService(Ci.nsIAlertsService)
+                     .QueryInterface(Ci.nsIAlertsDoNotDisturb);
+  } catch (ex) {
+    ok(true, "Do not disturb is not available on this platform: " + ex.message);
+    return;
+  }
+
+  let checkbox = doc.getElementById("notificationsDoNotDisturb");
+  ok(!checkbox.checked, "Checkbox should not be checked by default");
+  ok(!alertService.manualDoNotDisturb, "Do not disturb should be off by default");
+
+  let checkboxChanged = waitForEvent(checkbox, "command")
+  checkbox.click();
+  yield checkboxChanged;
+  ok(alertService.manualDoNotDisturb, "Do not disturb should be enabled when checked");
+
+  checkboxChanged = waitForEvent(checkbox, "command")
+  checkbox.click();
+  yield checkboxChanged;
+  ok(!alertService.manualDoNotDisturb, "Do not disturb should be disabled when unchecked");
+});
--- a/browser/components/preferences/in-content/tests/browser_permissions_urlFieldHidden.js
+++ b/browser/components/preferences/in-content/tests/browser_permissions_urlFieldHidden.js
@@ -1,45 +1,45 @@
-"use strict";
-
-const PERMISSIONS_URL = "chrome://browser/content/preferences/permissions.xul";
-
-add_task(function* urlFieldVisibleForPopupPermissions(finish) {
-  yield openPreferencesViaOpenPreferencesAPI("paneContent", null, {leaveOpen: true});
-  let win = gBrowser.selectedBrowser.contentWindow;
-  let doc = win.document;
-  let popupPolicyCheckbox = doc.getElementById("popupPolicy");
-  ok(!popupPolicyCheckbox.checked, "popupPolicyCheckbox should be unchecked by default");
-  popupPolicyCheckbox.click();
-  let popupPolicyButton = doc.getElementById("popupPolicyButton");
-  ok(popupPolicyButton, "popupPolicyButton found");
-  let dialogPromise = promiseLoadSubDialog(PERMISSIONS_URL);
-  popupPolicyButton.click();
-  let dialog = yield dialogPromise;
-  ok(dialog, "dialog loaded");
-
-  let urlLabel = dialog.document.getElementById("urlLabel");
-  ok(!urlLabel.hidden, "urlLabel should be visible when one of block/session/allow visible");
-  let url = dialog.document.getElementById("url");
-  ok(!url.hidden, "url should be visible when one of block/session/allow visible");
-
-  popupPolicyCheckbox.click();
-  gBrowser.removeCurrentTab();
-});
-
-add_task(function* urlFieldHiddenForNotificationPermissions() {
-  yield openPreferencesViaOpenPreferencesAPI("paneContent", null, {leaveOpen: true});
-  let win = gBrowser.selectedBrowser.contentWindow;
-  let doc = win.document;
-  let notificationsPolicyButton = doc.getElementById("notificationsPolicyButton");
-  ok(notificationsPolicyButton, "notificationsPolicyButton found");
-  let dialogPromise = promiseLoadSubDialog(PERMISSIONS_URL);
-  notificationsPolicyButton.click();
-  let dialog = yield dialogPromise;
-  ok(dialog, "dialog loaded");
-
-  let urlLabel = dialog.document.getElementById("urlLabel");
-  ok(urlLabel.hidden, "urlLabel should be hidden as requested");
-  let url = dialog.document.getElementById("url");
-  ok(url.hidden, "url should be hidden as requested");
-
-  gBrowser.removeCurrentTab();
-});
+"use strict";
+
+const PERMISSIONS_URL = "chrome://browser/content/preferences/permissions.xul";
+
+add_task(function* urlFieldVisibleForPopupPermissions(finish) {
+  yield openPreferencesViaOpenPreferencesAPI("paneContent", null, {leaveOpen: true});
+  let win = gBrowser.selectedBrowser.contentWindow;
+  let doc = win.document;
+  let popupPolicyCheckbox = doc.getElementById("popupPolicy");
+  ok(!popupPolicyCheckbox.checked, "popupPolicyCheckbox should be unchecked by default");
+  popupPolicyCheckbox.click();
+  let popupPolicyButton = doc.getElementById("popupPolicyButton");
+  ok(popupPolicyButton, "popupPolicyButton found");
+  let dialogPromise = promiseLoadSubDialog(PERMISSIONS_URL);
+  popupPolicyButton.click();
+  let dialog = yield dialogPromise;
+  ok(dialog, "dialog loaded");
+
+  let urlLabel = dialog.document.getElementById("urlLabel");
+  ok(!urlLabel.hidden, "urlLabel should be visible when one of block/session/allow visible");
+  let url = dialog.document.getElementById("url");
+  ok(!url.hidden, "url should be visible when one of block/session/allow visible");
+
+  popupPolicyCheckbox.click();
+  gBrowser.removeCurrentTab();
+});
+
+add_task(function* urlFieldHiddenForNotificationPermissions() {
+  yield openPreferencesViaOpenPreferencesAPI("paneContent", null, {leaveOpen: true});
+  let win = gBrowser.selectedBrowser.contentWindow;
+  let doc = win.document;
+  let notificationsPolicyButton = doc.getElementById("notificationsPolicyButton");
+  ok(notificationsPolicyButton, "notificationsPolicyButton found");
+  let dialogPromise = promiseLoadSubDialog(PERMISSIONS_URL);
+  notificationsPolicyButton.click();
+  let dialog = yield dialogPromise;
+  ok(dialog, "dialog loaded");
+
+  let urlLabel = dialog.document.getElementById("urlLabel");
+  ok(urlLabel.hidden, "urlLabel should be hidden as requested");
+  let url = dialog.document.getElementById("url");
+  ok(url.hidden, "url should be hidden as requested");
+
+  gBrowser.removeCurrentTab();
+});
--- a/browser/components/preferences/in-content/tests/browser_sanitizeOnShutdown_prefLocked.js
+++ b/browser/components/preferences/in-content/tests/browser_sanitizeOnShutdown_prefLocked.js
@@ -1,37 +1,37 @@
-"use strict";
-
-function switchToCustomHistoryMode(doc) {
-  // Select the last item in the menulist.
-  let menulist = doc.getElementById("historyMode");
-  menulist.focus();
-  EventUtils.sendKey("UP");
-}
-
-function testPrefStateMatchesLockedState() {
-  let win = gBrowser.contentWindow;
-  let doc = win.document;
-  switchToCustomHistoryMode(doc);
-
-  let checkbox = doc.getElementById("alwaysClear");
-  let preference = doc.getElementById("privacy.sanitize.sanitizeOnShutdown");
-  is(checkbox.disabled, preference.locked, "Always Clear checkbox should be enabled when preference is not locked.");
-
-  gBrowser.removeCurrentTab();
-}
-
-add_task(function setup() {
-  registerCleanupFunction(function resetPreferences() {
-    Services.prefs.unlockPref("privacy.sanitize.sanitizeOnShutdown");
-  });
-});
-
-add_task(function test_preference_enabled_when_unlocked() {
-  yield openPreferencesViaOpenPreferencesAPI("panePrivacy", undefined, {leaveOpen: true});
-  testPrefStateMatchesLockedState();
-});
-
-add_task(function test_preference_disabled_when_locked() {
-  Services.prefs.lockPref("privacy.sanitize.sanitizeOnShutdown");
-  yield openPreferencesViaOpenPreferencesAPI("panePrivacy", undefined, {leaveOpen: true});
-  testPrefStateMatchesLockedState();
-});
+"use strict";
+
+function switchToCustomHistoryMode(doc) {
+  // Select the last item in the menulist.
+  let menulist = doc.getElementById("historyMode");
+  menulist.focus();
+  EventUtils.sendKey("UP");
+}
+
+function testPrefStateMatchesLockedState() {
+  let win = gBrowser.contentWindow;
+  let doc = win.document;
+  switchToCustomHistoryMode(doc);
+
+  let checkbox = doc.getElementById("alwaysClear");
+  let preference = doc.getElementById("privacy.sanitize.sanitizeOnShutdown");
+  is(checkbox.disabled, preference.locked, "Always Clear checkbox should be enabled when preference is not locked.");
+
+  gBrowser.removeCurrentTab();
+}
+
+add_task(function setup() {
+  registerCleanupFunction(function resetPreferences() {
+    Services.prefs.unlockPref("privacy.sanitize.sanitizeOnShutdown");
+  });
+});
+
+add_task(function* test_preference_enabled_when_unlocked() {
+  yield openPreferencesViaOpenPreferencesAPI("panePrivacy", undefined, {leaveOpen: true});
+  testPrefStateMatchesLockedState();
+});
+
+add_task(function* test_preference_disabled_when_locked() {
+  Services.prefs.lockPref("privacy.sanitize.sanitizeOnShutdown");
+  yield openPreferencesViaOpenPreferencesAPI("panePrivacy", undefined, {leaveOpen: true});
+  testPrefStateMatchesLockedState();
+});
--- a/browser/components/preferences/jar.mn
+++ b/browser/components/preferences/jar.mn
@@ -4,26 +4,26 @@
 
 browser.jar:
     content/browser/preferences/applicationManager.xul
     content/browser/preferences/applicationManager.js
     content/browser/preferences/blocklists.xul
     content/browser/preferences/blocklists.js
 *   content/browser/preferences/colors.xul
 *   content/browser/preferences/cookies.xul
-*   content/browser/preferences/cookies.js
+    content/browser/preferences/cookies.js
 *   content/browser/preferences/connection.xul
     content/browser/preferences/connection.js
     content/browser/preferences/donottrack.xul
 *   content/browser/preferences/fonts.xul
     content/browser/preferences/fonts.js
     content/browser/preferences/handlers.xml
     content/browser/preferences/handlers.css
 *   content/browser/preferences/languages.xul
     content/browser/preferences/languages.js
     content/browser/preferences/permissions.xul
-*   content/browser/preferences/permissions.js
+    content/browser/preferences/permissions.js
     content/browser/preferences/sanitize.xul
     content/browser/preferences/sanitize.js
     content/browser/preferences/selectBookmark.xul
     content/browser/preferences/selectBookmark.js
     content/browser/preferences/translation.xul
     content/browser/preferences/translation.js
--- a/browser/components/preferences/languages.js
+++ b/browser/components/preferences/languages.js
@@ -2,19 +2,19 @@
 /* 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/. */
 
 var gLanguagesDialog = {
 
   _availableLanguagesList : [],
   _acceptLanguages        : { },
-  
+
   _selectedItemID         : null,
-  
+
   init: function ()
   {
     if (!this._availableLanguagesList.length)
       this._loadAvailableLanguages();
   },
 
   // Ugly hack used to trigger extra reflow in order to work around XUL bug 1194844;
   // see bug 1194346.
@@ -23,22 +23,22 @@ var gLanguagesDialog = {
     this._activeLanguages.style.fontKerning = "none";
     setTimeout("gLanguagesDialog._activeLanguages.style.removeProperty('font-kerning')", 0);
   },
 
   get _activeLanguages()
   {
     return document.getElementById("activeLanguages");
   },
-  
+
   get _availableLanguages()
   {
     return document.getElementById("availableLanguages");
   },
-  
+
   _loadAvailableLanguages: function ()
   {
     // This is a parser for: resource://gre/res/language.properties
     // The file is formatted like so:
     // ab[-cd].accept=true|false
     //  ab = language
     //  cd = region
     var bundleAccepted    = document.getElementById("bundleAccepted");
@@ -54,245 +54,245 @@ var gLanguagesDialog = {
     }
 
     // 1) Read the available languages out of language.properties
     var strings = bundleAccepted.strings;
     while (strings.hasMoreElements()) {
       var currString = strings.getNext();
       if (!(currString instanceof Components.interfaces.nsIPropertyElement))
         break;
-      
+
       var property = currString.key.split("."); // ab[-cd].accept
       if (property[1] == "accept") {
         var abCD = property[0];
         var abCDPairs = abCD.split("-");      // ab[-cd]
         var useABCDFormat = abCDPairs.length > 1;
         var ab = useABCDFormat ? abCDPairs[0] : abCD;
         var cd = useABCDFormat ? abCDPairs[1] : "";
         if (ab) {
           var language = "";
           try {
             language = bundleLanguages.getString(ab);
-          } 
-          catch (e) { continue; };
-          
+          }
+          catch (e) { continue; }
+
           var region = "";
           if (useABCDFormat) {
             try {
               region = bundleRegions.getString(cd);
             }
             catch (e) { continue; }
           }
-          
+
           var name = "";
           if (useABCDFormat)
-            name = bundlePreferences.getFormattedString("languageRegionCodeFormat", 
+            name = bundlePreferences.getFormattedString("languageRegionCodeFormat",
                                                         [language, region, abCD]);
           else
-            name = bundlePreferences.getFormattedString("languageCodeFormat", 
+            name = bundlePreferences.getFormattedString("languageCodeFormat",
                                                         [language, abCD]);
-          
+
           if (name && abCD) {
-            var isVisible = currString.value == "true" && 
+            var isVisible = currString.value == "true" &&
                             (!(abCD in this._acceptLanguages) || !this._acceptLanguages[abCD]);
             var li = new LanguageInfo(name, abCD, isVisible);
             this._availableLanguagesList.push(li);
           }
         }
       }
     }
     this._buildAvailableLanguageList();
   },
-  
+
   _buildAvailableLanguageList: function ()
   {
     var availableLanguagesPopup = document.getElementById("availableLanguagesPopup");
     while (availableLanguagesPopup.hasChildNodes())
       availableLanguagesPopup.removeChild(availableLanguagesPopup.firstChild);
-      
+
     // Sort the list of languages by name
     this._availableLanguagesList.sort(function (a, b) {
                                         return a.name.localeCompare(b.name);
                                       });
-                                  
+
     // Load the UI with the data
     for (var i = 0; i < this._availableLanguagesList.length; ++i) {
       var abCD = this._availableLanguagesList[i].abcd;
-      if (this._availableLanguagesList[i].isVisible && 
+      if (this._availableLanguagesList[i].isVisible &&
           (!(abCD in this._acceptLanguages) || !this._acceptLanguages[abCD])) {
         var menuitem = document.createElement("menuitem");
         menuitem.id = this._availableLanguagesList[i].abcd;
         availableLanguagesPopup.appendChild(menuitem);
         menuitem.setAttribute("label", this._availableLanguagesList[i].name);
       }
     }
   },
-  
+
   readAcceptLanguages: function ()
   {
     while (this._activeLanguages.hasChildNodes())
       this._activeLanguages.removeChild(this._activeLanguages.firstChild);
-    
+
     var selectedIndex = 0;
     var preference = document.getElementById("intl.accept_languages");
-    if (preference.value == "") 
+    if (preference.value == "")
       return undefined;
     var languages = preference.value.toLowerCase().split(/\s*,\s*/);
     for (var i = 0; i < languages.length; ++i) {
       var name = this._getLanguageName(languages[i]);
       if (!name)
         name = "[" + languages[i] + "]";
       var listitem = document.createElement("listitem");
       listitem.id = languages[i];
       if (languages[i] == this._selectedItemID)
         selectedIndex = i;
       this._activeLanguages.appendChild(listitem);
       listitem.setAttribute("label", name);
 
       // Hash this language as an "Active" language so we don't
-      // show it in the list that can be added. 
+      // show it in the list that can be added.
       this._acceptLanguages[languages[i]] = true;
     }
 
     if (this._activeLanguages.childNodes.length > 0) {
       this._activeLanguages.ensureIndexIsVisible(selectedIndex);
       this._activeLanguages.selectedIndex = selectedIndex;
     }
 
     return undefined;
   },
-  
+
   writeAcceptLanguages: function ()
   {
     return undefined;
   },
-  
+
   onAvailableLanguageSelect: function ()
   {
     var addButton = document.getElementById("addButton");
     addButton.disabled = false;
-    
+
     this._availableLanguages.removeAttribute("accesskey");
   },
-  
+
   addLanguage: function ()
   {
     var selectedID = this._availableLanguages.selectedItem.id;
     var preference = document.getElementById("intl.accept_languages");
     var arrayOfPrefs = preference.value.toLowerCase().split(/\s*,\s*/);
-    for (var i = 0; i < arrayOfPrefs.length; ++i ){
+    for (var i = 0; i < arrayOfPrefs.length; ++i ) {
       if (arrayOfPrefs[i] == selectedID)
         return;
     }
 
     this._selectedItemID = selectedID;
 
-    if (preference.value == "") 
+    if (preference.value == "")
       preference.value = selectedID;
     else {
       arrayOfPrefs.unshift(selectedID);
       preference.value = arrayOfPrefs.join(",");
     }
-  
+
     this._acceptLanguages[selectedID] = true;
     this._availableLanguages.selectedItem = null;
-    
+
     // Rebuild the available list with the added item removed...
-    this._buildAvailableLanguageList(); 
-    
+    this._buildAvailableLanguageList();
+
     this._availableLanguages.setAttribute("label", this._availableLanguages.getAttribute("label2"));
   },
-  
+
   removeLanguage: function ()
   {
     // Build the new preference value string.
     var languagesArray = [];
     for (var i = 0; i < this._activeLanguages.childNodes.length; ++i) {
       var item = this._activeLanguages.childNodes[i];
-      if (!item.selected) 
+      if (!item.selected)
         languagesArray.push(item.id);
-      else  
+      else
         this._acceptLanguages[item.id] = false;
     }
     var string = languagesArray.join(",");
 
-    // Get the item to select after the remove operation completes.     
+    // Get the item to select after the remove operation completes.
     var selection = this._activeLanguages.selectedItems;
     var lastSelected = selection[selection.length-1];
     var selectItem = lastSelected.nextSibling || lastSelected.previousSibling;
     selectItem = selectItem ? selectItem.id : null;
-    
+
     this._selectedItemID = selectItem;
 
     // Update the preference and force a UI rebuild
     var preference = document.getElementById("intl.accept_languages");
     preference.value = string;
 
-    this._buildAvailableLanguageList(); 
+    this._buildAvailableLanguageList();
   },
-  
+
   _getLanguageName: function (aABCD)
   {
     if (!this._availableLanguagesList.length)
       this._loadAvailableLanguages();
     for (var i = 0; i < this._availableLanguagesList.length; ++i) {
-      if (aABCD == this._availableLanguagesList[i].abcd) 
+      if (aABCD == this._availableLanguagesList[i].abcd)
         return this._availableLanguagesList[i].name;
     }
     return "";
   },
-  
+
   moveUp: function ()
   {
     var selectedItem = this._activeLanguages.selectedItems[0];
     var previousItem = selectedItem.previousSibling;
-    
+
     var string = "";
     for (var i = 0; i < this._activeLanguages.childNodes.length; ++i) {
       var item = this._activeLanguages.childNodes[i];
       string += (i == 0 ? "" : ",");
-      if (item.id == previousItem.id) 
+      if (item.id == previousItem.id)
         string += selectedItem.id;
       else if (item.id == selectedItem.id)
         string += previousItem.id;
       else
         string += item.id;
     }
-    
+
     this._selectedItemID = selectedItem.id;
 
     // Update the preference and force a UI rebuild
     var preference = document.getElementById("intl.accept_languages");
     preference.value = string;
   },
-  
+
   moveDown: function ()
   {
     var selectedItem = this._activeLanguages.selectedItems[0];
     var nextItem = selectedItem.nextSibling;
-    
+
     var string = "";
     for (var i = 0; i < this._activeLanguages.childNodes.length; ++i) {
       var item = this._activeLanguages.childNodes[i];
       string += (i == 0 ? "" : ",");
-      if (item.id == nextItem.id) 
+      if (item.id == nextItem.id)
         string += selectedItem.id;
       else if (item.id == selectedItem.id)
         string += nextItem.id;
       else
         string += item.id;
     }
-    
+
     this._selectedItemID = selectedItem.id;
 
     // Update the preference and force a UI rebuild
     var preference = document.getElementById("intl.accept_languages");
     preference.value = string;
   },
-  
+
   onLanguageSelect: function ()
   {
     var upButton = document.getElementById("up");
     var downButton = document.getElementById("down");
     var removeButton = document.getElementById("remove");
     switch (this._activeLanguages.selectedCount) {
     case 0:
       upButton.disabled = downButton.disabled = removeButton.disabled = true;
--- a/browser/components/preferences/permissions.js
+++ b/browser/components/preferences/permissions.js
@@ -39,24 +39,24 @@ var gPermissionManager = {
       else if (aColumn.id == "statusCol")
         return gPermissionManager._permissions[aRow].capability;
       return "";
     },
 
     isSeparator: function(aIndex) { return false; },
     isSorted: function() { return false; },
     isContainer: function(aIndex) { return false; },
-    setTree: function(aTree){},
+    setTree: function(aTree) {},
     getImageSrc: function(aRow, aColumn) {},
     getProgressMode: function(aRow, aColumn) {},
     getCellValue: function(aRow, aColumn) {},
     cycleHeader: function(column) {},
-    getRowProperties: function(row){ return ""; },
-    getColumnProperties: function(column){ return ""; },
-    getCellProperties: function(row,column){
+    getRowProperties: function(row) { return ""; },
+    getColumnProperties: function(column) { return ""; },
+    getCellProperties: function(row,column) {
       if (column.element.getAttribute("id") == "siteCol")
         return "ltr";
 
       return "";
     }
   },
 
   _getCapabilityString: function (aCapability)
@@ -93,23 +93,23 @@ var gPermissionManager = {
       // permission manager for storage, so this won't prevent any valid
       // permissions from being entered by the user.
       let uri;
       try {
         uri = Services.io.newURI(input_url, null, null);
         principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
         // If we have ended up with an unknown scheme, the following will throw.
         principal.origin;
-      } catch(ex) {
+      } catch (ex) {
         uri = Services.io.newURI("http://" + input_url, null, null);
         principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
         // If we have ended up with an unknown scheme, the following will throw.
         principal.origin;
       }
-    } catch(ex) {
+    } catch (ex) {
       var message = this._bundle.getString("invalidURI");
       var title = this._bundle.getString("invalidURITitle");
       Services.prompt.alert(window, title, message);
       return;
     }
 
     var capabilityString = this._getCapabilityString(aCapability);
 
@@ -344,22 +344,22 @@ var gPermissionManager = {
       this._removePermission(p);
     }
     document.getElementById("removePermission").disabled = true;
     document.getElementById("removeAllPermissions").disabled = true;
   },
 
   onPermissionKeyPress: function (aEvent)
   {
-    if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE
-#ifdef XP_MACOSX
-        || aEvent.keyCode == KeyEvent.DOM_VK_BACK_SPACE
-#endif
-       )
+    if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE) {
       this.onPermissionDeleted();
+    } else if (AppConstants.platform == "macosx" &&
+               aEvent.keyCode == KeyEvent.DOM_VK_BACK_SPACE) {
+      this.onPermissionDeleted();
+    }
   },
 
   _lastPermissionSortColumn: "",
   _lastPermissionSortAscending: false,
   _permissionsComparator : function (a, b)
   {
     return a.toLowerCase().localeCompare(b.toLowerCase());
   },
--- a/browser/components/preferences/selectBookmark.js
+++ b/browser/components/preferences/selectBookmark.js
@@ -1,36 +1,36 @@
 //* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /**
  * SelectBookmarkDialog controls the user interface for the "Use Bookmark for
- * Home Page" dialog. 
- * 
+ * Home Page" dialog.
+ *
  * The caller (gMainPane.setHomePageToBookmark in main.js) invokes this dialog
  * with a single argument - a reference to an object with a .urls property and
  * a .names property.  This dialog is responsible for updating the contents of
  * the .urls property with an array of URLs to use as home pages and for
  * updating the .names property with an array of names for those URLs before it
  * closes.
- */ 
+ */
 var SelectBookmarkDialog = {
   init: function SBD_init() {
     document.getElementById("bookmarks").place =
       "place:queryType=1&folder=" + PlacesUIUtils.allBookmarksFolderId;
 
     // Initial update of the OK button.
     this.selectionChanged();
   },
 
-  /** 
-   * Update the disabled state of the OK button as the user changes the 
-   * selection within the view. 
+  /**
+   * Update the disabled state of the OK button as the user changes the
+   * selection within the view.
    */
   selectionChanged: function SBD_selectionChanged() {
     var accept = document.documentElement.getButton("accept");
     var bookmarks = document.getElementById("bookmarks");
     var disableAcceptButton = true;
     if (bookmarks.hasSelection) {
       if (!PlacesUtils.nodeIsSeparator(bookmarks.selectedNode))
         disableAcceptButton = false;
--- a/browser/components/search/content/search.xml
+++ b/browser/components/search/content/search.xml
@@ -1272,22 +1272,21 @@
 
       <!-- This handles events outside the one-off buttons, like on the popup
            and textbox. -->
       <method name="handleEvent">
         <parameter name="event"/>
         <body><![CDATA[
           switch (event.type) {
             case "input":
-              // The urlbar's value property can be a moz-action URI, but we
-              // want the value that the user sees, which is textValue.  So see
-              // if the textbox has a textValue property, and use it if so.
-              this.query = typeof(event.target.textValue) == "string" ?
-                           event.target.textValue :
-                           event.target.value;
+              // Allow the consumer's input to override its value property with
+              // a oneOffSearchQuery property.  That way if the value is not
+              // actually what the user typed (e.g., it's autofilled, or it's a
+              // mozaction URI), the consumer has some way of providing it.
+              this.query = event.target.oneOffSearchQuery || event.target.value;
               break;
             case "popupshowing":
               this._rebuild();
               break;
             case "popuphidden":
               Services.tm.mainThread.dispatch(() => {
                 this.selectedButton = null;
               }, Ci.nsIThread.DISPATCH_NORMAL);
@@ -1553,21 +1552,20 @@
           if (aUpdateLogicallySelectedButton) {
             this._selectedButton = val;
             if (val && !val.engine) {
               // If the button doesn't have an engine, then clear the popup's
               // selection to indicate that pressing Return while the button is
               // selected will do the button's command, not search.
               this.popup.selectedIndex = -1;
             }
+            let event = document.createEvent("Events");
+            event.initEvent("SelectedOneOffButtonChanged", true, false);
+            this.dispatchEvent(event);
           }
-
-          let event = document.createEvent("Events");
-          event.initEvent("OneOffsVisuallySelectedButtonChanged", true, false);
-          this.dispatchEvent(event);
         ]]></body>
       </method>
 
       <method name="getSelectableButtons">
         <parameter name="aIncludeNonEngineButtons"/>
         <body><![CDATA[
           let buttons = [];
           let oneOff = document.getAnonymousElementByAttribute(this, "anonid",
@@ -1746,17 +1744,18 @@
           let stopEvent = false;
 
           // Tab cycles through the one-offs and moves the focus out at the end.
           // But only if non-Shift modifiers aren't also pressed, to avoid
           // clobbering other shortcuts.
           if (event.keyCode == KeyEvent.DOM_VK_TAB &&
               !event.altKey &&
               !event.ctrlKey &&
-              !event.metaKey) {
+              !event.metaKey &&
+              this.getAttribute("disabletab") != "true") {
             stopEvent = this.advanceSelection(!event.shiftKey, false, true);
           }
 
           // Alt + up/down is very similar to (shift +) tab but differs in that
           // it loops through the list, whereas tab will move the focus out.
           else if (event.altKey &&
                    (event.keyCode == KeyEvent.DOM_VK_DOWN ||
                     event.keyCode == KeyEvent.DOM_VK_UP)) {
@@ -1906,17 +1905,17 @@
 
     <handlers>
 
       <handler event="mousedown"><![CDATA[
         // Required to receive click events from the buttons on Linux.
         event.preventDefault();
       ]]></handler>
 
-      <handler event="mouseover"><![CDATA[
+      <handler event="mousemove"><![CDATA[
         let target = event.originalTarget;
         if (target.localName != "button")
           return;
 
         // Ignore mouse events when the context menu is open.
          if (this._ignoreMouseEvents)
            return;
 
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -3211,18 +3211,26 @@ var SessionStoreInternal = {
 
     let restoreImmediately = options.restoreImmediately;
     let loadArguments = options.loadArguments;
     let browser = tab.linkedBrowser;
     let window = tab.ownerGlobal;
     let tabbrowser = window.gBrowser;
     let forceOnDemand = options.forceOnDemand;
 
-    this._maybeUpdateBrowserRemoteness(Object.assign({
-      browser, tabbrowser, tabData }, options));
+    let willRestoreImmediately = restoreImmediately ||
+                                 tabbrowser.selectedBrowser == browser ||
+                                 loadArguments;
+
+    if (!willRestoreImmediately && !forceOnDemand) {
+      TabRestoreQueue.add(tab);
+    }
+
+    this._maybeUpdateBrowserRemoteness({ tabbrowser, tab,
+                                         willRestoreImmediately });
 
     // Increase the busy state counter before modifying the tab.
     this._setWindowStateBusy(window);
 
     // It's important to set the window state to dirty so that
     // we collect their data for the first time when saving state.
     DirtyWindows.add(window);
 
@@ -3322,20 +3330,19 @@ var SessionStoreInternal = {
 
     // Restore tab attributes.
     if ("attributes" in tabData) {
       TabAttributes.set(tab, tabData.attributes);
     }
 
     // This could cause us to ignore MAX_CONCURRENT_TAB_RESTORES a bit, but
     // it ensures each window will have its selected tab loaded.
-    if (restoreImmediately || tabbrowser.selectedBrowser == browser || loadArguments) {
+    if (willRestoreImmediately) {
       this.restoreTabContent(tab, loadArguments);
     } else if (!forceOnDemand) {
-      TabRestoreQueue.add(tab);
       this.restoreNextTab();
     }
 
     // Decrease the busy state counter after we're done.
     this._setWindowStateReady(window);
   },
 
   /**
@@ -3607,55 +3614,57 @@ var SessionStoreInternal = {
     }
 
     SessionSaver.runDelayed();
   },
 
   /* ........ Auxiliary Functions .............. */
 
   /**
-   * Determines whether or not a browser for a tab that is
-   * being restored needs to have its remoteness flipped first.
+   * Determines whether or not a tab that is being restored needs
+   * to have its remoteness flipped first.
    *
    * @param (object) with the following properties:
    *
-   *        browser (<xul:browser>):
-   *          The browser for the tab being restored.
-   *
    *        tabbrowser (<xul:tabbrowser>):
    *          The tabbrowser that the browser belongs to.
    *
-   *        tabData (object):
-   *          The tabData state that we're attempting to
-   *          restore for the tab.
+   *        tab (<xul:tab>):
+   *          The tab being restored
    *
-   *        restoreImmediately (bool):
-   *          true if loading of content should occur immediately
-   *          after restoration.
+   *        willRestoreImmediately (bool):
+   *          true if the tab is going to have its content
+   *          restored immediately by the caller.
    *
-   *        forceOnDemand (bool):
-   *          true if the tab is being put into the restore-on-demand
-   *          background state.
    */
-  _maybeUpdateBrowserRemoteness({ browser, tabbrowser, tabData,
-                                  restoreImmediately, forceOnDemand }) {
+  _maybeUpdateBrowserRemoteness({ tabbrowser, tab,
+                                  willRestoreImmediately }) {
     // If the browser we're attempting to restore happens to be
     // remote, we need to flip it back to non-remote if it's going
     // to go into the pending background tab state. This is to make
-    // sure that the background tab can't crash if it hasn't yet
-    // been restored. Normally, when a window is restored, the tabs
-    // that SessionStore inserts are non-remote - but the initial
-    // browser is, by default, remote, so this check and flip is
-    // mostly for that case.
+    // sure that a background tab can't crash if it hasn't yet
+    // been restored.
     //
-    // Note that pinned tabs are exempt from this flip, since
-    // they'll be loaded immediately anyways.
-    if (browser.isRemoteBrowser &&
-        !tabData.pinned &&
-        (!restoreImmediately || forceOnDemand)) {
+    // Normally, when a window is restored, the tabs that SessionStore
+    // inserts are non-remote - but the initial browser is, by default,
+    // remote, so this check and flip covers this case. The other case
+    // is when window state is overwriting the state of an existing
+    // window with some remote tabs.
+    let browser = tab.linkedBrowser;
+
+    // There are two ways that a tab might start restoring its content
+    // very soon - either the caller is going to restore the content
+    // immediately, or the TabRestoreQueue is set up so that the tab
+    // content is going to be restored in the very near future. In
+    // either case, we don't want to flip remoteness, since the browser
+    // will soon be loading content.
+    let willRestore = willRestoreImmediately ||
+                      TabRestoreQueue.willRestoreSoon(tab);
+
+    if (browser.isRemoteBrowser && !willRestore) {
       tabbrowser.updateBrowserRemoteness(browser, false);
     }
   },
 
   /**
    * Update the session start time and send a telemetry measurement
    * for the number of days elapsed since the session was started.
    *
@@ -4422,17 +4431,46 @@ var TabRestoreQueue = {
   visibleToHidden: function (tab) {
     let {visible, hidden} = this.tabs;
     let index = visible.indexOf(tab);
 
     if (index > -1) {
       visible.splice(index, 1);
       hidden.push(tab);
     }
-  }
+  },
+
+  /**
+   * Returns true if the passed tab is in one of the sets that we're
+   * restoring content in automatically.
+   *
+   * @param tab (<xul:tab>)
+   *        The tab to check
+   * @returns bool
+   */
+  willRestoreSoon: function (tab) {
+    let { priority, hidden, visible } = this.tabs;
+    let { restoreOnDemand, restorePinnedTabsOnDemand,
+          restoreHiddenTabs } = this.prefs;
+    let restorePinned = !(restoreOnDemand && restorePinnedTabsOnDemand);
+    let candidateSet = [];
+
+    if (restorePinned && priority.length)
+      candidateSet.push(...priority);
+
+    if (!restoreOnDemand) {
+      if (visible.length)
+        candidateSet.push(...visible);
+
+      if (restoreHiddenTabs && hidden.length)
+        candidateSet.push(...hidden);
+    }
+
+    return candidateSet.indexOf(tab) > -1;
+  },
 };
 
 // A map storing a closed window's state data until it goes aways (is GC'ed).
 // This ensures that API clients can still read (but not write) states of
 // windows they still hold a reference to but we don't.
 var DyingWindowCache = {
   _data: new WeakMap(),
 
--- a/browser/components/sessionstore/test/browser.ini
+++ b/browser/components/sessionstore/test/browser.ini
@@ -224,8 +224,10 @@ run-if = e10s
 [browser_async_window_flushing.js]
 [browser_forget_async_closings.js]
 [browser_newtab_userTypedValue.js]
 [browser_parentProcessRestoreHash.js]
 run-if = e10s
 [browser_sessionStoreContainer.js]
 [browser_windowStateContainer.js]
 [browser_1234021.js]
+[browser_remoteness_flip_on_restore.js]
+run-if = e10s
--- a/browser/components/sessionstore/test/browser_423132.js
+++ b/browser/components/sessionstore/test/browser_423132.js
@@ -1,81 +1,59 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-function test() {
-  // test that cookies are stored and restored correctly by sessionstore,
-  // bug 423132.
+"use strict";
 
-  waitForExplicitFinish();
-
-  let cs = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager2);
-  cs.removeAll();
-
-  // make sure that sessionstore.js can be forced to be created by setting
-  // the interval pref to 0
-  gPrefService.setIntPref("browser.sessionstore.interval", 0);
-
+/**
+ * Tests that cookies are stored and restored correctly
+ * by sessionstore (bug 423132).
+ */
+add_task(function*() {
   const testURL = "http://mochi.test:8888/browser/" +
     "browser/components/sessionstore/test/browser_423132_sample.html";
 
-  // open a new window
-  let newWin = openDialog(location, "_blank", "chrome,all,dialog=no", "about:blank");
-
-  // make sure sessionstore saves the cookie data, then close the window
-  newWin.addEventListener("load", function (aEvent) {
-    newWin.removeEventListener("load", arguments.callee, false);
-
-    // Wait for sessionstore to be ready to restore this window
-    executeSoon(function() {
-      newWin.gBrowser.loadURI(testURL, null, null);
+  Services.cookies.removeAll();
+  // make sure that sessionstore.js can be forced to be created by setting
+  // the interval pref to 0
+  yield SpecialPowers.pushPrefEnv({
+    set: [["browser.sessionstore.interval", 0]]
+  });
 
-      promiseBrowserLoaded(newWin.gBrowser.selectedBrowser).then(() => {
-        let ready = () => {
-          // get the sessionstore state for the window
-          let state = ss.getWindowState(newWin);
+  let win = yield BrowserTestUtils.openNewBrowserWindow();
+  let browser = win.gBrowser.selectedBrowser;
+  browser.loadURI(testURL);
+  yield BrowserTestUtils.browserLoaded(browser);
 
-          // verify our cookie got set during pageload
-          let e = cs.enumerator;
-          let cookie;
-          let i = 0;
-          while (e.hasMoreElements()) {
-            cookie = e.getNext().QueryInterface(Ci.nsICookie);
-            i++;
-          }
-          is(i, 1, "expected one cookie");
+  yield TabStateFlusher.flush(browser);
 
-          // remove the cookie
-          cs.removeAll();
+  // get the sessionstore state for the window
+  let state = ss.getWindowState(win);
 
-          // restore the window state
-          ss.setWindowState(newWin, state, true);
+  // verify our cookie got set during pageload
+  let enumerator = Services.cookies.enumerator;
+  let cookie;
+  let i = 0;
+  while (enumerator.hasMoreElements()) {
+    cookie = enumerator.getNext().QueryInterface(Ci.nsICookie);
+    i++;
+  }
+  Assert.equal(i, 1, "expected one cookie");
 
-          // at this point, the cookie should be restored...
-          e = cs.enumerator;
-          let cookie2;
-          while (e.hasMoreElements()) {
-            cookie2 = e.getNext().QueryInterface(Ci.nsICookie);
-            if (cookie.name == cookie2.name)
-              break;
-          }
-          is(cookie.name, cookie2.name, "cookie name successfully restored");
-          is(cookie.value, cookie2.value, "cookie value successfully restored");
-          is(cookie.path, cookie2.path, "cookie path successfully restored");
+  // remove the cookie
+  Services.cookies.removeAll();
+
+  // restore the window state
+  ss.setWindowState(win, state, true);
 
-          // clean up
-          if (gPrefService.prefHasUserValue("browser.sessionstore.interval"))
-            gPrefService.clearUserPref("browser.sessionstore.interval");
-          cs.removeAll();
-          BrowserTestUtils.closeWindow(newWin).then(finish);
-        };
+  // at this point, the cookie should be restored...
+  enumerator = Services.cookies.enumerator;
+  let cookie2;
+  while (enumerator.hasMoreElements()) {
+    cookie2 = enumerator.getNext().QueryInterface(Ci.nsICookie);
+    if (cookie.name == cookie2.name)
+      break;
+  }
+  is(cookie.name, cookie2.name, "cookie name successfully restored");
+  is(cookie.value, cookie2.value, "cookie value successfully restored");
+  is(cookie.path, cookie2.path, "cookie path successfully restored");
 
-        function flushAndReady() {
-          TabStateFlusher.flush(newWin.gBrowser.selectedBrowser).then(ready);
-        }
-
-        flushAndReady();
-      }, true, testURL);
-    });
-  }, false);
-}
-
+  // clean up
+  Services.cookies.removeAll();
+  yield BrowserTestUtils.closeWindow(win);
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_remoteness_flip_on_restore.js
@@ -0,0 +1,324 @@
+"use strict";
+
+/**
+ * This set of tests checks that the remoteness is properly
+ * set for each browser in a window when that window has
+ * session state loaded into it.
+ */
+
+/**
+ * Takes a SessionStore window state object for a single
+ * window, sets the selected tab for it, and then returns
+ * the object to be passed to SessionStore.setWindowState.
+ *
+ * @param state (object)
+ *        The state to prepare to be sent to a window. This is
+ *        state should just be for a single window.
+ * @param selected (int)
+ *        The 1-based index of the selected tab. Note that
+ *        If this is 0, then the selected tab will not change
+ *        from what's already selected in the window that we're
+ *        sending state to.
+ * @returns (object)
+ *        The JSON encoded string to call
+ *        SessionStore.setWindowState with.
+ */
+function prepareState(state, selected) {
+  // We'll create a copy so that we don't accidentally
+  // modify the caller's selected property.
+  let copy = {};
+  Object.assign(copy, state);
+  copy.selected = selected;
+
+  return {
+    windows: [ copy ],
+  };
+}
+
+const SIMPLE_STATE = {
+  tabs: [
+    { entries: [{ url: "http://example.com/", title: "title" }] },
+    { entries: [{ url: "http://example.com/", title: "title" }] },
+    { entries: [{ url: "http://example.com/", title: "title" }] },
+  ],
+  title: "",
+  _closedTabs: [],
+};
+
+const PINNED_STATE = {
+  tabs: [
+    { entries: [{ url: "http://example.com/", title: "title" }], pinned: true },
+    { entries: [{ url: "http://example.com/", title: "title" }], pinned: true },
+    { entries: [{ url: "http://example.com/", title: "title" }] },
+  ],
+  title: "",
+  _closedTabs: [],
+};
+
+/**
+ * This is where most of the action is happening. This function takes
+ * an Array of "test scenario" Objects and runs them. For each scenario, a
+ * window is opened, put into some state, and then a new state is
+ * loaded into that window. We then check to make sure that the
+ * right things have happened in that window wrt remoteness flips.
+ *
+ * The schema for a testing scenario Object is as follows:
+ *
+ * initialRemoteness:
+ *   an Array that represents the starting window. Each bool
+ *   in the Array represents the window tabs in order. A "true"
+ *   indicates that that tab should be remote. "false" if the tab
+ *   should be non-remote.
+ *
+ * initialSelectedTab:
+ *   The 1-based index of the tab that we want to select for the
+ *   restored window. This is 1-based to avoid confusion with the
+ *   selectedTab property described down below, though you probably
+ *   want to set this to be greater than 0, since the initial window
+ *   needs to have a defined initial selected tab. Because of this,
+ *   the test will throw if initialSelectedTab is 0.
+ *
+ * stateToRestore:
+ *   A JS Object for the state to send down to the window.
+ *
+ * selectedTab:
+ *   The 1-based index of the tab that we want to select for the
+ *   restored window. Leave this at 0 if you don't want to change
+ *   the selection from the initial window state.
+ *
+ * expectedFlips:
+ *   an Array that represents the window that we end up with after
+ *   restoring state. Each bool in the Array represents the window tabs,
+ *   in order. A "true" indicates that the tab should have flipped
+ *   its remoteness once. "false" indicates that the tab should never
+ *   have flipped remoteness. Note that any tab that flips its remoteness
+ *   more than once will cause a test failure.
+ *
+ * expectedRemoteness:
+ *   an Array that represents the window that we end up with after
+ *   restoring state. Each bool in the Array represents the window
+ *   tabs in order. A "true" indicates that the tab be remote, and
+ *   a "false" indicates that the tab should be "non-remote". We
+ *   need this Array in order to test pinned tabs which will also
+ *   be loaded by default, and therefore should end up remote.
+ *
+ */
+function* runScenarios(scenarios) {
+  for (let scenario of scenarios) {
+    // Let's make sure our scenario is sane first.
+    Assert.equal(scenario.expectedFlips.length,
+                 scenario.expectedRemoteness.length,
+                 "All expected flips and remoteness needs to be supplied");
+    Assert.ok(scenario.initialSelectedTab > 0,
+              "You must define an initially selected tab");
+
+    // First, we need to create the initial conditions, so we
+    // open a new window to put into our starting state...
+    let win = yield BrowserTestUtils.openNewBrowserWindow();
+    let tabbrowser = win.gBrowser;
+    Assert.ok(tabbrowser.selectedBrowser.isRemoteBrowser,
+              "The initial browser should be remote.");
+    // Now put the window into the expected initial state.
+    for (let i = 0; i < scenario.initialRemoteness.length; ++i) {
+      let tab;
+      if (i > 0) {
+        // The window starts with one tab, so we need to create
+        // any of the additional ones required by this test.
+        info("Opening a new tab");
+        tab = yield BrowserTestUtils.openNewForegroundTab(tabbrowser)
+      } else {
+        info("Using the selected tab");
+        tab = tabbrowser.selectedTab;
+      }
+      let browser = tab.linkedBrowser;
+      let remotenessState = scenario.initialRemoteness[i];
+      tabbrowser.updateBrowserRemoteness(browser, remotenessState);
+    }
+
+    // And select the requested tab.
+    let tabToSelect = tabbrowser.tabs[scenario.initialSelectedTab - 1];
+    if (tabbrowser.selectedTab != tabToSelect) {
+      yield BrowserTestUtils.switchTab(tabbrowser, tabToSelect);
+    }
+
+    // Hook up an event listener to make sure that the right
+    // tabs flip remoteness, and only once.
+    let flipListener = {
+      seenTabs: new Set(),
+      handleEvent(e) {
+        let index = Array.from(tabbrowser.tabs).indexOf(e.target);
+        info(`Saw a tab at index ${index} flip remoteness`);
+        if (this.seenTabs.has(e.target)) {
+          Assert.ok(false, "Saw a tab flip remoteness more than once");
+        }
+        this.seenTabs.add(e.target);
+      },
+    };
+
+    win.addEventListener("TabRemotenessChange", flipListener);
+
+    // Okay, time to test!
+    let state = prepareState(scenario.stateToRestore,
+                             scenario.selectedTab);
+
+    SessionStore.setWindowState(win, state, true);
+
+    win.removeEventListener("TabRemotenessChange", flipListener);
+
+    // Because we know that scenario.expectedFlips and
+    // scenario.expectedRemoteness have the same length, we
+    // can check that we satisfied both with the same loop.
+    for (let i = 0; i < scenario.expectedFlips.length; ++i) {
+      let expectedToFlip = scenario.expectedFlips[i];
+      let expectedRemoteness = scenario.expectedRemoteness[i];
+      let tab = tabbrowser.tabs[i];
+      if (expectedToFlip) {
+        Assert.ok(flipListener.seenTabs.has(tab),
+                  `We should have seen tab at index ${i} flip remoteness`);
+      } else {
+        Assert.ok(!flipListener.seenTabs.has(tab),
+                  `We should not have seen tab at index ${i} flip remoteness`);
+      }
+
+      Assert.equal(tab.linkedBrowser.isRemoteBrowser, expectedRemoteness,
+                   "Should have gotten the expected remoteness " +
+                   `for the tab at index ${i}`);
+    }
+
+    yield BrowserTestUtils.closeWindow(win);
+  }
+}
+
+/**
+ * Tests that if we restore state to browser windows with
+ * a variety of initial remoteness states, that we only flip
+ * the remoteness on the necessary tabs. For this particular
+ * set of tests, we assume that tabs are restoring on demand.
+ */
+add_task(function*() {
+  // This test opens and closes windows, which might bog down
+  // a debug build long enough to time out the test, so we
+  // extend the tolerance on timeouts.
+  requestLongerTimeout(5);
+
+  yield SpecialPowers.pushPrefEnv({
+    "set": [["browser.sessionstore.restore_on_demand", true]],
+  });
+
+  const TEST_SCENARIOS = [
+    // Only one tab in the new window, and it's remote. This
+    // is the common case, since this is how restoration occurs
+    // when the restored window is being opened.
+    {
+      initialRemoteness: [true],
+      initialSelectedTab: 1,
+      stateToRestore: SIMPLE_STATE,
+      selectedTab: 3,
+      // The initial tab is remote and should go into
+      // the background state. The second and third tabs
+      // are new and should be initialized non-remote.
+      expectedFlips: [true, false, true],
+      // Only the selected tab should be remote.
+      expectedRemoteness: [false, false, true],
+    },
+
+    // A single remote tab, and this is the one that's going
+    // to be selected once state is restored.
+    {
+      initialRemoteness: [true],
+      initialSelectedTab: 1,
+      stateToRestore: SIMPLE_STATE,
+      selectedTab: 1,
+      // The initial tab is remote and selected, so it should
+      // not flip remoteness. The other two new tabs should
+      // be non-remote by default.
+      expectedFlips: [false, false, false],
+      // Only the selected tab should be remote.
+      expectedRemoteness: [true, false, false],
+    },
+
+    // A single remote tab which starts selected. We set the
+    // selectedTab to 0 which is equivalent to "don't change
+    // the tab selection in the window".
+    {
+      initialRemoteness: [true],
+      initialSelectedTab: 1,
+      stateToRestore: SIMPLE_STATE,
+      selectedTab: 0,
+      // The initial tab is remote and selected, so it should
+      // not flip remoteness. The other two new tabs should
+      // be non-remote by default.
+      expectedFlips: [false, false, false],
+      // Only the selected tab should be remote.
+      expectedRemoteness: [true, false, false],
+    },
+
+    // An initially remote tab, but we're going to load
+    // some pinned tabs now, and the pinned tabs should load
+    // right away.
+    {
+      initialRemoteness: [true],
+      initialSelectedTab: 1,
+      stateToRestore: PINNED_STATE,
+      selectedTab: 3,
+      // The initial tab is pinned and will load right away,
+      // so it should stay remote. The second tab is new
+      // and pinned, so it should start remote and not flip.
+      // The third tab is not pinned, but it is selected,
+      // so it will start non-remote, and then flip remoteness.
+      expectedFlips: [false, false, true],
+      // Both pinned tabs and the selected tabs should all
+      // end up being remote.
+      expectedRemoteness: [true, true, true],
+    },
+
+    // A single non-remote tab.
+    {
+      initialRemoteness: [false],
+      initialSelectedTab: 1,
+      stateToRestore: SIMPLE_STATE,
+      selectedTab: 2,
+      // The initial tab is non-remote and should stay
+      // that way. The second and third tabs are new and
+      // should be initialized non-remote.
+      expectedFlips: [false, true, false],
+      // Only the selected tab should be remote.
+      expectedRemoteness: [false, true, false],
+    },
+
+    // A mixture of remote and non-remote tabs.
+    {
+      initialRemoteness: [true, false, true],
+      initialSelectedTab: 1,
+      stateToRestore: SIMPLE_STATE,
+      selectedTab: 3,
+      // The initial tab is remote and should flip to non-remote
+      // as it is put into the background. The second tab should
+      // stay non-remote, and the third one should stay remote.
+      expectedFlips: [true, false, false],
+      // Only the selected tab should be remote.
+      expectedRemoteness: [false, false, true],
+    },
+
+    // An initially non-remote tab, but we're going to load
+    // some pinned tabs now, and the pinned tabs should load
+    // right away.
+    {
+      initialRemoteness: [false],
+      initialSelectedTab: 1,
+      stateToRestore: PINNED_STATE,
+      selectedTab: 3,
+      // The initial tab is pinned and will load right away,
+      // so it should flip remoteness. The second tab is new
+      // and pinned, so it should start remote and not flip.
+      // The third tab is not pinned, but it is selected,
+      // so it will start non-remote, and then flip remoteness.
+      expectedFlips: [true, false, true],
+      // Both pinned tabs and the selected tabs should all
+      // end up being remote.
+      expectedRemoteness: [true, true, true],
+    },
+  ];
+
+  yield* runScenarios(TEST_SCENARIOS);
+});
--- a/browser/config/tooltool-manifests/macosx64/cross-releng.manifest
+++ b/browser/config/tooltool-manifests/macosx64/cross-releng.manifest
@@ -50,24 +50,24 @@
 "size": 188880, 
 "visibility": "public", 
 "digest": "1ffddd43efb03aed897ee42035d9d8d758a8d66ab6c867599ef755e1a586768fc22011ce03698af61454920b00fe8bed08c9a681e7bd324d7f8f78c026c83943", 
 "algorithm": "sha512", 
 "unpack": true,
 "filename": "genisoimage.tar.xz"
 },
 {
-"size": 60778437,
-"visibility": "public",
-"digest": "2d5d300dd0d829012953ca0dd70b0abfda4e848ff03450326cf0c10eac2a71e37275b824597641f0f536b97b754bc81b10f9dbbb73ff725412858a7c2b9df6a7",
+"version": "gecko rustc 1.10.0 (cfcb716cf 2016-07-03)",
+"size": 102276708,
+"digest": "8cc9ea8347fc7e6e6fdb15a8fd1faae977f1235a426b879b3f9128ec91d8f2b6268297ce80bf4eceb47738bd40bfeda13f143dc3fe85f1434b13adfbc095ab90",
 "algorithm": "sha512",
-"filename": "rust-std-lib.tar.bz2",
+"filename": "rustc.tar.xz",
 "unpack": true
 },
 {
-"size": 36038146,
+"size": 53754194,
 "visibility": "public",
-"digest": "b967ba81cb08ce4c3a18e3f8411365ee775861df1ba7f60bcaccd03f940732ee9c4c622cd3163198abb36b2124cde550c61b5d153f1ae8a9107b382ddf660895",
+"digest": "d861a8c857e5cbc98318951b009c82cd88cbf151f1f5ad83d140e5e662b92c1ae1250c71c5d4cd29ebbcb4efa360a2ed4a6eb4db6281ecfb313038b5487eb1d2",
 "algorithm": "sha512",
-"filename": "rustc-linux64.tar.bz2",
+"filename": "rust-std-lib-x86_64-apple-darwin.tar.bz2",
 "unpack": true
 }
 ]
--- a/browser/extensions/pocket/content/panels/js/saved.js
+++ b/browser/extensions/pocket/content/panels/js/saved.js
@@ -262,20 +262,18 @@ var PKT_SAVED_OVERLAY = function (option
             if (key == 8) {
                 var selected = $('.token-input-selected-token');
                 if (selected.length) {
                     e.preventDefault();
                     e.stopImmediatePropagation();
                     inputwrapper.find('.pkt_ext_tag_input').tokenInput('remove',{name:selected.find('p').text()});
                 }
             }
-            else {
-                if ($(e.target).parent().hasClass('token-input-input-token')) {
-                    e.stopImmediatePropagation();
-                }
+            else if ($(e.target).parent().hasClass('token-input-input-token')) {
+                e.stopImmediatePropagation();
             }
         });
     };
     this.disableInput = function() {
         this.wrapper.find('.pkt_ext_item_actions').addClass('pkt_ext_item_actions_disabled');
         this.wrapper.find('.pkt_ext_btn').addClass('pkt_ext_btn_disabled');
         this.wrapper.find('.pkt_ext_tag_input_wrapper').addClass('pkt_ext_tag_input_wrapper_disabled');
         if (this.wrapper.find('.pkt_ext_suggestedtag_detail').length) {
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -226,16 +226,18 @@ These should match what Safari and other
 <!ENTITY urlbar.pointerLockNotificationAnchor.label     "Change whether the site can hide the pointer">
 <!ENTITY urlbar.servicesNotificationAnchor.label        "View the service install message">
 <!ENTITY urlbar.translateNotificationAnchor.label       "Translate this page">
 <!ENTITY urlbar.translatedNotificationAnchor.label      "Manage page translation">
 <!ENTITY urlbar.emeNotificationAnchor.label             "Manage use of DRM software">
 
 <!ENTITY urlbar.openHistoryPopup.tooltip                "Show history">
 
+<!ENTITY urlbar.zoomReset.tooltip     "Reset zoom level">
+
 <!ENTITY searchItem.title             "Search">
 
 <!-- Toolbar items --> 
 <!ENTITY homeButton.label             "Home">
 
 <!ENTITY bookmarksButton.label          "Bookmarks">
 <!ENTITY bookmarksCmd.commandkey "b">
 
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -265,16 +265,20 @@ menuUndoCloseWindowSingleTabLabel=#1
 
 # Unified Back-/Forward Popup
 tabHistory.current=Stay on this page
 tabHistory.goBack=Go back to this page
 tabHistory.goForward=Go forward to this page
 
 # URL Bar
 pasteAndGo.label=Paste & Go
+# LOCALIZATION NOTE(urlbar-zoom-button.label): %S is the current zoom level,
+# %% will be displayed as a single % character (% is commonly used to define
+# format specifiers, so it needs to be escaped).
+urlbar-zoom-button.label = %S%%
 
 # Block autorefresh
 refreshBlocked.goButton=Allow
 refreshBlocked.goButton.accesskey=A
 refreshBlocked.refreshLabel=%S prevented this page from automatically reloading.
 refreshBlocked.redirectLabel=%S prevented this page from automatically redirecting to another page.
 
 # General bookmarks button
--- a/browser/modules/PluginContent.jsm
+++ b/browser/modules/PluginContent.jsm
@@ -711,23 +711,21 @@ PluginContent.prototype = {
       if (pluginInfo.permissionString == pluginHost.getPermissionStringForType(plugin.actualType)) {
         let overlay = this.getPluginUI(plugin, "main");
         pluginFound = true;
         if (newState == "block") {
           if (overlay) {
             overlay.addEventListener("click", this, true);
           }
           plugin.reload(true);
-        } else {
-          if (this.canActivatePlugin(plugin)) {
-            if (overlay) {
-              overlay.removeEventListener("click", this, true);
-            }
-            plugin.playPlugin();
+        } else if (this.canActivatePlugin(plugin)) {
+          if (overlay) {
+            overlay.removeEventListener("click", this, true);
           }
+          plugin.playPlugin();
         }
       }
     }
 
     // If there are no instances of the plugin on the page any more, what the
     // user probably needs is for us to allow and then refresh.
     if (newState != "block" && !pluginFound) {
       this.reloadPage();
@@ -1021,27 +1019,25 @@ PluginContent.prototype = {
       doc.mozNoPluginCrashedNotification = true;
 
       // Notify others that the crash reporter UI is now ready.
       // Currently, this event is only used by tests.
       let winUtils = this.content.QueryInterface(Ci.nsIInterfaceRequestor)
                                  .getInterface(Ci.nsIDOMWindowUtils);
       let event = new this.content.CustomEvent("PluginCrashReporterDisplayed", {bubbles: true});
       winUtils.dispatchEventToChromeOnly(plugin, event);
-    } else {
+    } else if (!doc.mozNoPluginCrashedNotification) {
       // If another plugin on the page was large enough to show our UI, we don't
       // want to show a notification bar.
-      if (!doc.mozNoPluginCrashedNotification) {
-        this.global.sendAsyncMessage("PluginContent:ShowPluginCrashedNotification",
-                                     { messageString: message, pluginID: runID });
-        // Remove the notification when the page is reloaded.
-        doc.defaultView.top.addEventListener("unload", event => {
-          this.hideNotificationBar("plugin-crashed");
-        }, false);
-      }
+      this.global.sendAsyncMessage("PluginContent:ShowPluginCrashedNotification",
+                                   { messageString: message, pluginID: runID });
+      // Remove the notification when the page is reloaded.
+      doc.defaultView.top.addEventListener("unload", event => {
+        this.hideNotificationBar("plugin-crashed");
+      }, false);
     }
   },
 
   NPAPIPluginCrashReportSubmitted: function({ runID, state }) {
     this.pluginCrashData.delete(runID);
     let contentWindow = this.global.content;
     let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIDOMWindowUtils);
new file mode 100644
--- /dev/null
+++ b/browser/modules/URLBarZoom.jsm
@@ -0,0 +1,51 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [ "URLBarZoom" ];
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var URLBarZoom = {
+
+  init: function(aWindow) {
+    // Register ourselves with the service so we know when the zoom prefs change.
+    Services.obs.addObserver(updateZoomButton, "browser-fullZoom:zoomChange", false);
+    Services.obs.addObserver(updateZoomButton, "browser-fullZoom:zoomReset", false);
+    Services.obs.addObserver(updateZoomButton, "browser-fullZoom:location-change", false);
+  },
+}
+
+function updateZoomButton(aSubject, aTopic) {
+  let win = aSubject.ownerDocument.defaultView;
+  let customizableZoomControls = win.document.getElementById("zoom-controls");
+  let zoomResetButton = win.document.getElementById("urlbar-zoom-button");
+  let zoomFactor = Math.round(win.ZoomManager.zoom * 100);
+
+  // Ensure that zoom controls haven't already been added to browser in Customize Mode
+  if (customizableZoomControls &&
+      customizableZoomControls.getAttribute("cui-areatype") == "toolbar") {
+    zoomResetButton.hidden = true;
+    return;
+  }
+  if (zoomFactor != 100) {
+    // Check if zoom button is visible and update label if it is
+    if (zoomResetButton.hidden) {
+      zoomResetButton.hidden = false;
+    }
+    // Only allow pulse animation for zoom changes, not tab switching
+    if (aTopic != "browser-fullZoom:location-change") {
+      zoomResetButton.setAttribute("animate", "true");
+    } else {
+      zoomResetButton.removeAttribute("animate");
+    }
+    zoomResetButton.setAttribute("label",
+        win.gNavigatorBundle.getFormattedString("urlbar-zoom-button.label", [zoomFactor]));
+  // Hide button if zoom is at 100%
+  } else {
+      zoomResetButton.hidden = true;
+  }
+}
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -40,16 +40,17 @@ EXTRA_JS_MODULES += [
     'RecentWindow.jsm',
     'RemotePrompt.jsm',
     'Sanitizer.jsm',
     'SelfSupportBackend.jsm',
     'SitePermissions.jsm',
     'Social.jsm',
     'TabGroupsMigrator.jsm',
     'TransientPrefs.jsm',
+    'URLBarZoom.jsm',
     'webrtcUI.jsm',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
     EXTRA_JS_MODULES += [
         'Windows8WindowFrameColor.jsm',
         'WindowsJumpLists.jsm',
         'WindowsPreviewPerTab.jsm',
--- a/browser/modules/test/browser.ini
+++ b/browser/modules/test/browser.ini
@@ -19,8 +19,9 @@ support-files =
 [browser_NetworkPrioritizer.js]
 [browser_SelfSupportBackend.js]
 support-files =
   ../../components/uitour/test/uitour.html
   ../../components/uitour/UITour-lib.js
 [browser_taskbar_preview.js]
 skip-if = os != "win"
 [browser_UsageTelemetry.js]
+[browser_urlBar_zoom.js]
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/browser_urlBar_zoom.js
@@ -0,0 +1,73 @@
+/* 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";
+
+var initialPageZoom = ZoomManager.zoom;
+const kTimeoutInMS = 20000;
+
+add_task(function* () {
+  info("Confirm whether the browser zoom is set to the default level");
+  is(initialPageZoom, 1, "Page zoom is set to default (100%)");
+  let zoomResetButton = document.getElementById("urlbar-zoom-button");
+  is(zoomResetButton.hidden, true, "Zoom reset button is currently hidden");
+
+  info("Change zoom and confirm zoom button appears");
+  let labelUpdatePromise = BrowserTestUtils.waitForAttribute("label", zoomResetButton);
+  FullZoom.enlarge();
+  yield labelUpdatePromise;
+  info("Zoom increased to " + Math.floor(ZoomManager.zoom * 100) + "%");
+  is(zoomResetButton.hidden, false, "Zoom reset button is now visible");
+  let pageZoomLevel = Math.floor(ZoomManager.zoom * 100);
+  let expectedZoomLevel = 110;
+  let buttonZoomLevel = parseInt(zoomResetButton.getAttribute("label"), 10);
+  is(buttonZoomLevel, expectedZoomLevel, ("Button label updated successfully to " + Math.floor(ZoomManager.zoom * 100) + "%"));
+
+  let zoomResetPromise = promiseObserverNotification("browser-fullZoom:zoomReset");
+  zoomResetButton.click();
+  yield zoomResetPromise;
+  pageZoomLevel = Math.floor(ZoomManager.zoom * 100);
+  expectedZoomLevel = 100;
+  is(pageZoomLevel, expectedZoomLevel, "Clicking zoom button successfully resets browser zoom to 100%");
+  is(zoomResetButton.hidden, true, "Zoom reset button returns to being hidden");
+
+});
+
+add_task(function* () {
+  info("Confirm that URL bar zoom button doesn't appear when customizable zoom widget is added to toolbar");
+  CustomizableUI.addWidgetToArea("zoom-controls", CustomizableUI.AREA_NAVBAR);
+  let zoomCustomizableWidget = document.getElementById("zoom-reset-button");
+  let zoomResetButton = document.getElementById("urlbar-zoom-button");
+  let zoomChangePromise = promiseObserverNotification("browser-fullZoom:zoomChange");
+  FullZoom.enlarge();
+  yield zoomChangePromise;
+  is(zoomResetButton.hidden, true, "URL zoom button remains hidden despite zoom increase");
+  is(parseInt(zoomCustomizableWidget.label, 10), 110, "Customizable zoom widget's label has updated to " + zoomCustomizableWidget.label);
+});
+
+add_task(function* asyncCleanup() {
+  // reset zoom level and customizable widget
+  ZoomManager.zoom = initialPageZoom;
+  is(ZoomManager.zoom, 1, "Zoom level was restored");
+  if (document.getElementById("zoom-controls")) {
+    CustomizableUI.removeWidgetFromArea("zoom-controls", CustomizableUI.AREA_NAVBAR);
+    ok(!document.getElementById("zoom-controls"),"Customizable zoom widget removed from toolbar");
+  }
+
+});
+
+function promiseObserverNotification(aObserver) {
+  let deferred = Promise.defer();
+  function notificationCallback(e) {
+    Services.obs.removeObserver(notificationCallback, aObserver, false);
+    clearTimeout(timeoutId);
+    deferred.resolve();
+  }
+  let timeoutId = setTimeout(() => {
+    Services.obs.removeObserver(notificationCallback, aObserver, false);
+    deferred.reject("Notification '" + aObserver + "' did not happen within 20 seconds.");
+  }, kTimeoutInMS);
+  Services.obs.addObserver(notificationCallback, aObserver, false);
+  return deferred.promise;
+}
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -949,16 +949,53 @@ toolbaritem[cui-areatype="menu-panel"] >
 
 .urlbar-icon {
   padding: 0 3px;
   /* 16x16 icon with border-box sizing */
   width: 22px;
   height: 16px;
 }
 
+/* ::::: URL Bar Zoom Reset Button ::::: */
+@keyframes urlbar-zoom-reset-pulse {
+  0% {
+  transform: scale(0);
+  }
+  100% {
+  transform: scale(1.5);
+  }
+}
+
+#urlbar-zoom-button {
+  -moz-appearance: none;
+  margin: 0 3px;
+  font-size: .8em;
+  padding: 0 8px;
+  border-radius: 1em;
+  background-color: hsla(0,0%,0%,.05);
+  border: 1px solid ThreeDLightShadow;
+}
+
+#urlbar-zoom-button[animate="true"] {
+  animation-name: urlbar-zoom-reset-pulse;
+  animation-duration: 250ms;
+}
+
+#urlbar-zoom-button:hover:active {
+  background-color: hsla(0,0%,0%,.1);
+}
+
+#urlbar-zoom-button > .toolbarbutton-text {
+  display: -moz-box;
+}
+
+#urlbar-zoom-button > .toolbarbutton-icon {
+  display: none;
+}
+
 #urlbar-search-footer {
   border-top: 1px solid var(--panel-separator-color);
   background-color: hsla(210,4%,10%,.07);
 }
 
 #urlbar-search-settings {
   -moz-appearance: none;
   -moz-user-focus: ignore;
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -1657,16 +1657,51 @@ toolbar .toolbarbutton-1 > .toolbarbutto
 
 .urlbar-icon {
   padding: 0 3px;
   /* 16x16 icon with border-box sizing */
   width: 22px;
   height: 16px;
 }
 
+/* ::::: URL Bar Zoom Reset Button ::::: */
+@keyframes urlbar-zoom-reset-pulse {
+  0% {
+    transform: scale(0);
+  }
+  100% {
+    transform: scale(1.5);
+  }
+}
+
+#urlbar-zoom-button {
+  margin: 0 3px;
+  font-size: .8em;
+  padding: 0 8px;
+  border-radius: 1em;
+  background-color: hsla(0,0%,0%,.05);
+  border: 1px solid hsla(0,0%,0%,.1);
+}
+
+#urlbar-zoom-button[animate="true"] {
+  animation-name: urlbar-zoom-reset-pulse;
+  animation-duration: 250ms;
+}
+#urlbar-zoom-button:hover:active {
+  background-color: hsla(0,0%,0%,.1);
+}
+
+#urlbar-zoom-button > .toolbarbutton-text {
+  display: -moz-box;
+}
+
+#urlbar-zoom-button > .toolbarbutton-icon {
+  display: none;
+}
+
 #urlbar-search-footer {
   border-top: 1px solid var(--panel-separator-color);
   background-color: hsla(210,4%,10%,.07);
 }
 
 #urlbar-search-settings {
   -moz-appearance: none;
   -moz-user-focus: ignore;
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -1381,16 +1381,53 @@ html|*.urlbar-input:-moz-lwtheme::-moz-p
 
 .urlbar-icon {
   padding: 0 3px;
   /* 16x16 icon with border-box sizing */
   width: 22px;
   height: 16px;
 }
 
+/* ::::: URL Bar Zoom Reset Button ::::: */
+@keyframes urlbar-zoom-reset-pulse {
+  0% {
+  transform: scale(0);
+  }
+  100% {
+  transform: scale(1.5);
+  }
+}
+
+#urlbar-zoom-button {
+  -moz-appearance: none;
+  margin: 0 3px;
+  font-size: .8em;
+  padding: 0 8px;
+  border-radius: 1em;
+  background-color: hsla(0,0%,0%,.05);
+  border: 1px solid ThreeDLightShadow;
+}
+
+#urlbar-zoom-button[animate="true"] {
+  animation-name: urlbar-zoom-reset-pulse;
+  animation-duration: 250ms;
+}
+
+#urlbar-zoom-button:hover:active {
+  background-color: hsla(0,0%,0%,.1);
+}
+
+#urlbar-zoom-button > .toolbarbutton-text {
+  display: -moz-box;
+}
+
+#urlbar-zoom-button > .toolbarbutton-icon {
+  display: none;
+}
+
 .search-go-container {
   padding: 2px 2px;
 }
 
 #urlbar-search-footer {
   border-top: 1px solid var(--panel-separator-color);
   background-color: hsla(210,4%,10%,.07);
 }
--- a/devtools/.eslintrc
+++ b/devtools/.eslintrc
@@ -37,16 +37,20 @@
 
     // Rules from the mozilla plugin
     "mozilla/mark-test-function-used": 1,
     "mozilla/no-aArgs": 1,
     "mozilla/no-cpows-in-tests": 2,
     "mozilla/no-single-arg-cu-import": 2,
     // See bug 1224289.
     "mozilla/reject-importGlobalProperties": 2,
+    // devtools/shared/platform is special; see the README.md in that
+    // directory for details.  We reject requires using explicit
+    // subdirectories of this directory.
+    "mozilla/reject-some-requires": [2, "^devtools/shared/platform/(chome|content)/"],
     "mozilla/var-only-at-top-level": 1,
 
     // Rules from the React plugin
     "react/display-name": 2,
     "react/no-danger": 2,
     "react/no-did-mount-set-state": 2,
     "react/no-did-update-set-state": 2,
     "react/no-direct-mutation-state": 2,
--- a/devtools/client/framework/test/browser_toolbox_transport_events.js
+++ b/devtools/client/framework/test/browser_toolbox_transport_events.js
@@ -1,13 +1,15 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+"use strict";
+
 const { on, off } = require("sdk/event/core");
 const { DebuggerClient } = require("devtools/shared/client/main");
 
 function test() {
   gDevTools.on("toolbox-created", onToolboxCreated);
   on(DebuggerClient, "connect", onDebuggerClientConnect);
 
   addTab("about:blank").then(function () {
@@ -68,21 +70,21 @@ function onPacket1(eventId, packet) {
 }
 
 function onToolboxCreated(eventId, toolbox) {
   toolbox.target.makeRemote();
   let client = toolbox.target.client;
   let transport = client._transport;
 
   transport.on("send", send1);
-  transport.on("onPacket", onPacket1);
+  transport.on("packet", onPacket1);
 
   client.addOneTimeListener("closed", event => {
     transport.off("send", send1);
-    transport.off("onPacket", onPacket1);
+    transport.off("packet", onPacket1);
   });
 }
 
 // Listen to all debugger client object protocols.
 var sent2 = [];
 var received2 = [];
 
 function send2(eventId, packet) {
@@ -92,15 +94,15 @@ function send2(eventId, packet) {
 function onPacket2(eventId, packet) {
   received2.push(packet);
 }
 
 function onDebuggerClientConnect(client) {
   let transport = client._transport;
 
   transport.on("send", send2);
-  transport.on("onPacket", onPacket2);
+  transport.on("packet", onPacket2);
 
   client.addOneTimeListener("closed", event => {
     transport.off("send", send2);
-    transport.off("onPacket", onPacket2);
+    transport.off("packet", onPacket2);
   });
 }
--- a/devtools/client/inspector/.eslintrc
+++ b/devtools/client/inspector/.eslintrc
@@ -2,11 +2,11 @@
   // Extend from the devtools eslintrc.
   "extends": "../../.eslintrc",
 
   "rules": {
     // The inspector is being migrated to HTML and cleaned of
     // chrome-privileged code, so this rule disallows requiring chrome
     // code. Some files in the inspector disable this rule still. The
     // goal is to enable the rule globally on all files.
-    "mozilla/reject-some-requires": [2, "^(chrome|chrome:.*|resource:.*|devtools/server/.*|.*\\.jsm)$"],
+    "mozilla/reject-some-requires": [2, "^(chrome|chrome:.*|resource:.*|devtools/server/.*|.*\\.jsm|devtools/shared/platform/(chome|content)/.*)$"],
   },
 }
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -19,17 +19,16 @@ devtools.jar:
     content/netmonitor/netmonitor-view.js (netmonitor/netmonitor-view.js)
     content/webconsole/webconsole.xul (webconsole/webconsole.xul)
 *   content/scratchpad/scratchpad.xul (scratchpad/scratchpad.xul)
     content/scratchpad/scratchpad.js (scratchpad/scratchpad.js)
     content/shared/splitview.css (shared/splitview.css)
     content/shared/theme-switching.js (shared/theme-switching.js)
     content/shared/frame-script-utils.js (shared/frame-script-utils.js)
     content/styleeditor/styleeditor.xul (styleeditor/styleeditor.xul)
-    content/styleeditor/styleeditor.css (styleeditor/styleeditor.css)
     content/storage/storage.xul (storage/storage.xul)
     content/inspector/fonts/fonts.js (inspector/fonts/fonts.js)
     content/inspector/layout/layout.js (inspector/layout/layout.js)
     content/inspector/markup/markup.xhtml (inspector/markup/markup.xhtml)
     content/animationinspector/animation-controller.js (animationinspector/animation-controller.js)
     content/animationinspector/animation-panel.js (animationinspector/animation-panel.js)
     content/animationinspector/animation-inspector.xhtml (animationinspector/animation-inspector.xhtml)
     content/sourceeditor/codemirror/addon/comment/comment.js (sourceeditor/codemirror/addon/comment/comment.js)
--- a/devtools/client/netmonitor/netmonitor-view.js
+++ b/devtools/client/netmonitor/netmonitor-view.js
@@ -461,26 +461,27 @@ function RequestsMenuView() {
 
 RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
   /**
    * Initialization function, called when the network monitor is started.
    */
   initialize: function () {
     dumpn("Initializing the RequestsMenuView");
 
-    this.widget = new SideMenuWidget($("#requests-menu-contents"));
+    let widgetParentEl = $("#requests-menu-contents");
+    this.widget = new SideMenuWidget(widgetParentEl);
     this._splitter = $("#network-inspector-view-splitter");
     this._summary = $("#requests-menu-network-summary-button");
     this._summary.setAttribute("label", L10N.getStr("networkMenu.empty"));
     this.userInputTimer = Cc["@mozilla.org/timer;1"]
       .createInstance(Ci.nsITimer);
 
     // Create a tooltip for the newly appended network request item.
     this.tooltip = new HTMLTooltip(NetMonitorController._toolbox, { type: "arrow" });
-    this.tooltip.startTogglingOnHover(this.widget, this._onHover, {
+    this.tooltip.startTogglingOnHover(widgetParentEl, this._onHover, {
       toggleDelay: REQUESTS_TOOLTIP_TOGGLE_DELAY,
       interactive: true
     });
     $("#requests-menu-contents").addEventListener("scroll", this._onScroll, true);
 
     Prefs.filters.forEach(type => this.filterOn(type));
     this.sortContents(this._byTiming);
 
--- a/devtools/client/netmonitor/test/browser_net_image-tooltip.js
+++ b/devtools/client/netmonitor/test/browser_net_image-tooltip.js
@@ -33,16 +33,22 @@ add_task(function* test() {
   yield NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
   debuggee.performRequests();
   yield onEvents;
   yield onThumbnail;
 
   info("Checking the image thumbnail after a reload.");
   yield showTooltipAndVerify(RequestsMenu.tooltip, RequestsMenu.items[6]);
 
+  info("Checking if the image thumbnail is hidden when mouse leaves the menu widget");
+  let requestsMenuEl = $("#requests-menu-contents");
+  let onHidden = RequestsMenu.tooltip.once("hidden");
+  EventUtils.synthesizeMouse(requestsMenuEl, 0, 0, {type: "mouseout"}, monitor.panelWin);
+  yield onHidden;
+
   yield teardown(monitor);
   finish();
 
   /**
    * Show a tooltip on the {requestItem} and verify that it was displayed
    * with the expected content.
    */
   function* showTooltipAndVerify(tooltip, requestItem) {
--- a/devtools/client/responsive.html/browser/swap.js
+++ b/devtools/client/responsive.html/browser/swap.js
@@ -123,16 +123,21 @@ function swapToInnerBrowser({ tab, conta
       //    into this tab.
       gBrowser.updateBrowserRemoteness(tab.linkedBrowser, true);
 
       // 6. Swap the content into the original browser tab and close the
       //    temporary tab used to hold the content via
       //    `swapBrowsersAndCloseOther`.
       gBrowser.swapBrowsersAndCloseOther(tab, contentTab);
       gBrowser = null;
+
+      // The focus manager seems to get a little dizzy after all this swapping.  If a
+      // content element had been focused inside the viewport before stopping, it will
+      // have lost focus.  Activate the frame to restore expected focus.
+      tab.linkedBrowser.frameLoader.activateRemoteFrame();
     },
 
   };
 }
 
 /**
  * Browser navigation properties we'll freeze temporarily to avoid "blinking" in the
  * location bar, etc. caused by the containerURL peeking through before the swap is
--- a/devtools/client/shared/test/browser.ini
+++ b/devtools/client/shared/test/browser.ini
@@ -116,16 +116,17 @@ skip-if = e10s # Bug 1221911, bug 122228
 [browser_html_tooltip-01.js]
 [browser_html_tooltip-02.js]
 [browser_html_tooltip-03.js]
 [browser_html_tooltip-04.js]
 [browser_html_tooltip-05.js]
 [browser_html_tooltip_arrow-01.js]
 [browser_html_tooltip_arrow-02.js]
 [browser_html_tooltip_consecutive-show.js]
+[browser_html_tooltip_hover.js]
 [browser_html_tooltip_offset.js]
 [browser_html_tooltip_rtl.js]
 [browser_html_tooltip_variable-height.js]
 [browser_html_tooltip_width-auto.js]
 [browser_html_tooltip_xul-wrapper.js]
 [browser_inplace-editor-01.js]
 [browser_inplace-editor-02.js]
 [browser_inplace-editor_autocomplete_01.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/browser_html_tooltip_hover.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+/* import-globals-from helper_html_tooltip.js */
+
+"use strict";
+
+/**
+ * Test the TooltipToggle helper class for HTMLTooltip
+ */
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
+  <?xml-stylesheet href="chrome://global/skin/global.css"?>
+  <?xml-stylesheet href="chrome://devtools/skin/variables.css"?>
+  <?xml-stylesheet href="chrome://devtools/skin/tooltips.css"?>
+  <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+   class="theme-light" title="Tooltip hover test">
+    <vbox id="container" flex="1">
+      <hbox id="box1" flex="1"><label>test1</label></hbox>
+      <hbox id="box2" flex="1"><label>test2</label></hbox>
+      <hbox id="box3" flex="1"><label>test3</label></hbox>
+      <hbox id="box4" flex="1"><label>test4</label></hbox>
+    </vbox>
+  </window>`;
+
+const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
+loadHelperScript("helper_html_tooltip.js");
+
+add_task(function* () {
+  let [,, doc] = yield createHost("bottom", TEST_URI);
+
+  let width = 100, height = 50;
+  let tooltipContent = doc.createElementNS(HTML_NS, "div");
+  tooltipContent.textContent = "tooltip";
+  let tooltip = new HTMLTooltip({doc}, {useXulWrapper: false});
+  tooltip.setContent(tooltipContent, {width, height});
+
+  let container = doc.getElementById("container");
+  tooltip.startTogglingOnHover(container, () => true);
+
+  info("Hover on each of the 4 boxes, expect the tooltip to appear");
+  function* showAndCheck(boxId, position) {
+    info(`Show tooltip on ${boxId}`);
+    let box = doc.getElementById(boxId);
+    let shown = tooltip.once("shown");
+    EventUtils.synthesizeMouseAtCenter(box, { type: "mousemove" }, doc.defaultView);
+    yield shown;
+    checkTooltipGeometry(tooltip, box, {position, width, height});
+  }
+
+  yield showAndCheck("box1", "bottom");
+  yield showAndCheck("box2", "bottom");
+  yield showAndCheck("box3", "top");
+  yield showAndCheck("box4", "top");
+
+  info("Move out of the container");
+  let hidden = tooltip.once("hidden");
+  EventUtils.synthesizeMouseAtCenter(container, { type: "mouseout" }, doc.defaultView);
+  yield hidden;
+
+  info("Destroy the tooltip and finish");
+  tooltip.destroy();
+});
deleted file mode 100644
--- a/devtools/client/styleeditor/styleeditor.css
+++ /dev/null
@@ -1,133 +0,0 @@
-/* vim:set ts=2 sw=2 sts=2 et: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#style-editor-chrome {
-  -moz-box-flex: 1;
-}
-
-.stylesheet-error-message {
-  display: none;
-}
-
-li.error > .stylesheet-info > .stylesheet-more  > .stylesheet-error-message {
-  display: block;
-}
-
-.devtools-toolbar > spacer {
-  -moz-box-flex: 1;
-}
-
-.splitview-nav > li,
-.stylesheet-info,
-.stylesheet-more {
-  display: -moz-box;
-}
-
-.stylesheet-details-container {
-  -moz-box-flex: 1;
-}
-
-.stylesheet-media-container {
-  overflow-y: auto;
-}
-
-.media-rule-label {
-  display: flex;
-}
-
-.media-rule-condition {
-  flex: 1;
-  overflow: hidden;
-}
-
-.splitview-nav > li {
-  -moz-box-orient: horizontal;
-}
-
-.splitview-nav > li > hgroup {
-  display: -moz-box;
-  -moz-box-orient: vertical;
-  -moz-box-flex: 1;
-}
-
-.stylesheet-info > h1 {
-  -moz-box-flex: 1;
-}
-
-.stylesheet-name > label {
-  display: inline;
-  cursor: pointer;
-}
-
-.splitview-nav > li > hgroup.stylesheet-info {
-  -moz-box-pack: center;
-}
-
-.stylesheet-name {
-  white-space: nowrap;
-}
-
-li.unsaved > hgroup > h1 > .stylesheet-name:before {
-  content: "*";
-}
-
-li.linked-file-error .stylesheet-linked-file {
-  text-decoration: line-through;
-}
-
-li.linked-file-error .stylesheet-linked-file:after {
-  content: " ✘";
-}
-
-li.linked-file-error .stylesheet-rule-count {
-  visibility: hidden;
-}
-
-.stylesheet-linked-file:not(:empty):before {
-  content: " ↳ ";
-}
-
-.stylesheet-enabled {
-  display: -moz-box;
-  cursor: pointer;
-}
-
-.stylesheet-saveButton {
-  display: none;
-  margin-top: 0px;
-  margin-bottom: 0px;
-}
-
-.stylesheet-rule-count,
-li.splitview-active > hgroup > .stylesheet-more > h3 > .stylesheet-saveButton,
-li:hover > hgroup > .stylesheet-more > h3 > .stylesheet-saveButton {
-  display: -moz-box;
-}
-
-.stylesheet-more > spacer {
-  -moz-box-flex: 1;
-}
-
-/* portrait mode */
-@media (max-width: 550px) {
-  li.splitview-active > hgroup > .stylesheet-more > .stylesheet-rule-count,
-  li:hover > hgroup > .stylesheet-more > .stylesheet-rule-count {
-    display: none;
-  }
-
-  .stylesheet-more {
-    -moz-box-flex: 1;
-    -moz-box-pack: end;
-  }
-
-  .splitview-nav > li > hgroup.stylesheet-info {
-    -moz-box-orient: horizontal;
-    -moz-box-flex: 1;
-  }
-
-  .stylesheet-more > spacer {
-    -moz-box-flex: 0;
-  }
-}
--- a/devtools/client/styleeditor/styleeditor.xul
+++ b/devtools/client/styleeditor/styleeditor.xul
@@ -13,17 +13,16 @@
  %csscoverageDTD;
 ]>
 
 <?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/content/shared/splitview.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/widgets.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/splitview.css" type="text/css"?>
-<?xml-stylesheet href="chrome://devtools/content/styleeditor/styleeditor.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/styleeditor.css" type="text/css"?>
 <?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
 
 <xul:window xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         xmlns="http://www.w3.org/1999/xhtml"
         id="style-editor-chrome-window">
 
   <script type="application/javascript;version=1.8"
--- a/devtools/client/themes/styleeditor.css
+++ b/devtools/client/themes/styleeditor.css
@@ -1,28 +1,79 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#style-editor-chrome {
+  -moz-box-flex: 1;
+}
+
+.splitview-nav > li,
+.stylesheet-info,
+.stylesheet-more,
+.stylesheet-rule-count,
+li.splitview-active > hgroup > .stylesheet-more > h3 > .stylesheet-saveButton,
+li:hover > hgroup > .stylesheet-more > h3 > .stylesheet-saveButton {
+  display: -moz-box;
+}
+
+.devtools-toolbar > spacer {
+  -moz-box-flex: 1;
+}
+
 .style-editor-newButton {
   list-style-image: url(images/add.svg);
 }
 
 .style-editor-importButton {
   list-style-image: url(images/import.svg);
 }
 
+.stylesheet-details-container {
+  -moz-box-flex: 1;
+}
+
+.stylesheet-media-container {
+  overflow-y: auto;
+}
+
+.stylesheet-error-message {
+  display: none;
+}
+
+li.error > .stylesheet-info > .stylesheet-more  > .stylesheet-error-message {
+  display: block;
+}
+
 .stylesheet-title,
 .stylesheet-name {
   text-decoration: none;
 }
 
 .stylesheet-name {
   font-size: 13px;
+  white-space: nowrap;
+}
+
+.stylesheet-name > label {
+  display: inline;
+  cursor: pointer;
+}
+
+.stylesheet-info > h1 {
+  -moz-box-flex: 1;
+}
+
+.splitview-nav > li > hgroup.stylesheet-info {
+  -moz-box-pack: center;
+}
+
+.stylesheet-more > spacer {
+  -moz-box-flex: 1;
 }
 
 .theme-dark .stylesheet-title,
 .theme-dark .stylesheet-name {
   color: var(--theme-selection-color);
 }
 
 .theme-dark .stylesheet-rule-count,
@@ -38,32 +89,45 @@
 
 .theme-light .stylesheet-rule-count,
 .theme-light .stylesheet-linked-file,
 .theme-light .stylesheet-saveButton {
   color: var(--theme-body-color);
 }
 
 .stylesheet-saveButton {
+  display: none;
+  margin-top: 0px;
+  margin-bottom: 0px;
   text-decoration: underline;
   cursor: pointer;
 }
 
 .splitview-active .stylesheet-title,
 .splitview-active .stylesheet-name,
 .theme-light .splitview-active .stylesheet-rule-count,
 .theme-light .splitview-active .stylesheet-linked-file,
 .theme-light .splitview-active .stylesheet-saveButton {
   color: var(--theme-selection-color);
 }
 
 .splitview-nav:focus {
   outline: 0; /* focus ring is on the stylesheet name */
 }
 
+.splitview-nav > li {
+  -moz-box-orient: horizontal;
+}
+
+.splitview-nav > li > hgroup {
+  display: -moz-box;
+  -moz-box-orient: vertical;
+  -moz-box-flex: 1;
+}
+
 .splitview-nav > li.unsaved > hgroup .stylesheet-name {
   font-style: italic;
 }
 
 .splitview-nav:-moz-locale-dir(ltr) > li.unsaved > hgroup .stylesheet-name:before,
 .splitview-nav:-moz-locale-dir(rtl) > li.unsaved > hgroup .stylesheet-name:after {
   font-style: italic;
 }
@@ -82,16 +146,17 @@
   border-bottom-color: #cddae5; /* Grey */
 }
 
 .theme-dark .media-rule-label {
   border-bottom-color: #303b47; /* Grey */
 }
 
 .media-rule-label {
+  display: flex;
   padding: 4px;
   cursor: pointer;
   border-bottom: 1px solid;
 }
 
 .media-responsive-mode-toggle {
   color: var(--theme-highlight-blue);
   text-decoration: underline;
@@ -100,17 +165,24 @@
 .media-rule-line {
   padding-inline-start: 4px;
 }
 
 .media-condition-unmatched {
   opacity: 0.4;
 }
 
+.media-rule-condition {
+  flex: 1;
+  overflow: hidden;
+}
+
 .stylesheet-enabled {
+  display: -moz-box;
+  cursor: pointer;
   padding: 8px 0;
   margin: 0 8px;
   background-image: url(images/itemToggle.svg);
   background-repeat: no-repeat;
   background-clip: content-box;
   background-position: center;
   background-size: 16px;
   width: 24px;
@@ -133,20 +205,34 @@
 }
 
 .stylesheet-linked-file:not(:empty){
   margin-inline-end: 0.4em;
 }
 
 .stylesheet-linked-file:not(:empty):before {
   margin-inline-start: 0.4em;
+  content: " ↳ ";
+}
+
+li.unsaved > hgroup > h1 > .stylesheet-name:before {
+  content: "*";
+}
+
+li.linked-file-error .stylesheet-linked-file {
+  text-decoration: line-through;
 }
 
 li.linked-file-error .stylesheet-linked-file:after {
   font-size: 110%;
+  content: " ✘";
+}
+
+li.linked-file-error .stylesheet-rule-count {
+  visibility: hidden;
 }
 
 .stylesheet-more > h3 {
   font-size: 11px;
   margin-inline-end: 2px;
 }
 
 .devtools-searchinput,
@@ -171,16 +257,21 @@ h3 {
 @media (max-width: 700px) {
   .stylesheet-sidebar {
     width: 150px;
   }
 }
 
 /* portrait mode */
 @media (max-width: 550px) {
+  li.splitview-active > hgroup > .stylesheet-more > .stylesheet-rule-count,
+  li:hover > hgroup > .stylesheet-more > .stylesheet-rule-count {
+    display: none;
+  }
+
   .splitview-nav {
     box-shadow: none;
   }
 
   .splitview-nav > li.splitview-active {
     background-size: 0 0, 0 0, auto;
   }
 
@@ -191,21 +282,32 @@ h3 {
   }
 
   .disabled > .stylesheet-enabled {
     background-position: -24px 0;
   }
 
   .splitview-nav > li > hgroup.stylesheet-info {
     -moz-box-align: baseline;
+    -moz-box-orient: horizontal;
+    -moz-box-flex: 1;
   }
 
   .stylesheet-sidebar {
     width: 180px;
   }
+
+  .stylesheet-more {
+    -moz-box-flex: 1;
+    -moz-box-pack: end;
+  }
+
+  .stylesheet-more > spacer {
+    -moz-box-flex: 0;
+  }
 }
 
 /* CSS coverage */
 .csscoverage-report {
   background-color: var(--theme-toolbar-background);
   -moz-box-orient: horizontal;
 }
 
--- a/devtools/client/themes/webconsole.css
+++ b/devtools/client/themes/webconsole.css
@@ -48,22 +48,24 @@ a {
   align-self: flex-start;
 }
 
 .theme-firebug .message > .icon {
   margin: 0;
   margin-inline-end: 6px;
 }
 
-.theme-firebug .message[severity="error"] {
+.theme-firebug .message[severity="error"],
+.theme-firebug .message.error {
   color: var(--error-color);
   background-color: var(--error-background-color);
 }
 
-.theme-firebug .message[severity="warn"] {
+.theme-firebug .message[severity="warn"],
+.theme-firebug .message.warn {
   background-color: var(--warning-background-color);
 }
 
 .message > .icon::before {
   content: "";
   background-image: url(chrome://devtools/skin/images/webconsole.svg);
   background-position: 12px 12px;
   background-repeat: no-repeat;
@@ -205,21 +207,23 @@ a {
   background-image: linear-gradient(#444444, #000000);
   border-color: #777;
 }
 
 .message:hover {
   background-color: var(--theme-selection-background-semitransparent) !important;
 }
 
-.theme-light .message[severity=error] {
+.theme-light .message[severity=error],
+.theme-light .message.error {
   background-color: rgba(255, 150, 150, 0.3);
 }
 
-.theme-dark .message[severity=error] {
+.theme-dark .message[severity=error],
+.theme-dark .message.error {
   background-color: rgba(235, 83, 104, 0.17);
 }
 
 .console-string {
   color: var(--theme-highlight-lightorange);
 }
 
 .theme-selected .console-string,
@@ -228,54 +232,61 @@ a {
 .theme-selected .kind-ArrayLike {
   color: #f5f7fa !important; /* Selection Text Color */
 }
 
 .message[category=network] > .indent {
   border-inline-end: solid var(--theme-body-color-alt) 6px;
 }
 
-.message[category=network][severity=error] > .icon::before {
+.message[category=network][severity=error] > .icon::before,
+.message.network.error > .icon::before {
   background-position: -12px 0;
 }
 
-.message[category=network] > .message-body {
+.message[category=network] > .message-body,
+.message.network > .message-body {
   display: flex;
   flex-wrap: wrap;
 }
 
-.message[category=network] .method {
+.message[category=network] .method,
+.message.network .method {
   flex: none;
 }
 
-.message[category=network]:not(.navigation-marker) .url {
+.message[category=network]:not(.navigation-marker) .url,
+.message.network:not(.navigation-marker) .url {
   flex: 1 1 auto;
   /* Make sure the URL is very small initially, let flex change width as needed. */
   width: 100px;
   min-width: 5em;
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
 }
 
-.message[category=network] .status {
+.message[category=network] .status,
+.message.network .status {
   flex: none;
   margin-inline-start: 6px;
 }
 
-.message[category=network].mixed-content .url {
+.message[category=network].mixed-content .url,
+.message.network.mixed-content .url {
   color: var(--theme-highlight-red);
 }
 
 .message .learn-more-link {
   color: var(--theme-highlight-blue);
   margin: 0 6px;
 }
 
-.message[category=network] .xhr {
+.message[category=network] .xhr,
+.message.network .xhr {
   background-color: var(--theme-body-color-alt);
   color: var(--theme-body-background);
   border-radius: 3px;
   font-weight: bold;
   font-size: 10px;
   padding: 2px;
   line-height: 10px;
   margin-inline-start: 3px;
@@ -283,94 +294,113 @@ a {
 }
 
 /* CSS styles */
 .webconsole-filter-button[category="css"] > .toolbarbutton-menubutton-button:before {
   background-image: linear-gradient(#2DC3F3, #00B6F0);
   border-color: #1BA2CC;
 }
 
-.message[category=cssparser] > .indent {
+.message[category=cssparser] > .indent,
+.message.cssparser > .indent  {
   border-inline-end: solid #00b6f0 6px;
 }
 
-.message[category=cssparser][severity=error] > .icon::before {
+.message[category=cssparser][severity=error] > .icon::before,
+.message.cssparser.error > .icon::before {
   background-position: -12px -12px;
 }
 
-.message[category=cssparser][severity=warn] > .icon::before {
+.message[category=cssparser][severity=warn] > .icon::before,
+.message.cssparser.warn > .icon::before {
   background-position: -24px -12px;
 }
 
 /* JS styles */
 .webconsole-filter-button[category="js"] > .toolbarbutton-menubutton-button:before {
   background-image: linear-gradient(#FCB142, #FB9500);
   border-color: #E98A00;
 }
 
-.message[category=exception] > .indent {
+.message[category=exception] > .indent,
+.message.exception > .indent {
   border-inline-end: solid #fb9500 6px;
 }
 
-.message[category=exception][severity=error] > .icon::before {
+.message[category=exception][severity=error] > .icon::before,
+.message.exception.error > .icon::before {
   background-position: -12px -24px;
 }
 
-.message[category=exception][severity=warn] > .icon::before {
+.message[category=exception][severity=warn] > .icon::before,
+.message.exception.warn > .icon::before {
   background-position: -24px -24px;
 }
 
 /* Web Developer styles */
 .webconsole-filter-button[category="logging"] > .toolbarbutton-menubutton-button:before {
   background-image: linear-gradient(#B9B9B9, #AAAAAA);
   border-color: #929292;
 }
 
-.message[category=console] > .indent {
+.message[category=console] > .indent,
+.message.console > .indent {
   border-inline-end: solid #cbcbcb 6px;
 }
 
 .message[category=console][severity=error] > .icon::before,
 .message[category=output][severity=error] > .icon::before,
-.message[category=server][severity=error] > .icon::before {
+.message[category=server][severity=error] > .icon::before,
+.message.console.error > .icon::before,
+.message.output.error > .icon::before,
+.message.server.error > .icon::before {
   background-position: -12px -36px;
 }
 
 .message[category=console][severity=warn] > .icon::before,
-.message[category=server][severity=warn] > .icon::before {
+.message[category=server][severity=warn] > .icon::before,
+.message.console.warn > .icon::before,
+.message.server.warn > .icon::before {
   background-position: -24px -36px;
 }
 
 .message[category=console][severity=info] > .icon::before,
-.message[category=server][severity=info] > .icon::before {
+.message[category=server][severity=info] > .icon::before,
+.message.console.info > .icon::before,
+.message.server.info > .icon::before {
   background-position: -36px -36px;
 }
 
 /* Server Logging Styles */
 
 .webconsole-filter-button[category="server"] > .toolbarbutton-menubutton-button:before {
   background-image: linear-gradient(rgb(144, 176, 144), rgb(99, 151, 99));
   border-color: rgb(76, 143, 76);
 }
 
-.message[category=server] > .indent {
+.message[category=server] > .indent,
+.message.server > .indent {
   border-inline-end: solid #90B090 6px;
 }
 
 /* Input and output styles */
 .message[category=input] > .indent,
-.message[category=output] > .indent {
+.message[category=output] > .indent,
+.message.input > .indent,
+.message.output > .indent {
   border-inline-end: solid #808080 6px;
 }
 
-.message[category=input] > .icon::before {
+.message[category=input] > .icon::before,
+.message.input > .icon::before {
   background-position: -48px -36px;
 }
 
-.message[category=output] > .icon::before {
+.message[category=output] > .icon::before,
+.message.output > .icon::before {
   background-position: -60px -36px;
 }
 
 /* JSTerm Styles */
 .jsterm-input-container {
   background-color: var(--theme-tab-toolbar-background);
   border-top: 1px solid var(--theme-splitter-color);
 }
@@ -453,30 +483,33 @@ a {
 }
 
 .devtools-side-splitter ~ #webconsole-sidebar[hidden] {
   display: none;
 }
 
 /* Security styles */
 
-.message[category=security] > .indent {
+.message[category=security] > .indent,
+.message.security > .indent {
   border-inline-end: solid red 6px;
 }
 
 .webconsole-filter-button[category="security"] > .toolbarbutton-menubutton-button:before {
   background-image: linear-gradient(#FF3030, #FF7D7D);
   border-color: #D12C2C;
 }
 
-.message[category=security][severity=error] > .icon::before {
+.message[category=security][severity=error] > .icon::before,
+.message.security.error > .icon::before {
   background-position: -12px -48px;
 }
 
-.message[category=security][severity=warn] > .icon::before {
+.message[category=security][severity=warn] > .icon::before,
+.message.security.warn > .icon::before {
   background-position: -24px -48px;
 }
 
 .navigation-marker {
   color: #aaa;
   background: linear-gradient(#aaa, #aaa) no-repeat left 50%;
   background-size: 100% 2px;
   margin-top: 6px;
@@ -512,21 +545,23 @@ a {
    rows get out of vertical alignment when one cell has a lot of content. */
 .consoletable .table-widget-cell > span {
   overflow: hidden;
   display: flex;
   height: 1.25em;
   line-height: 1.25em;
 }
 
-.theme-light .message[severity=error] .stacktrace {
+.theme-light .message[severity=error] .stacktrace,
+.theme-light .message.error .stacktrace {
   background-color: rgba(255, 255, 255, 0.5);
 }
 
-.theme-dark .message[severity=error] .stacktrace {
+.theme-dark .message[severity=error] .stacktrace,
+.theme-dark .message.error .stacktrace {
   background-color: rgba(0, 0, 0, 0.5);
 }
 
 .message[open] .stacktrace {
   display: block;
 }
 
 .message .theme-twisty {
--- a/devtools/client/webconsole/new-console-output/components/message-types/console-api-call.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/console-api-call.js
@@ -19,32 +19,37 @@ const MessageIcon = createFactory(requir
 ConsoleApiCall.displayName = "ConsoleApiCall";
 
 ConsoleApiCall.propTypes = {
   message: PropTypes.object.isRequired,
 };
 
 function ConsoleApiCall(props) {
   const { message } = props;
+  const {category, severity} = message;
 
   const messageBody = message.parameters ?
     message.parameters.map((grip) => GripMessageBody({grip})) :
     message.messageText;
 
-  const icon = MessageIcon({severity: message.severity});
+  const icon = MessageIcon({severity: severity});
   const repeat = MessageRepeat({repeat: message.repeat});
 
-  // @TODO Use of "is" is a temporary hack to get the category and severity
-  // attributes to be applied. There are targeted in webconsole's CSS rules,
-  // so if we remove this hack, we have to modify the CSS rules accordingly.
+  const classes = ["message", "cm-s-mozilla"];
+
+  if (category) {
+    classes.push(category);
+  }
+
+  if (severity) {
+    classes.push(severity);
+  }
+
   return dom.div({
-    class: "message cm-s-mozilla",
-    is: "fdt-message",
-    category: message.category,
-    severity: message.severity
+    className: classes.join(" ")
   },
     // @TODO add timestamp
     // @TODO add indent if necessary
     icon,
     dom.span({className: "message-body-wrapper"},
       dom.span({},
         dom.span({className: "message-flex-body"},
           dom.span({className: "message-body devtools-monospace"},
--- a/devtools/client/webconsole/new-console-output/components/message-types/console-command.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/console-command.js
@@ -21,28 +21,33 @@ ConsoleCommand.propTypes = {
   message: PropTypes.instanceOf(ConsoleCommandType).isRequired,
 };
 
 /**
  * Displays input from the console.
  */
 function ConsoleCommand(props) {
   const { message } = props;
+  const {category, severity} = message;
 
-  const icon = MessageIcon({severity: message.severity});
+  const icon = MessageIcon({severity: severity});
+
+  const classes = ["message"];
 
-  // @TODO Use of "is" is a temporary hack to get the category and severity
-  // attributes to be applied. There are targeted in webconsole's CSS rules,
-  // so if we remove this hack, we have to modify the CSS rules accordingly.
+  if (category) {
+    classes.push(category);
+  }
+
+  if (severity) {
+    classes.push(severity);
+  }
+
   return dom.div({
-    class: "message",
+    className: classes.join(" "),
     ariaLive: "off",
-    is: "fdt-message",
-    category: message.category,
-    severity: message.severity
   },
     // @TODO add timestamp
     // @TODO add indent if necessary
     icon,
     dom.span({className: "message-body-wrapper message-body devtools-monospace"},
       dom.span({}, message.messageText)
     )
   );
--- a/devtools/client/webconsole/new-console-output/components/message-types/default-renderer.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/default-renderer.js
@@ -9,17 +9,14 @@
 // React & Redux
 const {
   DOM: dom,
 } = require("devtools/client/shared/vendor/react");
 
 DefaultRenderer.displayName = "DefaultRenderer";
 
 function DefaultRenderer(props) {
-  // @TODO Use of "is" is a temporary hack to get the category and severity
-  // attributes to be applied. There are targeted in webconsole's CSS rules,
-  // so if we remove this hack, we have to modify the CSS rules accordingly.
   return dom.div({},
     "This message type is not supported yet."
   );
 }
 
 module.exports.DefaultRenderer = DefaultRenderer;
--- a/devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js
@@ -18,26 +18,31 @@ const MessageIcon = createFactory(requir
 EvaluationResult.displayName = "EvaluationResult";
 
 EvaluationResult.propTypes = {
   message: PropTypes.object.isRequired,
 };
 
 function EvaluationResult(props) {
   const { message } = props;
-  const icon = MessageIcon({severity: message.severity});
+  const {category, severity} = message;
+  const icon = MessageIcon({severity: severity});
+
+  const classes = ["message", "cm-s-mozilla"];
 
-  // @TODO Use of "is" is a temporary hack to get the category and severity
-  // attributes to be applied. There are targeted in webconsole's CSS rules,
-  // so if we remove this hack, we have to modify the CSS rules accordingly.
+  if (category) {
+    classes.push(category);
+  }
+
+  if (severity) {
+    classes.push(severity);
+  }
+
   return dom.div({
-    class: "message cm-s-mozilla",
-    is: "fdt-message",
-    category: message.category,
-    severity: message.severity
+    className: classes.join(" ")
   },
     // @TODO add timestamp
     // @TODO add indent if needed with console.group
     icon,
     dom.span(
       {className: "message-body-wrapper message-body devtools-monospace"},
       dom.span({},
         GripMessageBody({grip: message.parameters})
--- a/devtools/client/webconsole/new-console-output/components/message-types/page-error.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/page-error.js
@@ -18,27 +18,33 @@ const MessageIcon = createFactory(requir
 PageError.displayName = "PageError";
 
 PageError.propTypes = {
   message: PropTypes.object.isRequired,
 };
 
 function PageError(props) {
   const { message } = props;
+  const {category, severity} = message;
+
   const repeat = MessageRepeat({repeat: message.repeat});
-  const icon = MessageIcon({severity: message.severity});
+  const icon = MessageIcon({severity: severity});
+
+  const classes = ["message"];
 
-  // @TODO Use of "is" is a temporary hack to get the category and severity
-  // attributes to be applied. There are targeted in webconsole's CSS rules,
-  // so if we remove this hack, we have to modify the CSS rules accordingly.
+  if (category) {
+    classes.push(category);
+  }
+
+  if (severity) {
+    classes.push(severity);
+  }
+
   return dom.div({
-    class: "message",
-    is: "fdt-message",
-    category: message.category,
-    severity: message.severity
+    className: classes.join(" ")
   },
     icon,
     dom.span(
       {className: "message-body-wrapper message-body devtools-monospace"},
       dom.span({},
         message.messageText
       )
     ),
--- a/devtools/shared/DevToolsUtils.js
+++ b/devtools/shared/DevToolsUtils.js
@@ -6,16 +6,17 @@
 
 /* General utilities used throughout devtools. */
 
 var { Ci, Cu, Cc, components } = require("chrome");
 var Services = require("Services");
 var promise = require("promise");
 var defer = require("devtools/shared/defer");
 var flags = require("./flags");
+var {getStack, callFunctionWithAsyncStack} = require("devtools/shared/platform/stack");
 
 loader.lazyRequireGetter(this, "FileUtils",
                          "resource://gre/modules/FileUtils.jsm", true);
 
 // Re-export the thread-safe utils.
 const ThreadSafeDevToolsUtils = require("./ThreadSafeDevToolsUtils.js");
 for (let key of Object.keys(ThreadSafeDevToolsUtils)) {
   exports[key] = ThreadSafeDevToolsUtils[key];
@@ -27,19 +28,19 @@ for (let key of Object.keys(ThreadSafeDe
 exports.executeSoon = function executeSoon(aFn) {
   if (isWorker) {
     setImmediate(aFn);
   } else {
     let executor;
     // Only enable async stack reporting when DEBUG_JS_MODULES is set
     // (customized local builds) to avoid a performance penalty.
     if (AppConstants.DEBUG_JS_MODULES || flags.testing) {
-      let stack = components.stack;
+      let stack = getStack();
       executor = () => {
-        Cu.callFunctionWithAsyncStack(aFn, stack, "DevToolsUtils.executeSoon");
+        callFunctionWithAsyncStack(aFn, stack, "DevToolsUtils.executeSoon");
       };
     } else {
       executor = aFn;
     }
     Services.tm.mainThread.dispatch({
       run: exports.makeInfallible(executor)
     }, Ci.nsIThread.DISPATCH_NORMAL);
   }
--- a/devtools/shared/Loader.jsm
+++ b/devtools/shared/Loader.jsm
@@ -28,16 +28,23 @@ var sharedGlobalBlocklist = ["sdk/indexe
  */
 function BuiltinProvider() {}
 BuiltinProvider.prototype = {
   load: function () {
     const paths = {
       // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
       "": "resource://gre/modules/commonjs/",
       // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
+      // Modules here are intended to have one implementation for
+      // chrome, and a separate implementation for content.  Here we
+      // map the directory to the chrome subdirectory, but the content
+      // loader will map to the content subdirectory.  See the
+      // README.md in devtools/shared/platform.
+      "devtools/shared/platform": "resource://devtools/shared/platform/chrome",
+      // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
       "devtools": "resource://devtools",
       // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
       "gcli": "resource://devtools/shared/gcli/source/lib/gcli",
       // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
       "acorn": "resource://devtools/acorn",
       // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
       "acorn/util/walk": "resource://devtools/acorn/walk.js",
       // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
--- a/devtools/shared/client/main.js
+++ b/devtools/shared/client/main.js
@@ -1,19 +1,20 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { Ci, Cu, components } = require("chrome");
+const { Ci, Cu } = require("chrome");
 const Services = require("Services");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const { getStack, callFunctionWithAsyncStack } = require("devtools/shared/platform/stack");
 
 const promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
 
 loader.lazyRequireGetter(this, "events", "sdk/event/core");
 loader.lazyRequireGetter(this, "WebConsoleClient", "devtools/shared/webconsole/client", true);
 loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
 loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
 
@@ -698,17 +699,17 @@ DebuggerClient.prototype = {
       if (aOnResponse) {
         aOnResponse(resp);
       }
       return promise.reject(resp);
     }
 
     let request = new Request(aRequest);
     request.format = "json";
-    request.stack = components.stack;
+    request.stack = getStack();
     if (aOnResponse) {
       request.on("json-reply", aOnResponse);
     }
 
     this._sendOrQueueRequest(request);
 
     // Implement a Promise like API on the returned object
     // that resolves/rejects on request response
@@ -1004,18 +1005,18 @@ DebuggerClient.prototype = {
     // that lack a packet type.
     if (aPacket.type) {
       this.emit(aPacket.type, aPacket);
     }
 
     if (activeRequest) {
       let emitReply = () => activeRequest.emit("json-reply", aPacket);
       if (activeRequest.stack) {
-        Cu.callFunctionWithAsyncStack(emitReply, activeRequest.stack,
-                                      "DevTools RDP");
+        callFunctionWithAsyncStack(emitReply, activeRequest.stack,
+                                   "DevTools RDP");
       } else {
         emitReply();
       }
     }
   },
 
   /**
    * Called by the DebuggerTransport to dispatch incoming bulk packets as
--- a/devtools/shared/event-emitter.js
+++ b/devtools/shared/event-emitter.js
@@ -40,38 +40,38 @@
     // but it doesn't depends on any real module. We can save a few cycles
     // and bytes by not loading Loader.jsm.
     let require = function (module) {
       switch (module) {
         case "devtools/shared/defer":
           return Cu.import("resource://gre/modules/Promise.jsm", {}).Promise.defer;
         case "Services":
           return Cu.import("resource://gre/modules/Services.jsm", {}).Services;
-        case "chrome":
-          return {
-            Cu,
-            components: Components
-          };
+        case "devtools/shared/platform/stack": {
+          let obj = {};
+          Cu.import("resource://devtools/shared/platform/chrome/stack.js", obj);
+          return obj;
+        }
       }
       return null;
     };
     factory.call(this, require, this, { exports: this }, console);
     this.EXPORTED_SYMBOLS = ["EventEmitter"];
   }
 }).call(this, function (require, exports, module, console) {
   // ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
   // After this point the code may not use Cu.import, and should only
   // require() modules that are "clean-for-content".
   let EventEmitter = this.EventEmitter = function () {};
   module.exports = EventEmitter;
 
   // See comment in JSM module boilerplate when adding a new dependency.
-  const { components } = require("chrome");
   const Services = require("Services");
   const defer = require("devtools/shared/defer");
+  const { describeNthCaller } = require("devtools/shared/platform/stack");
   let loggingEnabled = true;
 
   if (!isWorker) {
     loggingEnabled = Services.prefs.getBoolPref("devtools.dump.emit");
     Services.prefs.addObserver("devtools.dump.emit", {
       observe: () => {
         loggingEnabled = Services.prefs.getBoolPref("devtools.dump.emit");
       }
@@ -199,26 +199,17 @@
       }
     },
 
     logEvent(event, args) {
       if (!loggingEnabled) {
         return;
       }
 
-      let caller, func, path;
-      if (!isWorker) {
-        caller = components.stack.caller.caller;
-        func = caller.name;
-        let file = caller.filename;
-        if (file.includes(" -> ")) {
-          file = caller.filename.split(/ -> /)[1];
-        }
-        path = file + ":" + caller.lineNumber;
-      }
+      let description = describeNthCaller(2);
 
       let argOut = "(";
       if (args.length === 1) {
         argOut += event;
       }
 
       let out = "EMITTING: ";
 
@@ -246,14 +237,14 @@
           }
         }
       } catch (e) {
         // Object is dead so the toolbox is most likely shutting down,
         // do nothing.
       }
 
       argOut += ")";
-      out += "emit" + argOut + " from " + func + "() -> " + path + "\n";
+      out += "emit" + argOut + " from " + description + "\n";
 
       dump(out);
     },
   };
 });
--- a/devtools/shared/moz.build
+++ b/devtools/shared/moz.build
@@ -14,16 +14,17 @@ DIRS += [
     'fronts',
     'gcli',
     'heapsnapshot',
     'inspector',
     'jsbeautify',
     'layout',
     'locales',
     'performance',
+    'platform',
     'pretty-fast',
     'qrcode',
     'security',
     'sourcemap',
     'shims',
     'specs',
     'touch',
     'transport',
new file mode 100644
--- /dev/null
+++ b/devtools/shared/platform/README.md
@@ -0,0 +1,13 @@
+This directory is treated specially by the loaders.
+
+In particular, when running in chrome, a resource like
+"devtools/shared/platform/mumble" will be found in the chrome
+subdirectory; and when running in content, it will be found in the
+content subdirectory.
+
+Outside of tests, it's not ok to require a specific version of a file;
+and there is an eslint test to check for that.  That is,
+require("devtools/shared/platform/client/mumble") is an error.
+
+When adding a new file, you must add two copies, one to chrome and one
+to content.  Otherwise, one case or the other will fail to work.
new file mode 100644
--- /dev/null
+++ b/devtools/shared/platform/chrome/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; 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/.
+
+DevToolsModules(
+    'stack.js',
+)
new file mode 100644
--- /dev/null
+++ b/devtools/shared/platform/chrome/stack.js
@@ -0,0 +1,75 @@
+/* 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/. */
+
+// A few wrappers for stack-manipulation.  This version of the module
+// is used in chrome code.
+
+"use strict";
+
+(function (factory) {
+  // This file might be require()d, but might also be loaded via
+  // Cu.import.  Account for the differences here.
+  if (this.module && module.id.indexOf("stack") >= 0) {
+    // require.
+    const {components, Cu} = require("chrome");
+    factory.call(this, components, Cu, exports);
+  } else {
+    // Cu.import.
+    this.isWorker = false;
+    factory.call(this, Components, Components.utils, this);
+    this.EXPORTED_SYMBOLS = ["callFunctionWithAsyncStack", "describeNthCaller",
+                             "getStack"];
+  }
+}).call(this, function (components, Cu, exports) {
+  /**
+   * Return a description of the Nth caller, suitable for logging.
+   *
+   * @param {Number} n the caller to describe
+   * @return {String} a description of the nth caller.
+   */
+  function describeNthCaller(n) {
+    if (isWorker) {
+      return "";
+    }
+
+    let caller = components.stack;
+    // Do one extra iteration to skip this function.
+    while (n >= 0) {
+      --n;
+      caller = caller.caller;
+    }
+
+    let func = caller.name;
+    let file = caller.filename;
+    if (file.includes(" -> ")) {
+      file = caller.filename.split(/ -> /)[1];
+    }
+    let path = file + ":" + caller.lineNumber;
+
+    return func + "() -> " + path;
+  }
+
+  /**
+   * Return a stack object that can be serialized and, when
+   * deserialized, passed to callFunctionWithAsyncStack.
+   */
+  function getStack() {
+    return components.stack.caller;
+  }
+
+  /**
+   * Like Cu.callFunctionWithAsyncStack but handles the isWorker case
+   * -- |Cu| isn't defined in workers.
+   */
+  function callFunctionWithAsyncStack(callee, stack, id) {
+    if (isWorker) {
+      return callee();
+    }
+    return Cu.callFunctionWithAsyncStack(callee, stack, id);
+  }
+
+  exports.callFunctionWithAsyncStack = callFunctionWithAsyncStack;
+  exports.describeNthCaller = describeNthCaller;
+  exports.getStack = getStack;
+});
new file mode 100644
--- /dev/null
+++ b/devtools/shared/platform/content/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; 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/.
+
+DevToolsModules(
+    'stack.js',
+)
+
+XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell.ini']
new file mode 100644
--- /dev/null
+++ b/devtools/shared/platform/content/stack.js
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// A few wrappers for stack-manipulation.  This version of the module
+// is used in content code.  Note that this particular copy of the
+// file can only be loaded via require(), because Cu.import doesn't
+// exist in the content case.  So, we don't need the code to handle
+// both require and import here.
+
+"use strict";
+
+/**
+ * Looks like Cu.callFunctionWithAsyncStack, but just calls the callee.
+ */
+function callFunctionWithAsyncStack(callee, stack, id) {
+  return callee();
+}
+
+/**
+ * Return a description of the Nth caller, suitable for logging.
+ *
+ * @param {Number} n the caller to describe
+ * @return {String} a description of the nth caller.
+ */
+function describeNthCaller(n) {
+  if (isWorker) {
+    return "";
+  }
+
+  let stack = new Error().stack.split("\n");
+  // Add one here to skip this function.
+  return stack[n + 1];
+}
+
+/**
+ * Return a stack object that can be serialized and, when
+ * deserialized, passed to callFunctionWithAsyncStack.
+ */
+function getStack() {
+  // There's no reason for this to do anything fancy, since it's only
+  // used to pass back into callFunctionWithAsyncStack, which we can't
+  // implement.
+  return null;
+}
+
+exports.callFunctionWithAsyncStack = callFunctionWithAsyncStack;
+exports.describeNthCaller = describeNthCaller;
+exports.getStack = getStack;
new file mode 100644
--- /dev/null
+++ b/devtools/shared/platform/content/test/.eslintrc
@@ -0,0 +1,4 @@
+{
+  // Extend from the common devtools xpcshell eslintrc config.
+  "extends": "../../../../.eslintrc.xpcshell"
+}
new file mode 100644
--- /dev/null
+++ b/devtools/shared/platform/content/test/test_stack.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// There isn't really very much about the content stack.js that we can
+// test, but we'll do what we can.
+
+"use strict";
+
+var Cu = Components.utils;
+const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+
+// Make sure to explicitly require the content version of this module.
+// We have to use the ".." trick due to the way the loader remaps
+// devtools/shared/platform.
+const {
+  callFunctionWithAsyncStack,
+  getStack,
+  describeNthCaller
+} = require("devtools/shared/platform/../content/stack");
+
+function f3() {
+  return describeNthCaller(2);
+}
+
+function f2() {
+  return f3();
+}
+
+function f1() {
+  return f2();
+}
+
+function run_test() {
+  let value = 7;
+
+  const changeValue = () => {
+    value = 9;
+  };
+
+  callFunctionWithAsyncStack(changeValue, getStack(), "test_stack");
+  equal(value, 9, "callFunctionWithAsyncStack worked");
+
+  let stack = getStack();
+  equal(JSON.parse(JSON.stringify(stack)), stack, "stack is serializable");
+
+  let desc = f1();
+  ok(desc.includes("f1"), "stack description includes f1");
+}
new file mode 100644
--- /dev/null
+++ b/devtools/shared/platform/content/test/xpcshell.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+tags = devtools
+head =
+tail =
+firefox-appdir = browser
+
+[test_stack.js]
new file mode 100644
--- /dev/null
+++ b/devtools/shared/platform/moz.build
@@ -0,0 +1,10 @@
+# -*- Mode: python; 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/.
+
+DIRS += [
+    'chrome',
+    'content',
+]
--- a/devtools/shared/protocol.js
+++ b/devtools/shared/protocol.js
@@ -1,22 +1,21 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-var { Cu, components } = require("chrome");
-var Services = require("Services");
 var promise = require("promise");
 var defer = require("devtools/shared/defer");
 var {Class} = require("sdk/core/heritage");
 var {EventTarget} = require("sdk/event/target");
 var events = require("sdk/event/core");
 var object = require("sdk/util/object");
+var {getStack, callFunctionWithAsyncStack} = require("devtools/shared/platform/stack");
 
 exports.emit = events.emit;
 
 /**
  * Types: named marshallers/demarshallers.
  *
  * Types provide a 'write' function that takes a js representation and
  * returns a protocol representation, and a "read" function that
@@ -1201,17 +1200,17 @@ var Front = Class({
   request: function (packet) {
     let deferred = defer();
     // Save packet basics for debugging
     let { to, type } = packet;
     this._requests.push({
       deferred,
       to: to || this.actorID,
       type,
-      stack: components.stack,
+      stack: getStack(),
     });
     this.send(packet);
     return deferred.promise;
   },
 
   /**
    * Handler for incoming packets from the client's actor.
    */
@@ -1247,17 +1246,17 @@ var Front = Class({
     if (this._requests.length === 0) {
       let msg = "Unexpected packet " + this.actorID + ", " + JSON.stringify(packet);
       let err = Error(msg);
       console.error(err);
       throw err;
     }
 
     let { deferred, stack } = this._requests.shift();
-    Cu.callFunctionWithAsyncStack(() => {
+    callFunctionWithAsyncStack(() => {
       if (packet.error) {
         // "Protocol error" is here to avoid TBPL heuristics. See also
         // https://mxr.mozilla.org/webtools-central/source/tbpl/php/inc/GeneralErrorFilter.php
         let message;
         if (packet.error && packet.message) {
           message = "Protocol error (" + packet.error + "): " + packet.message;
         } else {
           message = packet.error;
--- a/devtools/shared/task.js
+++ b/devtools/shared/task.js
@@ -131,17 +131,17 @@ function isGenerator(value) {
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Task
 
 /**
  * This object provides the public module functions.
  */
-this.Task = {
+var Task = {
   /**
    * Creates and starts a new task.
    *
    * @param task
    *        - If you specify a generator function, it is called with no
    *          arguments to retrieve the associated iterator.  The generator
    *          function is a task, that is can yield promise objects to wait
    *          upon.
new file mode 100644
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/test_transport_events.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function run_test() {
+  initTestDebuggerServer();
+
+  add_task(function* () {
+    yield test_transport_events("socket", socket_transport);
+    yield test_transport_events("local", local_transport);
+    DebuggerServer.destroy();
+  });
+
+  run_next_test();
+}
+
+function* test_transport_events(name, transportFactory) {
+  do_print(`Started testing of transport: ${name}`);
+
+  do_check_eq(Object.keys(DebuggerServer._connections).length, 0);
+
+  let transport = yield transportFactory();
+
+  // Transport expects the hooks to be not null
+  transport.hooks = {
+    onPacket: () => {},
+    onClosed: () => {},
+  };
+
+  let rootReceived = transport.once("packet", (event, packet) => {
+    do_print(`Packet event: ${event} ${JSON.stringify(packet)}`);
+    do_check_eq(event, "packet");
+    do_check_eq(packet.from, "root");
+  });
+
+  transport.ready();
+  yield rootReceived;
+
+  let echoSent = transport.once("send", (event, packet) => {
+    do_print(`Send event: ${event} ${JSON.stringify(packet)}`);
+    do_check_eq(event, "send");
+    do_check_eq(packet.to, "root");
+    do_check_eq(packet.type, "echo");
+  });
+
+  let echoReceived = transport.once("packet", (event, packet) => {
+    do_print(`Packet event: ${event} ${JSON.stringify(packet)}`);
+    do_check_eq(event, "packet");
+    do_check_eq(packet.from, "root");
+    do_check_eq(packet.type, "echo");
+  });
+
+  transport.send({ to: "root", type: "echo" });
+  yield echoSent;
+  yield echoReceived;
+
+  let clientClosed = transport.once("close", (event) => {
+    do_print(`Close event: ${event}`);
+    do_check_eq(event, "close");
+  });
+
+  let serverClosed = DebuggerServer.once("connectionchange", (event, type) => {
+    do_print(`Server closed`);
+    do_check_eq(event, "connectionchange");
+    do_check_eq(type, "closed");
+  });
+
+  transport.close();
+
+  yield clientClosed;
+  yield serverClosed;
+
+  do_print(`Finished testing of transport: ${name}`);
+}
--- a/devtools/shared/transport/tests/unit/xpcshell.ini
+++ b/devtools/shared/transport/tests/unit/xpcshell.ini
@@ -15,8 +15,9 @@ support-files =
 skip-if = toolkit == "gonk"
 reason = bug 821285
 [test_dbgsocket_connection_drop.js]
 [test_delimited_read.js]
 [test_no_bulk.js]
 [test_packet.js]
 [test_queue.js]
 [test_transport_bulk.js]
+[test_transport_events.js]
--- a/devtools/shared/transport/transport.js
+++ b/devtools/shared/transport/transport.js
@@ -180,33 +180,33 @@
      *                     The stream to copy from.
      *             @return Promise
      *                     The promise is resolved when copying completes or
      *                     rejected if any (unexpected) errors occur.
      *                     This object also emits "progress" events for each chunk
      *                     that is copied.  See stream-utils.js.
      */
     startBulkSend: function (header) {
-      this.emit("startBulkSend", header);
+      this.emit("startbulksend", header);
 
       let packet = new BulkPacket(this);
       packet.header = header;
       this._outgoing.push(packet);
       this._flushOutgoing();
       return packet.streamReadyForWriting;
     },
 
     /**
      * Close the transport.
      * @param reason nsresult / object (optional)
      *        The status code or error message that corresponds to the reason for
      *        closing the transport (likely because a stream closed or failed).
      */
     close: function (reason) {
-      this.emit("onClosed", reason);
+      this.emit("close", reason);
 
       this.active = false;
       this._input.close();
       this._scriptableInput.close();
       this._output.close();
       this._destroyIncoming();
       this._destroyAllOutgoing();
       if (this.hooks) {
@@ -474,33 +474,33 @@
     /**
      * Handler triggered by an incoming JSONPacket completing it's |read| method.
      * Delivers the packet to this.hooks.onPacket.
      */
     _onJSONObjectReady: function (object) {
       DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
       // Ensure the transport is still alive by the time this runs.
         if (this.active) {
-          this.emit("onPacket", object);
+          this.emit("packet", object);
           this.hooks.onPacket(object);
         }
       }, "DebuggerTransport instance's this.hooks.onPacket"));
     },
 
     /**
      * Handler triggered by an incoming BulkPacket entering the |read| phase for
      * the stream portion of the packet.  Delivers info about the incoming
      * streaming data to this.hooks.onBulkPacket.  See the main comment on the
      * transport at the top of this file for more details.
      */
     _onBulkReadReady: function (...args) {
       DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
       // Ensure the transport is still alive by the time this runs.
         if (this.active) {
-          this.emit("onBulkPacket", ...args);
+          this.emit("bulkpacket", ...args);
           this.hooks.onBulkPacket(...args);
         }
       }, "DebuggerTransport instance's this.hooks.onBulkPacket"));
     },
 
     /**
      * Remove all handlers and references related to the current incoming packet,
      * either because it is now complete or because the transport is closing.
@@ -561,34 +561,34 @@
       let other = this.other;
       if (other) {
         DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
           // Avoid the cost of JSON.stringify() when logging is disabled.
           if (flags.wantLogging) {
             dumpn("Received packet " + serial + ": " + JSON.stringify(packet, null, 2));
           }
           if (other.hooks) {
-            other.emit("onPacket", packet);
+            other.emit("packet", packet);
             other.hooks.onPacket(packet);
           }
         }, "LocalDebuggerTransport instance's this.other.hooks.onPacket"));
       }
     },
 
     /**
      * Send a streaming bulk packet directly to the onBulkPacket handler of the
      * other endpoint.
      *
      * This case is much simpler than the full DebuggerTransport, since there is
      * no primary stream we have to worry about managing while we hand it off to
      * others temporarily.  Instead, we can just make a single use pipe and be
      * done with it.
      */
     startBulkSend: function ({actor, type, length}) {
-      this.emit("startBulkSend", {actor, type, length});
+      this.emit("startbulksend", {actor, type, length});
 
       let serial = this._serial.count++;
 
       dumpn("Sent bulk packet " + serial + " for actor " + actor);
       if (!this.other) {
         let error = new Error("startBulkSend: other side of transport missing");
         return promise.reject(error);
       }
@@ -612,17 +612,17 @@
             StreamUtils.copyStream(pipe.inputStream, output, length);
             deferred.resolve(copying);
             return copying;
           },
           stream: pipe.inputStream,
           done: deferred
         };
 
-        this.other.emit("onBulkPacket", packet);
+        this.other.emit("bulkpacket", packet);
         this.other.hooks.onBulkPacket(packet);
 
         // Await the result of reading from the stream
         deferred.promise.then(() => pipe.inputStream.close(), this.close);
       }, "LocalDebuggerTransport instance's this.other.hooks.onBulkPacket"));
 
       // Sender
       let sendDeferred = defer();
@@ -739,22 +739,22 @@
       } catch (e) {
         if (e.result != Cr.NS_ERROR_NULL_POINTER) {
           throw e;
         }
         // In some cases, especially when using messageManagers in non-e10s mode, we reach
         // this point with a dead messageManager which only throws errors but does not
         // seem to indicate in any other way that it is dead.
       }
-      this.emit("onClosed");
+      this.emit("close");
       this.hooks.onClosed();
     },
 
     receiveMessage: function ({data}) {
-      this.emit("onPacket", data);
+      this.emit("packet", data);
       this.hooks.onPacket(data);
     },
 
     send: function (packet) {
       this.emit("send", packet);
       try {
         this._sender.sendAsyncMessage(this._messageName, packet);
       } catch (e) {
--- a/devtools/shared/transport/websocket-transport.js
+++ b/devtools/shared/transport/websocket-transport.js
@@ -33,17 +33,17 @@ WebSocketDebuggerTransport.prototype = {
     }
   },
 
   startBulkSend() {
     throw new Error("Bulk send is not supported by WebSocket transport");
   },
 
   close() {
-    this.emit("onClosed");
+    this.emit("close");
     this.active = false;
 
     this.socket.removeEventListener("message", this);
     this.socket.removeEventListener("close", this);
     this.socket.close();
     this.socket = null;
 
     if (this.hooks) {
@@ -64,16 +64,16 @@ WebSocketDebuggerTransport.prototype = {
   },
 
   onMessage({ data }) {
     if (typeof data !== "string") {
       throw new Error("Binary messages are not supported by WebSocket transport");
     }
 
     let object = JSON.parse(data);
-    this.emit("onPacket", object);
+    this.emit("packet", object);
     if (this.hooks) {
       this.hooks.onPacket(object);
     }
   },
 };
 
 module.exports = WebSocketDebuggerTransport;
--- a/devtools/shared/worker/loader.js
+++ b/devtools/shared/worker/loader.js
@@ -493,16 +493,23 @@ this.worker = new WorkerDebuggerLoader({
     "Services": Object.create(null),
     "chrome": chrome,
     "xpcInspector": xpcInspector
   },
   paths: {
     // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
     "": "resource://gre/modules/commonjs/",
     // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
+    // Modules here are intended to have one implementation for
+    // chrome, and a separate implementation for content.  Here we
+    // map the directory to the chrome subdirectory, but the content
+    // loader will map to the content subdirectory.  See the
+    // README.md in devtools/shared/platform.
+    "devtools/shared/platform": "resource://devtools/shared/platform/chrome",
+    // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
     "devtools": "resource://devtools",
     // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
     "promise": "resource://gre/modules/Promise-backend.js",
     // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
     "source-map": "resource://devtools/shared/sourcemap/source-map.js",
     // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
     "xpcshell-test": "resource://test"
     // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
--- a/dom/base/nsContentIterator.cpp
+++ b/dom/base/nsContentIterator.cpp
@@ -940,22 +940,25 @@ nsContentIterator::First()
 
   mIsDone = mFirst == nullptr;
 }
 
 
 void
 nsContentIterator::Last()
 {
-  NS_ASSERTION(mLast, "No last node!");
+  // Note that mLast can be nullptr if MakeEmpty() is called in Init() since
+  // at that time, Init() returns NS_OK.
+  if (!mLast) {
+    MOZ_ASSERT(mIsDone);
+    return;
+  }
 
-  if (mLast) {
-    mozilla::DebugOnly<nsresult> rv = PositionAt(mLast);
-    NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to position iterator!");
-  }
+  mozilla::DebugOnly<nsresult> rv = PositionAt(mLast);
+  NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to position iterator!");
 
   mIsDone = mLast == nullptr;
 }
 
 
 void
 nsContentIterator::Next()
 {
--- a/dom/events/ContentEventHandler.cpp
+++ b/dom/events/ContentEventHandler.cpp
@@ -390,16 +390,19 @@ ContentEventHandler::QueryContentRect(ns
     nsRect frameRect(nsPoint(0, 0), frame->GetRect().Size());
     rv = ConvertToRootRelativeOffset(frame, frameRect);
     NS_ENSURE_SUCCESS(rv, rv);
     resultRect.UnionRect(resultRect, frameRect);
   }
 
   aEvent->mReply.mRect = LayoutDeviceIntRect::FromUnknownRect(
       resultRect.ToOutsidePixels(mPresContext->AppUnitsPerDevPixel()));
+  // Returning empty rect may cause native IME confused, let's make sure to
+  // return non-empty rect.
+  EnsureNonEmptyRect(aEvent->mReply.mRect);
   aEvent->mSucceeded = true;
 
   return NS_OK;
 }
 
 // Editor places a bogus BR node under its root content if the editor doesn't
 // have any text. This happens even for single line editors.
 // When we get text content and when we change the selection,
@@ -412,16 +415,21 @@ static bool IsContentBR(nsIContent* aCon
                                 nsGkAtoms::moz,
                                 eIgnoreCase) &&
          !aContent->AttrValueIs(kNameSpaceID_None,
                                 nsGkAtoms::mozeditorbogusnode,
                                 nsGkAtoms::_true,
                                 eIgnoreCase);
 }
 
+static bool IsMozBR(nsIContent* aContent)
+{
+  return aContent->IsHTMLElement(nsGkAtoms::br) && !IsContentBR(aContent);
+}
+
 static void ConvertToNativeNewlines(nsAFlatString& aString)
 {
 #if defined(XP_WIN)
   aString.ReplaceSubstring(NS_LITERAL_STRING("\n"), NS_LITERAL_STRING("\r\n"));
 #endif
 }
 
 static void AppendString(nsAString& aString, nsIContent* aContent)
@@ -651,21 +659,37 @@ ContentEventHandler::ShouldBreakLineBefo
 
   // If the element is unknown element, we shouldn't insert line breaks before
   // it since unknown elements should be ignored.
   RefPtr<HTMLUnknownElement> unknownHTMLElement = do_QueryObject(aContent);
   return !unknownHTMLElement;
 }
 
 nsresult
+ContentEventHandler::GenerateFlatTextContent(nsIContent* aContent,
+                                             nsAFlatString& aString,
+                                             LineBreakType aLineBreakType)
+{
+  MOZ_ASSERT(aString.IsEmpty());
+
+  RefPtr<nsRange> range = new nsRange(mRootContent);
+  ErrorResult rv;
+  range->SelectNodeContents(*aContent, rv);
+  if (NS_WARN_IF(rv.Failed())) {
+    return rv.StealNSResult();
+  }
+  return GenerateFlatTextContent(range, aString, aLineBreakType);
+}
+
+nsresult
 ContentEventHandler::GenerateFlatTextContent(nsRange* aRange,
                                              nsAFlatString& aString,
                                              LineBreakType aLineBreakType)
 {
-  NS_ASSERTION(aString.IsEmpty(), "aString must be empty string");
+  MOZ_ASSERT(aString.IsEmpty());
 
   if (aRange->Collapsed()) {
     return NS_OK;
   }
 
   nsINode* startNode = aRange->GetStartParent();
   nsINode* endNode = aRange->GetEndParent();
   if (NS_WARN_IF(!startNode) || NS_WARN_IF(!endNode)) {
@@ -940,21 +964,25 @@ ContentEventHandler::ExpandToClusterBoun
 }
 
 nsresult
 ContentEventHandler::SetRangeFromFlatTextOffset(nsRange* aRange,
                                                 uint32_t aOffset,
                                                 uint32_t aLength,
                                                 LineBreakType aLineBreakType,
                                                 bool aExpandToClusterBoundaries,
-                                                uint32_t* aNewOffset)
+                                                uint32_t* aNewOffset,
+                                                nsIContent** aLastTextNode)
 {
   if (aNewOffset) {
     *aNewOffset = aOffset;
   }
+  if (aLastTextNode) {
+    *aLastTextNode = nullptr;
+  }
 
   // Special case like <br contenteditable>
   if (!mRootContent->HasChildren()) {
     nsresult rv = aRange->SetStart(mRootContent, 0);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     rv = aRange->SetEnd(mRootContent, 0);
@@ -978,16 +1006,21 @@ ContentEventHandler::SetRangeFromFlatTex
       break;
     }
     // FYI: mRootContent shouldn't cause any text. So, we can skip it simply.
     if (node == mRootContent || !node->IsContent()) {
       continue;
     }
     nsIContent* content = node->AsContent();
 
+    if (aLastTextNode && content->IsNodeOfType(nsINode::eTEXT)) {
+      NS_IF_RELEASE(*aLastTextNode);
+      NS_ADDREF(*aLastTextNode = content);
+    }
+
     uint32_t textLength =
       content->IsNodeOfType(nsINode::eTEXT) ?
         GetTextLength(content, aLineBreakType) :
         (ShouldBreakLineBefore(content, mRootContent) ?
            GetBRLength(aLineBreakType) : 0);
     if (!textLength) {
       continue;
     }
@@ -1066,17 +1099,29 @@ ContentEventHandler::SetRangeFromFlatTex
     // range.
     if (endOffset <= offset + textLength) {
       MOZ_ASSERT(startSet,
         "The start of the range should've been set already");
       if (content->IsNodeOfType(nsINode::eTEXT)) {
         // Rule #2.1: ]textNode or text]Node or textNode]
         uint32_t xpOffset = endOffset - offset;
         if (aLineBreakType == LINE_BREAK_TYPE_NATIVE) {
-          xpOffset = ConvertToXPOffset(content, xpOffset);
+          uint32_t xpOffsetCurrent = ConvertToXPOffset(content, xpOffset);
+          if (xpOffset && GetBRLength(aLineBreakType) > 1) {
+            MOZ_ASSERT(GetBRLength(aLineBreakType) == 2);
+            uint32_t xpOffsetPre = ConvertToXPOffset(content, xpOffset - 1);
+            // If previous character's XP offset is same as current character's,
+            // it means that the end offset is between \r and \n.  So, the
+            // range end should be after the \n.
+            if (xpOffsetPre == xpOffsetCurrent) {
+              xpOffset = xpOffsetCurrent + 1;
+            } else {
+              xpOffset = xpOffsetCurrent;
+            }
+          }
         }
         if (aExpandToClusterBoundaries) {
           rv = ExpandToClusterBoundary(content, true, &xpOffset);
           if (NS_WARN_IF(NS_FAILED(rv))) {
             return rv;
           }
         }
         NS_ASSERTION(xpOffset <= INT32_MAX,
@@ -1370,217 +1415,975 @@ ContentEventHandler::OnQueryTextContent(
                "Font ranges doesn't match the string");
   }
 
   aEvent->mSucceeded = true;
 
   return NS_OK;
 }
 
-// Adjust to use a child node if possible
-// to make the returned rect more accurate
-static nsINode* AdjustTextRectNode(nsINode* aNode,
-                                   int32_t& aNodeOffset)
+void
+ContentEventHandler::EnsureNonEmptyRect(nsRect& aRect) const
+{
+  // See the comment in ContentEventHandler.h why this doesn't set them to
+  // one device pixel.
+  aRect.height = std::max(1, aRect.height);
+  aRect.width = std::max(1, aRect.width);
+}
+
+void
+ContentEventHandler::EnsureNonEmptyRect(LayoutDeviceIntRect& aRect) const
+{
+  aRect.height = std::max(1, aRect.height);
+  aRect.width = std::max(1, aRect.width);
+}
+
+ContentEventHandler::NodePosition
+ContentEventHandler::GetNodePositionHavingFlatText(
+                       const NodePosition& aNodePosition)
+{
+  return GetNodePositionHavingFlatText(aNodePosition.mNode,
+                                       aNodePosition.mOffset);
+}
+
+ContentEventHandler::NodePosition
+ContentEventHandler::GetNodePositionHavingFlatText(nsINode* aNode,
+                                                   int32_t aNodeOffset)
+{
+  if (aNode->IsNodeOfType(nsINode::eTEXT)) {
+    return NodePosition(aNode, aNodeOffset);
+  }
+
+  int32_t childCount = static_cast<int32_t>(aNode->GetChildCount());
+
+  // If it's a empty element node, returns itself.
+  if (!childCount) {
+    MOZ_ASSERT(!aNodeOffset || aNodeOffset == 1);
+    return NodePosition(aNode, aNodeOffset);
+  }
+
+  // If there is a node at given position, return the start of it.
+  if (aNodeOffset < childCount) {
+    return NodePosition(aNode->GetChildAt(aNodeOffset), 0);
+  }
+
+  // If the offset represents "after" the node, we need to return the last
+  // child of it.  For example, if a range is |<p>[<br>]</p>|, then, the
+  // end point is {<p>, 1}.  In such case, callers need the <br> node.
+  if (aNodeOffset == childCount) {
+    NodePosition result;
+    result.mNode = aNode->GetChildAt(childCount - 1);
+    result.mOffset = result.mNode->IsNodeOfType(nsINode::eTEXT) ?
+      static_cast<int32_t>(result.mNode->AsContent()->TextLength()) : 1;
+  }
+
+  NS_WARNING("aNodeOffset is invalid value");
+  return NodePosition();
+}
+
+ContentEventHandler::FrameAndNodeOffset
+ContentEventHandler::GetFirstFrameInRangeForTextRect(nsRange* aRange)
 {
-  int32_t childCount = int32_t(aNode->GetChildCount());
-  nsINode* node = aNode;
-  if (childCount) {
-    if (aNodeOffset < childCount) {
-      node = aNode->GetChildAt(aNodeOffset);
-      aNodeOffset = 0;
-    } else if (aNodeOffset == childCount) {
-      node = aNode->GetChildAt(childCount - 1);
-      aNodeOffset = node->IsNodeOfType(nsINode::eTEXT) ?
-        static_cast<int32_t>(static_cast<nsIContent*>(node)->TextLength()) : 1;
+  NodePosition nodePosition;
+  nsCOMPtr<nsIContentIterator> iter = NS_NewPreContentIterator();
+  for (iter->Init(aRange); !iter->IsDone(); iter->Next()) {
+    nsINode* node = iter->GetCurrentNode();
+    if (NS_WARN_IF(!node)) {
+      break;
+    }
+
+    if (!node->IsContent()) {
+      continue;
+    }
+
+    if (node->IsNodeOfType(nsINode::eTEXT)) {
+      // If the range starts at the end of a text node, we need to find
+      // next node which causes text.
+      int32_t offsetInNode =
+        node == aRange->GetStartParent() ? aRange->StartOffset() : 0;
+      if (static_cast<uint32_t>(offsetInNode) < node->Length()) {
+        nodePosition.mNode = node;
+        nodePosition.mOffset = offsetInNode;
+        break;
+      }
+      continue;
+    }
+
+    // If the element node causes a line break before it, it's the first
+    // node causing text.
+    if (ShouldBreakLineBefore(node->AsContent(), mRootContent) ||
+        IsMozBR(node->AsContent())) {
+      nodePosition.mNode = node;
+      nodePosition.mOffset = 0;
+    }
+  }
+
+  if (!nodePosition.IsValid()) {
+    return FrameAndNodeOffset();
+  }
+
+  nsIFrame* firstFrame = nullptr;
+  GetFrameForTextRect(nodePosition.mNode, nodePosition.mOffset,
+                      true, &firstFrame);
+  return FrameAndNodeOffset(firstFrame, nodePosition.mOffset);
+}
+
+ContentEventHandler::FrameAndNodeOffset
+ContentEventHandler::GetLastFrameInRangeForTextRect(nsRange* aRange)
+{
+  NodePosition nodePosition;
+  nsCOMPtr<nsIContentIterator> iter = NS_NewPreContentIterator();
+  iter->Init(aRange);
+
+  nsINode* endNode = aRange->GetEndParent();
+  uint32_t endOffset = static_cast<uint32_t>(aRange->EndOffset());
+  // If the end point is start of a text node or specified by its parent and
+  // index, the node shouldn't be included into the range.  For example,
+  // with this case, |<p>abc[<br>]def</p>|, the range ends at 3rd children of
+  // <p> (see the range creation rules, "2.4. Cases: <element/>]"). This causes
+  // following frames:
+  // +----+-----+
+  // | abc|[<br>|
+  // +----+-----+
+  // +----+
+  // |]def|
+  // +----+
+  // So, if this method includes the 2nd text frame's rect to its result, the
+  // caller will return too tall rect which includes 2 lines in this case isn't
+  // expected by native IME  (e.g., popup of IME will be positioned at bottom
+  // of "d" instead of right-bottom of "c").  Therefore, this method shouldn't
+  // include the last frame when its content isn't really in aRange.
+  nsINode* nextNodeOfRangeEnd = nullptr;
+  if (endNode->IsNodeOfType(nsINode::eTEXT)) {
+    if (!endOffset) {
+      nextNodeOfRangeEnd = endNode;
+    }
+  } else if (endOffset < endNode->GetChildCount()) {
+    nextNodeOfRangeEnd = endNode->GetChildAt(endOffset);
+  }
+
+  for (iter->Last(); !iter->IsDone(); iter->Prev()) {
+    nsINode* node = iter->GetCurrentNode();
+    if (NS_WARN_IF(!node)) {
+      break;
+    }
+
+    if (!node->IsContent() || node == nextNodeOfRangeEnd) {
+      continue;
+    }
+
+    if (node->IsNodeOfType(nsINode::eTEXT)) {
+      nodePosition.mNode = node;
+      if (node == aRange->GetEndParent()) {
+        nodePosition.mOffset = aRange->EndOffset();
+      } else {
+        nodePosition.mOffset = node->Length();
+      }
+      break;
+    }
+
+    if (ShouldBreakLineBefore(node->AsContent(), mRootContent) ||
+        IsMozBR(node->AsContent())) {
+      nodePosition.mNode = node;
+      nodePosition.mOffset = 0;
+      break;
     }
   }
-  return node;
+
+  if (!nodePosition.IsValid()) {
+    return FrameAndNodeOffset();
+  }
+
+  nsIFrame* lastFrame = nullptr;
+  GetFrameForTextRect(nodePosition.mNode, nodePosition.mOffset,
+                      true, &lastFrame);
+  if (!lastFrame) {
+    return FrameAndNodeOffset();
+  }
+
+  // If the last frame is a text frame, we need to check if the range actually
+  // includes at least one character in the range.  Therefore, if it's not a
+  // text frame, we need to do nothing anymore.
+  if (lastFrame->GetType() != nsGkAtoms::textFrame) {
+    return FrameAndNodeOffset(lastFrame, nodePosition.mOffset);
+  }
+
+  int32_t start, end;
+  if (NS_WARN_IF(NS_FAILED(lastFrame->GetOffsets(start, end)))) {
+    return FrameAndNodeOffset();
+  }
+
+  // If the start offset in the node is same as the computed offset in the
+  // node, the frame shouldn't be added to the text rect.  So, this should
+  // return previous text frame and its last offset.
+  if (nodePosition.mOffset == start) {
+    MOZ_ASSERT(nodePosition.mOffset);
+    GetFrameForTextRect(nodePosition.mNode, --nodePosition.mOffset,
+                        true, &lastFrame);
+    if (NS_WARN_IF(!lastFrame)) {
+      return FrameAndNodeOffset();
+    }
+  }
+
+  return FrameAndNodeOffset(lastFrame, nodePosition.mOffset);
 }
 
-static
-nsIFrame*
-GetFirstFrameInRange(nsRange* aRange)
+ContentEventHandler::FrameRelativeRect
+ContentEventHandler::GetLineBreakerRectBefore(nsIFrame* aFrame)
 {
-  // used to iterate over all contents and their frames
-  nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator();
-  iter->Init(aRange);
+  // Note that this method should be called only with an element's frame whose
+  // open tag causes a line break or moz-<br> for computing empty last line's
+  // rect.
+  MOZ_ASSERT(ShouldBreakLineBefore(aFrame->GetContent(), mRootContent) ||
+             IsMozBR(aFrame->GetContent()));
+
+  nsIFrame* frameForFontMetrics = aFrame;
+
+  // If it's not a <br> frame, this method computes the line breaker's rect
+  // outside the frame.  Therefore, we need to compute with parent frame's
+  // font metrics in such case.
+  if (aFrame->GetType() != nsGkAtoms::brFrame && aFrame->GetParent()) {
+    frameForFontMetrics = aFrame->GetParent();
+  }
+
+  // Note that <br> element's rect is decided with line-height but we need
+  // a rect only with font height.  Additionally, <br> frame's width and
+  // height are 0 in quirks mode if it's not an empty line.  So, we cannot
+  // use frame rect information even if it's a <br> frame.
+
+  FrameRelativeRect result(aFrame);
+
+  RefPtr<nsFontMetrics> fontMetrics =
+    nsLayoutUtils::GetInflatedFontMetricsForFrame(frameForFontMetrics);
+  if (NS_WARN_IF(!fontMetrics)) {
+    return FrameRelativeRect();
+  }
+
+  const WritingMode kWritingMode = frameForFontMetrics->GetWritingMode();
+  nscoord baseline = aFrame->GetCaretBaseline();
+  if (kWritingMode.IsVertical()) {
+    if (kWritingMode.IsLineInverted()) {
+      result.mRect.x = baseline - fontMetrics->MaxDescent();
+    } else {
+      result.mRect.x = baseline - fontMetrics->MaxAscent();
+    }
+    result.mRect.width = fontMetrics->MaxHeight();
+  } else {
+    result.mRect.y = baseline - fontMetrics->MaxAscent();
+    result.mRect.height = fontMetrics->MaxHeight();
+  }
 
-  // get the starting frame
-  int32_t nodeOffset = aRange->StartOffset();
-  nsINode* node = iter->GetCurrentNode();
-  if (!node) {
-    node = AdjustTextRectNode(aRange->GetStartParent(), nodeOffset);
+  // If aFrame isn't a <br> frame, caret should be at outside of it because
+  // the line break is before its open tag.  For example, case of
+  // |<div><p>some text</p></div>|, caret is before <p> element and in <div>
+  // element, the caret should be left of top-left corner of <p> element like:
+  // 
+  // +-<div>-------------------  <div>'s border box
+  // | I +-<p>-----------------  <p>'s border box
+  // | I |
+  // | I |
+  // |   |
+  //   ^- caret
+  //
+  // However, this is a hack for unusual scenario.  This hack shouldn't be
+  // used as far as possible.
+  if (aFrame->GetType() != nsGkAtoms::brFrame) {
+    if (kWritingMode.IsVertical()) {
+      if (kWritingMode.IsLineInverted()) {
+        // above of top-left corner of aFrame.
+        result.mRect.x = 0;
+      } else {
+        // above of top-right corner of aFrame.
+        result.mRect.x = aFrame->GetRect().XMost() - result.mRect.width;
+      }
+      result.mRect.y = -mPresContext->AppUnitsPerDevPixel();
+    } else {
+      // left of top-left corner of aFrame.
+      result.mRect.x = -mPresContext->AppUnitsPerDevPixel();
+      result.mRect.y = 0;
+    }
+  }
+  return result;
+}
+
+ContentEventHandler::FrameRelativeRect
+ContentEventHandler::GuessLineBreakerRectAfter(nsIContent* aTextContent)
+{
+  // aTextContent should be a text node.
+  MOZ_ASSERT(aTextContent->IsNodeOfType(nsINode::eTEXT));
+
+  FrameRelativeRect result;
+  int32_t length = static_cast<int32_t>(aTextContent->Length());
+  if (NS_WARN_IF(length < 0)) {
+    return result;
+  }
+  // Get the last nsTextFrame which is caused by aTextContent.  Note that
+  // a text node can cause multiple text frames, e.g., the text is too long
+  // and wrapped by its parent block or the text has line breakers and its
+  // white-space property respects the line breakers (e.g., |pre|).
+  nsIFrame* lastTextFrame = nullptr;
+  nsresult rv = GetFrameForTextRect(aTextContent, length, true, &lastTextFrame);
+  if (NS_WARN_IF(NS_FAILED(rv)) || NS_WARN_IF(!lastTextFrame)) {
+    return result;
   }
-  nsIFrame* firstFrame = nullptr;
-  GetFrameForTextRect(node, nodeOffset, true, &firstFrame);
-  return firstFrame;
+  const nsRect kLastTextFrameRect = lastTextFrame->GetRect();
+  if (lastTextFrame->GetWritingMode().IsVertical()) {
+    // Below of the last text frame.
+    result.mRect.SetRect(0, kLastTextFrameRect.height,
+                         kLastTextFrameRect.width, 0);
+  } else {
+    // Right of the last text frame (not bidi-aware).
+    result.mRect.SetRect(kLastTextFrameRect.width, 0,
+                         0, kLastTextFrameRect.height);
+  }
+  result.mBaseFrame = lastTextFrame;
+  return result;
+}
+
+ContentEventHandler::FrameRelativeRect
+ContentEventHandler::GuessFirstCaretRectIn(nsIFrame* aFrame)
+{
+  const WritingMode kWritingMode = aFrame->GetWritingMode();
+
+  // Computes the font height, but if it's not available, we should use
+  // default font size of Firefox.  The default font size in default settings
+  // is 16px.
+  RefPtr<nsFontMetrics> fontMetrics =
+    nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame);
+  const nscoord kMaxHeight =
+    fontMetrics ? fontMetrics->MaxHeight() :
+                  16 * mPresContext->AppUnitsPerDevPixel();
+
+  nsRect caretRect;
+  const nsRect kContentRect = aFrame->GetContentRect() - aFrame->GetPosition();
+  caretRect.y = kContentRect.y;
+  if (!kWritingMode.IsVertical()) {
+    if (kWritingMode.IsBidiLTR()) {
+      caretRect.x = kContentRect.x;
+    } else {
+      // Move 1px left for the space of caret itself.
+      const nscoord kOnePixel = mPresContext->AppUnitsPerDevPixel();
+      caretRect.x = kContentRect.XMost() - kOnePixel;
+    }
+    caretRect.height = kMaxHeight;
+    // However, don't add kOnePixel here because it may cause 2px width at
+    // aligning the edge to device pixels.
+    caretRect.width = 1;
+  } else {
+    if (kWritingMode.IsVerticalLR()) {
+      caretRect.x = kContentRect.x;
+    } else {
+      caretRect.x = kContentRect.XMost() - kMaxHeight;
+    }
+    caretRect.width = kMaxHeight;
+    // Don't add app units for a device pixel because it may cause 2px height
+    // at aligning the edge to device pixels.
+    caretRect.height = 1;
+  }
+  return FrameRelativeRect(caretRect, aFrame);
 }
 
 nsresult
 ContentEventHandler::OnQueryTextRectArray(WidgetQueryContentEvent* aEvent)
 {
   nsresult rv = Init(aEvent);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   LineBreakType lineBreakType = GetLineBreakType(aEvent);
+  const uint32_t kBRLength = GetBRLength(lineBreakType);
+
   RefPtr<nsRange> range = new nsRange(mRootContent);
-  uint32_t offset = aEvent->mInput.mOffset;
 
+  bool isVertical = false;
   LayoutDeviceIntRect rect;
-  while (aEvent->mInput.mLength > aEvent->mReply.mRectArray.Length()) {
+  uint32_t offset = aEvent->mInput.mOffset;
+  const uint32_t kEndOffset = offset + aEvent->mInput.mLength;
+  bool wasLineBreaker = false;
+  nsRect lastCharRect;
+  while (offset < kEndOffset) {
+    nsCOMPtr<nsIContent> lastTextContent;
     rv = SetRangeFromFlatTextOffset(range, offset, 1, lineBreakType, true,
-                                    nullptr);
+                                    nullptr, getter_AddRefs(lastTextContent));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    // get the starting frame
-    nsIFrame* firstFrame = GetFirstFrameInRange(range);
-    if (NS_WARN_IF(!firstFrame)) {
+    // If the range is collapsed, offset has already reached the end of the
+    // contents.
+    if (range->Collapsed()) {
+      break;
+    }
+
+    // Get the first frame which causes some text after the offset.
+    FrameAndNodeOffset firstFrame = GetFirstFrameInRangeForTextRect(range);
+
+    // If GetFirstFrameInRangeForTextRect() does not return valid frame, that
+    // means that there are no visible frames having text or the offset reached
+    // the end of contents.
+    if (!firstFrame.IsValid()) {
+      nsAutoString allText;
+      rv = GenerateFlatTextContent(mRootContent, allText, lineBreakType);
+      // If the offset doesn't reach the end of contents yet but there is no
+      // frames for the node, that means that current offset's node is hidden
+      // by CSS or something.  Ideally, we should handle it with the last
+      // visible text node's last character's rect, but it's not usual cases
+      // in actual web services.  Therefore, currently, we should make this
+      // case fail.
+      if (NS_WARN_IF(NS_FAILED(rv)) || offset < allText.Length()) {
+        return NS_ERROR_FAILURE;
+      }
+      // Otherwise, we should append caret rect at the end of the contents
+      // later.
+      break;
+    }
+
+    nsIContent* firstContent = firstFrame.mFrame->GetContent();
+    if (NS_WARN_IF(!firstContent)) {
       return NS_ERROR_FAILURE;
     }
 
     // get the starting frame rect
     nsRect frameRect(nsPoint(0, 0), firstFrame->GetRect().Size());
     rv = ConvertToRootRelativeOffset(firstFrame, frameRect);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    int32_t nodeOffset = range->StartOffset();
+    bool startsBetweenLineBreaker = false;
+    nsAutoString chars;
+    // XXX not bidi-aware this class...
+    isVertical = firstFrame->GetWritingMode().IsVertical();
+
     AutoTArray<nsRect, 16> charRects;
-    rv = firstFrame->GetCharacterRectsInRange(
-           nodeOffset,
-           aEvent->mInput.mLength - aEvent->mReply.mRectArray.Length(),
-           charRects);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
+
+    // If the first frame is a text frame, the result should be computed with
+    // the frame's API.
+    if (firstFrame->GetType() == nsGkAtoms::textFrame) {
+      rv = firstFrame->GetCharacterRectsInRange(firstFrame.mOffsetInNode,
+                                                kEndOffset - offset, charRects);
+      if (NS_WARN_IF(NS_FAILED(rv)) || NS_WARN_IF(charRects.IsEmpty())) {
+        return rv;
+      }
+      // Assign the characters whose rects are computed by the call of
+      // nsTextFrame::GetCharacterRectsInRange().
+      AppendSubString(chars, firstContent, firstFrame.mOffsetInNode,
+                      charRects.Length());
+      if (NS_WARN_IF(chars.Length() != charRects.Length())) {
+        return NS_ERROR_UNEXPECTED;
+      }
+      if (kBRLength > 1 && chars[0] == '\n' &&
+          offset == aEvent->mInput.mOffset && offset) {
+        // If start of range starting from previous offset of query range is
+        // same as the start of query range, the query range starts from
+        // between a line breaker (i.e., the range starts between "\r" and
+        // "\n").
+        RefPtr<nsRange> rangeToPrevOffset = new nsRange(mRootContent);
+        rv = SetRangeFromFlatTextOffset(rangeToPrevOffset,
+                                        aEvent->mInput.mOffset - 1, 1,
+                                        lineBreakType, true, nullptr);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return rv;
+        }
+        startsBetweenLineBreaker =
+          range->GetStartParent() == rangeToPrevOffset->GetStartParent() &&
+          range->StartOffset() == rangeToPrevOffset->StartOffset();
+      }
+    }
+    // Other contents should cause a line breaker rect before it.
+    // Note that moz-<br> element does not cause any text, however,
+    // it represents empty line at the last of current block.  Therefore,
+    // we need to compute its rect too.
+    else if (ShouldBreakLineBefore(firstContent, mRootContent) ||
+             IsMozBR(firstContent)) {
+      nsRect brRect;
+      // If the frame is not a <br> frame, we need to compute the caret rect
+      // with last character's rect before firstContent if there is.
+      // For example, if caret is after "c" of |<p>abc</p><p>def</p>|, IME may
+      // query a line breaker's rect after "c".  Then, if we compute it only
+      // with the 2nd <p>'s block frame, the result will be:
+      //  +-<p>--------------------------------+
+      //  |abc                                 |
+      //  +------------------------------------+
+      //
+      // I+-<p>--------------------------------+
+      //  |def                                 |
+      //  +------------------------------------+
+      // However, users expect popup windows of IME should be positioned at
+      // right-bottom of "c" like this:
+      //  +-<p>--------------------------------+
+      //  |abcI                                |
+      //  +------------------------------------+
+      //
+      //  +-<p>--------------------------------+
+      //  |def                                 |
+      //  +------------------------------------+
+      // Therefore, if the first frame isn't a <br> frame and there is a text
+      // node before the first node in the queried range, we should compute the
+      // first rect with the previous character's rect.
+      // If we already compute a character's rect in the queried range, we can
+      // compute it with the cached last character's rect.  (However, don't
+      // use this path if it's a <br> frame because trusting <br> frame's rect
+      // is better than guessing the rect from the previous character.)
+      if (firstFrame->GetType() != nsGkAtoms::brFrame &&
+          aEvent->mInput.mOffset != offset) {
+        // The frame position in the root widget will be added in the
+        // following for loop but we need the rect in the previous frame.
+        // So, we need to avoid using current frame position.
+        brRect = lastCharRect - frameRect.TopLeft();
+        if (!wasLineBreaker) {
+          if (isVertical) {
+            // Right of the last character.
+            brRect.y = brRect.YMost() + 1;
+            brRect.height = 1;
+          } else {
+            // Under the last character.
+            brRect.x = brRect.XMost() + 1;
+            brRect.width = 1;
+          }
+        }
+      }
+      // If it's not a <br> frame and it's the first character rect at the
+      // queried range, we need to the previous character of the start of
+      // the queried range if there is a text node.
+      else if (firstFrame->GetType() != nsGkAtoms::brFrame && lastTextContent) {
+        FrameRelativeRect brRectRelativeToLastTextFrame =
+          GuessLineBreakerRectAfter(lastTextContent);
+        if (NS_WARN_IF(!brRectRelativeToLastTextFrame.IsValid())) {
+          return NS_ERROR_FAILURE;
+        }
+        brRect = brRectRelativeToLastTextFrame.RectRelativeTo(firstFrame);
+      }
+      // Otherwise, we need to compute the line breaker's rect only with the
+      // first frame's rect.  But this may be unexpected.  For example,
+      // |<div contenteditable>[<p>]abc</p></div>|.  In this case, caret is
+      // before "a", therefore, users expect the rect left of "a".  However,
+      // we don't have enough information about the next character here and
+      // this isn't usual case (e.g., IME typically tries to query the rect
+      // of "a" or caret rect for computing its popup position).  Therefore,
+      // we shouldn't do more complicated hack here unless we'll get some bug
+      // reports actually.
+      else {
+        FrameRelativeRect relativeBRRect = GetLineBreakerRectBefore(firstFrame);
+        brRect = relativeBRRect.RectRelativeTo(firstFrame);
+      }
+      charRects.AppendElement(brRect);
+      chars.AssignLiteral("\n");
+      if (kBRLength > 1 && offset == aEvent->mInput.mOffset && offset) {
+        // If the first frame for the previous offset of the query range and
+        // the first frame for the start of query range are same, that means
+        // the start offset is between the first line breaker (i.e., the range
+        // starts between "\r" and "\n").
+        rv = SetRangeFromFlatTextOffset(range, aEvent->mInput.mOffset - 1, 1,
+                                        lineBreakType, true, nullptr);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return NS_ERROR_UNEXPECTED;
+        }
+        FrameAndNodeOffset frameForPrevious =
+          GetFirstFrameInRangeForTextRect(range);
+        startsBetweenLineBreaker = frameForPrevious.mFrame == firstFrame.mFrame;
+      }
+    } else {
+      NS_WARNING("The frame is neither a text frame nor a frame whose content "
+                 "causes a line break");
+      return NS_ERROR_FAILURE;
     }
 
-    for (size_t i = 0; i < charRects.Length(); i++) {
+    for (size_t i = 0; i < charRects.Length() && offset < kEndOffset; i++) {
       nsRect charRect = charRects[i];
       charRect.x += frameRect.x;
       charRect.y += frameRect.y;
+      lastCharRect = charRect;
 
       rect = LayoutDeviceIntRect::FromUnknownRect(
                charRect.ToOutsidePixels(mPresContext->AppUnitsPerDevPixel()));
-
-      // Ensure at least 1px width and height for avoiding empty rect.
-      rect.height = std::max(1, rect.height);
-      rect.width = std::max(1, rect.width);
+      // Returning empty rect may cause native IME confused, let's make sure to
+      // return non-empty rect.
+      EnsureNonEmptyRect(rect);
 
       aEvent->mReply.mRectArray.AppendElement(rect);
+      offset++;
+
+      // If it's not a line breaker or the line breaker length is same as
+      // XP line breaker's, we need to do nothing for current character.
+      wasLineBreaker = chars[i] == '\n';
+      if (!wasLineBreaker || kBRLength == 1) {
+        continue;
+      }
+
+      MOZ_ASSERT(kBRLength == 2);
+
+      // If it's already reached the end of query range, we don't need to do
+      // anymore.
+      if (offset == kEndOffset) {
+        break;
+      }
+
+      // If the query range starts from between a line breaker, i.e., it starts
+      // between "\r" and "\n", the appended rect was for the "\n".  Therefore,
+      // we don't need to append same rect anymore for current "\r\n".
+      if (startsBetweenLineBreaker) {
+        continue;
+      }
+
+      // The appended rect was for "\r" of "\r\n".  Therefore, we need to
+      // append same rect for "\n" too because querying rect of "\r" and "\n"
+      // should return same rect.  E.g., IME may query previous character's
+      // rect of first character of a line.
+      aEvent->mReply.mRectArray.AppendElement(rect);
+      offset++;
     }
-    offset += charRects.Length();
   }
+
+  // If the query range is longer than actual content length, we should append
+  // caret rect at the end of the content as the last character rect because
+  // native IME may want to query character rect at the end of contents for
+  // deciding the position of a popup window (e.g., suggest window for next
+  // word).  Note that when this method hasn't appended character rects, it
+  // means that the offset is too large or the query range is collapsed.
+  if (offset < kEndOffset || aEvent->mReply.mRectArray.IsEmpty()) {
+    // If we've already retrieved some character rects before current offset,
+    // we can guess the last rect from the last character's rect unless it's a
+    // line breaker.  (If it's a line breaker, the caret rect is in next line.)
+    if (!aEvent->mReply.mRectArray.IsEmpty() && !wasLineBreaker) {
+      rect = aEvent->mReply.mRectArray.LastElement();
+      if (isVertical) {
+        rect.y = rect.YMost() + 1;
+        rect.height = 1;
+        MOZ_ASSERT(rect.width);
+      } else {
+        rect.x = rect.XMost() + 1;
+        rect.width = 1;
+        MOZ_ASSERT(rect.height);
+      }
+      aEvent->mReply.mRectArray.AppendElement(rect);
+    } else {
+      // Note that don't use eQueryCaretRect here because if caret is at the
+      // end of the content, it returns actual caret rect instead of computing
+      // the rect itself.  It means that the result depends on caret position.
+      // So, we shouldn't use it for consistency result in automated tests.
+      WidgetQueryContentEvent queryTextRect(eQueryTextRect, *aEvent);
+      WidgetQueryContentEvent::Options options(*aEvent);
+      queryTextRect.InitForQueryTextRect(offset, 1, options);
+      rv = OnQueryTextRect(&queryTextRect);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+      if (NS_WARN_IF(!queryTextRect.mSucceeded)) {
+        return NS_ERROR_FAILURE;
+      }
+      MOZ_ASSERT(!queryTextRect.mReply.mRect.IsEmpty());
+      if (queryTextRect.mReply.mWritingMode.IsVertical()) {
+        queryTextRect.mReply.mRect.height = 1;
+      } else {
+        queryTextRect.mReply.mRect.width = 1;
+      }
+      aEvent->mReply.mRectArray.AppendElement(queryTextRect.mReply.mRect);
+    }
+  }
+
   aEvent->mSucceeded = true;
   return NS_OK;
 }
 
 nsresult
 ContentEventHandler::OnQueryTextRect(WidgetQueryContentEvent* aEvent)
 {
   nsresult rv = Init(aEvent);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
+  // If mLength is 0 (this may be caused by bug of native IME), we should
+  // redirect this event to OnQueryCaretRect().
+  if (!aEvent->mInput.mLength) {
+    return OnQueryCaretRect(aEvent);
+  }
+
   LineBreakType lineBreakType = GetLineBreakType(aEvent);
   RefPtr<nsRange> range = new nsRange(mRootContent);
+  nsCOMPtr<nsIContent> lastTextContent;
   rv = SetRangeFromFlatTextOffset(range, aEvent->mInput.mOffset,
                                   aEvent->mInput.mLength, lineBreakType, true,
-                                  &aEvent->mReply.mOffset);
+                                  &aEvent->mReply.mOffset,
+                                  getter_AddRefs(lastTextContent));
   NS_ENSURE_SUCCESS(rv, rv);
   rv = GenerateFlatTextContent(range, aEvent->mReply.mString, lineBreakType);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // used to iterate over all contents and their frames
   nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator();
   iter->Init(range);
 
-  // get the starting frame
-  int32_t nodeOffset = range->StartOffset();
-  nsINode* node = iter->GetCurrentNode();
-  if (!node) {
-    node = AdjustTextRectNode(range->GetStartParent(), nodeOffset);
-  }
-  nsIFrame* firstFrame = nullptr;
-  rv = GetFrameForTextRect(node, nodeOffset, true, &firstFrame);
-  NS_ENSURE_SUCCESS(rv, rv);
+  // Get the first frame which causes some text after the offset.
+  FrameAndNodeOffset firstFrame = GetFirstFrameInRangeForTextRect(range);
+
+  // If GetFirstFrameInRangeForTextRect() does not return valid frame, that
+  // means that there are no visible frames having text or the offset reached
+  // the end of contents.
+  if (!firstFrame.IsValid()) {
+    nsAutoString allText;
+    rv = GenerateFlatTextContent(mRootContent, allText, lineBreakType);
+    // If the offset doesn't reach the end of contents but there is no frames
+    // for the node, that means that current offset's node is hidden by CSS or
+    // something.  Ideally, we should handle it with the last visible text
+    // node's last character's rect, but it's not usual cases in actual web
+    // services.  Therefore, currently, we should make this case fail.
+    if (NS_WARN_IF(NS_FAILED(rv)) ||
+        static_cast<uint32_t>(aEvent->mInput.mOffset) < allText.Length()) {
+      return NS_ERROR_FAILURE;
+    }
 
-  // get the starting frame rect
-  nsRect rect(nsPoint(0, 0), firstFrame->GetRect().Size());
-  rv = ConvertToRootRelativeOffset(firstFrame, rect);
-  NS_ENSURE_SUCCESS(rv, rv);
-  nsRect frameRect = rect;
-  nsPoint ptOffset;
-  firstFrame->GetPointFromOffset(nodeOffset, &ptOffset);
-  // minus 1 to avoid creating an empty rect
-  if (firstFrame->GetWritingMode().IsVertical()) {
-    rect.y += ptOffset.y - 1;
-    rect.height -= ptOffset.y - 1;
-  } else {
-    rect.x += ptOffset.x - 1;
-    rect.width -= ptOffset.x - 1;
+    // Look for the last frame which should be included text rects.
+    ErrorResult erv;
+    range->SelectNodeContents(*mRootContent, erv);
+    if (NS_WARN_IF(erv.Failed())) {
+      return NS_ERROR_UNEXPECTED;
+    }
+    nsRect rect;
+    FrameAndNodeOffset lastFrame = GetLastFrameInRangeForTextRect(range);
+    // If there is at least one frame which can be used for computing a rect
+    // for a character or a line breaker, we should use it for guessing the
+    // caret rect at the end of the contents.
+    if (lastFrame) {
+      if (NS_WARN_IF(!lastFrame->GetContent())) {
+        return NS_ERROR_FAILURE;
+      }
+      FrameRelativeRect relativeRect;
+      // If there is a <br> frame at the end, it represents an empty line at
+      // the end with moz-<br> or content <br> in a block level element.
+      if (lastFrame->GetType() == nsGkAtoms::brFrame) {
+        relativeRect = GetLineBreakerRectBefore(lastFrame);
+      }
+      // If there is a text frame at the end, use its information.
+      else if (lastFrame->GetType() == nsGkAtoms::textFrame) {
+        relativeRect = GuessLineBreakerRectAfter(lastFrame->GetContent());
+      }
+      // If there is an empty frame which is neither a text frame nor a <br>
+      // frame at the end, guess caret rect in it.
+      else {
+        relativeRect = GuessFirstCaretRectIn(lastFrame);
+      }
+      if (NS_WARN_IF(!relativeRect.IsValid())) {
+        return NS_ERROR_FAILURE;
+      }
+      rect = relativeRect.RectRelativeTo(lastFrame);
+      rv = ConvertToRootRelativeOffset(lastFrame, rect);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+      aEvent->mReply.mWritingMode = lastFrame->GetWritingMode();
+    }
+    // Otherwise, if there are no contents in mRootContent, guess caret rect in
+    // its frame (with its font height and content box).
+    else {
+      nsIFrame* rootContentFrame = mRootContent->GetPrimaryFrame();
+      if (NS_WARN_IF(!rootContentFrame)) {
+        return NS_ERROR_FAILURE;
+      }
+      FrameRelativeRect relativeRect = GuessFirstCaretRectIn(rootContentFrame);
+      if (NS_WARN_IF(!relativeRect.IsValid())) {
+        return NS_ERROR_FAILURE;
+      }
+      rect = relativeRect.RectRelativeTo(rootContentFrame);
+      rv = ConvertToRootRelativeOffset(rootContentFrame, rect);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+      aEvent->mReply.mWritingMode = rootContentFrame->GetWritingMode();
+    }
+    aEvent->mReply.mRect = LayoutDeviceIntRect::FromUnknownRect(
+      rect.ToOutsidePixels(mPresContext->AppUnitsPerDevPixel()));
+    EnsureNonEmptyRect(aEvent->mReply.mRect);
+    aEvent->mSucceeded = true;
+    return NS_OK;
   }
 
-  // get the ending frame
-  nodeOffset = range->EndOffset();
-  node = AdjustTextRectNode(range->GetEndParent(), nodeOffset);
-  nsIFrame* lastFrame = nullptr;
-  rv = GetFrameForTextRect(node, nodeOffset, range->Collapsed(), &lastFrame);
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsRect rect, frameRect;
+  nsPoint ptOffset;
+
+  // If the first frame is a text frame, the result should be computed with
+  // the frame's rect but not including the rect before start point of the
+  // queried range.
+  if (firstFrame->GetType() == nsGkAtoms::textFrame) {
+    rect.SetRect(nsPoint(0, 0), firstFrame->GetRect().Size());
+    rv = ConvertToRootRelativeOffset(firstFrame, rect);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+    frameRect = rect;
+    // Exclude the rect before start point of the queried range.
+    firstFrame->GetPointFromOffset(firstFrame.mOffsetInNode, &ptOffset);
+    if (firstFrame->GetWritingMode().IsVertical()) {
+      rect.y += ptOffset.y;
+      rect.height -= ptOffset.y;
+    } else {
+      rect.x += ptOffset.x;
+      rect.width -= ptOffset.x;
+    }
+  }
+  // If first frame causes a line breaker but it's not a <br> frame, we cannot
+  // compute proper rect only with the frame because typically caret is at
+  // right of the last character of it.  For example, if caret is after "c" of
+  // |<p>abc</p><p>def</p>|, IME may query a line breaker's rect after "c".
+  // Then, if we compute it only with the 2nd <p>'s block frame, the result
+  // will be:
+  //  +-<p>--------------------------------+
+  //  |abc                                 |
+  //  +------------------------------------+
+  //
+  // I+-<p>--------------------------------+
+  //  |def                                 |
+  //  +------------------------------------+
+  // However, users expect popup windows of IME should be positioned at
+  // right-bottom of "c" like this:
+  //  +-<p>--------------------------------+
+  //  |abcI                                |
+  //  +------------------------------------+
+  //
+  //  +-<p>--------------------------------+
+  //  |def                                 |
+  //  +------------------------------------+
+  // Therefore, if the first frame isn't a <br> frame and there is a text
+  // node before the first node in the queried range, we should compute the
+  // first rect with the previous character's rect.
+  else if (firstFrame->GetType() != nsGkAtoms::brFrame && lastTextContent) {
+    FrameRelativeRect brRectAfterLastChar =
+      GuessLineBreakerRectAfter(lastTextContent);
+    if (NS_WARN_IF(!brRectAfterLastChar.IsValid())) {
+      return NS_ERROR_FAILURE;
+    }
+    rect = brRectAfterLastChar.mRect;
+    rv = ConvertToRootRelativeOffset(brRectAfterLastChar.mBaseFrame, rect);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+    frameRect = rect;
+  }
+  // Otherwise, we need to compute the line breaker's rect only with the
+  // first frame's rect.  But this may be unexpected.  For example,
+  // |<div contenteditable>[<p>]abc</p></div>|.  In this case, caret is before
+  // "a", therefore, users expect the rect left of "a".  However, we don't
+  // have enough information about the next character here and this isn't
+  // usual case (e.g., IME typically tries to query the rect of "a" or caret
+  // rect for computing its popup position).  Therefore, we shouldn't do
+  // more complicated hack here unless we'll get some bug reports actually.
+  else {
+    FrameRelativeRect relativeRect = GetLineBreakerRectBefore(firstFrame);
+    if (NS_WARN_IF(!relativeRect.IsValid())) {
+      return NS_ERROR_FAILURE;
+    }
+    rect = relativeRect.RectRelativeTo(firstFrame);
+    rv = ConvertToRootRelativeOffset(firstFrame, rect);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+    frameRect = rect;
+  }
+  // UnionRect() requires non-empty rect.  So, let's make sure to get non-emtpy
+  // rect from the first frame.
+  EnsureNonEmptyRect(rect);
+
+  // Get the last frame which causes some text in the range.
+  FrameAndNodeOffset lastFrame = GetLastFrameInRangeForTextRect(range);
+  if (NS_WARN_IF(!lastFrame.IsValid())) {
+    return NS_ERROR_FAILURE;
+  }
 
   // iterate over all covered frames
   for (nsIFrame* frame = firstFrame; frame != lastFrame;) {
     frame = frame->GetNextContinuation();
     if (!frame) {
       do {
         iter->Next();
-        node = iter->GetCurrentNode();
+        nsINode* node = iter->GetCurrentNode();
         if (!node) {
           break;
         }
         if (!node->IsNodeOfType(nsINode::eCONTENT)) {
           continue;
         }
-        frame = static_cast<nsIContent*>(node)->GetPrimaryFrame();
+        nsIFrame* primaryFrame = node->AsContent()->GetPrimaryFrame();
+        // The node may be hidden by CSS.
+        if (!primaryFrame) {
+          continue;
+        }
+        // We should take only text frame's rect and br frame's rect.  We can
+        // always use frame rect of text frame and GetLineBreakerRectBefore()
+        // can return exactly correct rect only for <br> frame for now.  On the
+        // other hand, GetLineBreakRectBefore() returns guessed caret rect for
+        // the other frames.  We shouldn't include such odd rect to the result.
+        if (primaryFrame->GetType() == nsGkAtoms::textFrame ||
+            primaryFrame->GetType() == nsGkAtoms::brFrame) {
+          frame = primaryFrame;
+        }
       } while (!frame && !iter->IsDone());
       if (!frame) {
-        // this can happen when the end offset of the range is 0.
-        frame = lastFrame;
+        break;
       }
     }
-    frameRect.SetRect(nsPoint(0, 0), frame->GetRect().Size());
+    if (frame->GetType() == nsGkAtoms::textFrame) {
+      frameRect.SetRect(nsPoint(0, 0), frame->GetRect().Size());
+    } else {
+      MOZ_ASSERT(frame->GetType() == nsGkAtoms::brFrame);
+      FrameRelativeRect relativeRect = GetLineBreakerRectBefore(frame);
+      if (NS_WARN_IF(!relativeRect.IsValid())) {
+        return NS_ERROR_FAILURE;
+      }
+      frameRect = relativeRect.RectRelativeTo(frame);
+    }
     rv = ConvertToRootRelativeOffset(frame, frameRect);
-    NS_ENSURE_SUCCESS(rv, rv);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+    // UnionRect() requires non-empty rect.  So, let's make sure to get
+    // non-emtpy rect from the frame.
+    EnsureNonEmptyRect(frameRect);
     if (frame != lastFrame) {
       // not last frame, so just add rect to previous result
       rect.UnionRect(rect, frameRect);
     }
   }
 
-  // get the ending frame rect
-  lastFrame->GetPointFromOffset(nodeOffset, &ptOffset);
-  // minus 1 to avoid creating an empty rect
-  if (lastFrame->GetWritingMode().IsVertical()) {
-    frameRect.height -= lastFrame->GetRect().height - ptOffset.y - 1;
-  } else {
-    frameRect.width -= lastFrame->GetRect().width - ptOffset.x - 1;
+  // Get the ending frame rect.
+  // FYI: If first frame and last frame are same, frameRect is already set
+  //      to the rect excluding the text before the query range.
+  if (firstFrame.mFrame != lastFrame.mFrame) {
+    frameRect.SetRect(nsPoint(0, 0), lastFrame->GetRect().Size());
+    rv = ConvertToRootRelativeOffset(lastFrame, frameRect);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
   }
 
-  if (firstFrame == lastFrame) {
-    rect.IntersectRect(rect, frameRect);
-  } else {
-    rect.UnionRect(rect, frameRect);
+  // Shrink the last frame for cutting off the text after the query range.
+  if (lastFrame->GetType() == nsGkAtoms::textFrame) {
+    lastFrame->GetPointFromOffset(lastFrame.mOffsetInNode, &ptOffset);
+    if (lastFrame->GetWritingMode().IsVertical()) {
+      frameRect.height -= lastFrame->GetRect().height - ptOffset.y;
+    } else {
+      frameRect.width -= lastFrame->GetRect().width - ptOffset.x;
+    }
+    // UnionRect() requires non-empty rect.  So, let's make sure to get
+    // non-empty rect from the last frame.
+    EnsureNonEmptyRect(frameRect);
+
+    if (firstFrame.mFrame == lastFrame.mFrame) {
+      rect.IntersectRect(rect, frameRect);
+    } else {
+      rect.UnionRect(rect, frameRect);
+    }
   }
+
   aEvent->mReply.mRect = LayoutDeviceIntRect::FromUnknownRect(
       rect.ToOutsidePixels(mPresContext->AppUnitsPerDevPixel()));
+  // Returning empty rect may cause native IME confused, let's make sure to
+  // return non-empty rect.
+  EnsureNonEmptyRect(aEvent->mReply.mRect);
   aEvent->mReply.mWritingMode = lastFrame->GetWritingMode();
   aEvent->mSucceeded = true;
   return NS_OK;
 }
 
 nsresult
 ContentEventHandler::OnQueryEditorRect(WidgetQueryContentEvent* aEvent)
 {
@@ -1599,94 +2402,61 @@ ContentEventHandler::OnQueryEditorRect(W
 nsresult
 ContentEventHandler::OnQueryCaretRect(WidgetQueryContentEvent* aEvent)
 {
   nsresult rv = Init(aEvent);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
-  LineBreakType lineBreakType = GetLineBreakType(aEvent);
-
-  nsRect caretRect;
-
   // When the selection is collapsed and the queried offset is current caret
   // position, we should return the "real" caret rect.
   if (mSelection->IsCollapsed()) {
+    nsRect caretRect;
     nsIFrame* caretFrame = nsCaret::GetGeometry(mSelection, &caretRect);
     if (caretFrame) {
       uint32_t offset;
       rv = GetFlatTextLengthBefore(mFirstSelectedRange,
-                                   &offset, lineBreakType);
+                                   &offset, GetLineBreakType(aEvent));
       NS_ENSURE_SUCCESS(rv, rv);
       if (offset == aEvent->mInput.mOffset) {
         rv = ConvertToRootRelativeOffset(caretFrame, caretRect);
         NS_ENSURE_SUCCESS(rv, rv);
         nscoord appUnitsPerDevPixel =
           caretFrame->PresContext()->AppUnitsPerDevPixel();
         aEvent->mReply.mRect = LayoutDeviceIntRect::FromUnknownRect(
           caretRect.ToOutsidePixels(appUnitsPerDevPixel));
+        // Returning empty rect may cause native IME confused, let's make sure
+        // to return non-empty rect.
+        EnsureNonEmptyRect(aEvent->mReply.mRect);
         aEvent->mReply.mWritingMode = caretFrame->GetWritingMode();
         aEvent->mReply.mOffset = aEvent->mInput.mOffset;
         aEvent->mSucceeded = true;
         return NS_OK;
       }
     }
   }
 
-  // Otherwise, we should set the guessed caret rect.
-  RefPtr<nsRange> range = new nsRange(mRootContent);
-  rv = SetRangeFromFlatTextOffset(range, aEvent->mInput.mOffset, 0,
-                                  lineBreakType, true,
-                                  &aEvent->mReply.mOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = AdjustCollapsedRangeMaybeIntoTextNode(range);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
+  // Otherwise, we should guess the caret rect from the character's rect.
+  WidgetQueryContentEvent queryTextRectEvent(eQueryTextRect, *aEvent);
+  WidgetQueryContentEvent::Options options(*aEvent);
+  queryTextRectEvent.InitForQueryTextRect(aEvent->mInput.mOffset, 1, options);
+  rv = OnQueryTextRect(&queryTextRectEvent);
+  if (NS_WARN_IF(NS_FAILED(rv)) || NS_WARN_IF(!queryTextRectEvent.mSucceeded)) {
+    return NS_ERROR_FAILURE;
   }
-
-  int32_t xpOffsetInFrame;
-  nsIFrame* frame;
-  rv = GetStartFrameAndOffset(range, frame, xpOffsetInFrame);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsPoint posInFrame;
-  rv = frame->GetPointFromOffset(range->StartOffset(), &posInFrame);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  aEvent->mReply.mWritingMode = frame->GetWritingMode();
-  bool isVertical = aEvent->mReply.mWritingMode.IsVertical();
-
-  nsRect rect;
-  rect.x = posInFrame.x;
-  rect.y = posInFrame.y;
-
-  RefPtr<nsFontMetrics> fontMetrics =
-    nsLayoutUtils::GetInflatedFontMetricsForFrame(frame);
-  if (isVertical) {
-    rect.width = fontMetrics->MaxHeight();
-    rect.height = caretRect.height;
+  queryTextRectEvent.mReply.mString.Truncate();
+  aEvent->mReply = queryTextRectEvent.mReply;
+  if (aEvent->GetWritingMode().IsVertical()) {
+    aEvent->mReply.mRect.height = 1;
   } else {
-    rect.width = caretRect.width;
-    rect.height = fontMetrics->MaxHeight();
-  }
-
-  rv = ConvertToRootRelativeOffset(frame, rect);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  aEvent->mReply.mRect = LayoutDeviceIntRect::FromUnknownRect(
-      rect.ToOutsidePixels(mPresContext->AppUnitsPerDevPixel()));
-  // If the caret rect is empty, let's make it non-empty rect.
-  if (!aEvent->mReply.mRect.width) {
     aEvent->mReply.mRect.width = 1;
   }
-  if (!aEvent->mReply.mRect.height) {
-    aEvent->mReply.mRect.height = 1;
-  }
+  // Returning empty rect may cause native IME confused, let's make sure to
+  // return non-empty rect.
   aEvent->mSucceeded = true;
   return NS_OK;
 }
 
 nsresult
 ContentEventHandler::OnQueryContentState(WidgetQueryContentEvent* aEvent)
 {
   nsresult rv = Init(aEvent);
@@ -2243,9 +3013,37 @@ ContentEventHandler::OnSelectionEvent(Wi
 
   mSelection->ScrollIntoViewInternal(
     nsISelectionController::SELECTION_FOCUS_REGION,
     false, nsIPresShell::ScrollAxis(), nsIPresShell::ScrollAxis());
   aEvent->mSucceeded = true;
   return NS_OK;
 }
 
+nsRect
+ContentEventHandler::FrameRelativeRect::RectRelativeTo(
+                                          nsIFrame* aDestFrame) const
+{
+  if (!mBaseFrame || NS_WARN_IF(!aDestFrame)) {
+    return nsRect();
+  }
+
+  if (NS_WARN_IF(aDestFrame->PresContext() != mBaseFrame->PresContext())) {
+    return nsRect();
+  }
+
+  if (aDestFrame == mBaseFrame) {
+    return mRect;
+  }
+
+  nsIFrame* rootFrame = mBaseFrame->PresContext()->PresShell()->GetRootFrame();
+  nsRect baseFrameRectInRootFrame =
+    nsLayoutUtils::TransformFrameRectToAncestor(mBaseFrame, nsRect(),
+                                                rootFrame);
+  nsRect destFrameRectInRootFrame =
+    nsLayoutUtils::TransformFrameRectToAncestor(aDestFrame, nsRect(),
+                                                rootFrame);
+  nsPoint difference =
+    destFrameRectInRootFrame.TopLeft() - baseFrameRectInRootFrame.TopLeft();
+  return mRect - difference;
+}
+
 } // namespace mozilla
--- a/dom/events/ContentEventHandler.h
+++ b/dom/events/ContentEventHandler.h
@@ -235,16 +235,24 @@ protected:
                                 LineBreakType aLineBreakType,
                                 uint32_t aMaxLength = UINT32_MAX);
   // Get the text length of a given range of a content node in
   // the given line break type.
   static uint32_t GetTextLengthInRange(nsIContent* aContent,
                                        uint32_t aXPStartOffset,
                                        uint32_t aXPEndOffset,
                                        LineBreakType aLineBreakType);
+  // Get the contents in aContent (meaning all children of aContent) as plain
+  // text.  E.g., specifying mRootContent gets whole text in it.
+  // Note that the result is not same as .textContent.  The result is
+  // optimized for native IMEs.  For example, <br> element and some block
+  // elements causes "\n" (or "\r\n"), see also ShouldBreakLineBefore().
+  nsresult GenerateFlatTextContent(nsIContent* aContent,
+                                   nsAFlatString& aString,
+                                   LineBreakType aLineBreakType);
   // Get the contents of aRange as plain text.
   nsresult GenerateFlatTextContent(nsRange* aRange,
                                    nsAFlatString& aString,
                                    LineBreakType aLineBreakType);
   // Get the text length before the start position of aRange.
   nsresult GetFlatTextLengthBefore(nsRange* aRange,
                                    uint32_t* aOffset,
                                    LineBreakType aLineBreakType);
@@ -268,17 +276,18 @@ protected:
   // Make the DOM range from the offset of FlatText and the text length.
   // If aExpandToClusterBoundaries is true, the start offset and the end one are
   // expanded to nearest cluster boundaries.
   nsresult SetRangeFromFlatTextOffset(nsRange* aRange,
                                       uint32_t aOffset,
                                       uint32_t aLength,
                                       LineBreakType aLineBreakType,
                                       bool aExpandToClusterBoundaries,
-                                      uint32_t* aNewOffset = nullptr);
+                                      uint32_t* aNewOffset = nullptr,
+                                      nsIContent** aLastTextNode = nullptr);
   // If the aRange isn't in text node but next to a text node, this method
   // modifies it in the text node.  Otherwise, not modified.
   nsresult AdjustCollapsedRangeMaybeIntoTextNode(nsRange* aCollapsedRange);
   // Find the first frame for the range and get the start offset in it.
   nsresult GetStartFrameAndOffset(const nsRange* aRange,
                                   nsIFrame*& aFrame,
                                   int32_t& aOffsetInFrame);
   // Convert the frame relative offset to be relative to the root frame of the
@@ -299,13 +308,124 @@ protected:
                                LineBreakType aLineBreakType);
   nsresult GenerateFlatFontRanges(nsRange* aRange,
                                   FontRangeArray& aFontRanges,
                                   uint32_t& aLength,
                                   LineBreakType aLineBreakType);
   nsresult QueryTextRectByRange(nsRange* aRange,
                                 LayoutDeviceIntRect& aRect,
                                 WritingMode& aWritingMode);
+
+  // Returns a node and position in the node for computing text rect.
+  NodePosition GetNodePositionHavingFlatText(const NodePosition& aNodePosition);
+  NodePosition GetNodePositionHavingFlatText(nsINode* aNode,
+                                             int32_t aNodeOffset);
+
+  struct MOZ_STACK_CLASS FrameAndNodeOffset final
+  {
+    // mFrame is safe since this can live in only stack class and
+    // ContentEventHandler doesn't modify layout after
+    // ContentEventHandler::Init() flushes pending layout.  In other words,
+    // this struct shouldn't be used before calling
+    // ContentEventHandler::Init().
+    nsIFrame* mFrame;
+    // offset in the node of mFrame
+    int32_t mOffsetInNode;
+
+    FrameAndNodeOffset()
+      : mFrame(nullptr)
+      , mOffsetInNode(-1)
+    {
+    }
+
+    FrameAndNodeOffset(nsIFrame* aFrame, int32_t aStartOffsetInNode)
+      : mFrame(aFrame)
+      , mOffsetInNode(aStartOffsetInNode)
+    {
+    }
+
+    nsIFrame* operator->() { return mFrame; }
+    const nsIFrame* operator->() const { return mFrame; }
+    operator nsIFrame*() { return mFrame; }
+    operator const nsIFrame*() const { return mFrame; }
+    bool IsValid() const { return mFrame && mOffsetInNode >= 0; }
+  };
+  // Get first frame after the start of the given range for computing text rect.
+  // This returns invalid FrameAndNodeOffset if there is no content which
+  // should affect to computing text rect in the range.  mOffsetInNode is start
+  // offset in the frame.
+  FrameAndNodeOffset GetFirstFrameInRangeForTextRect(nsRange* aRange);
+
+  // Get last frame before the end of the given range for computing text rect.
+  // This returns invalid FrameAndNodeOffset if there is no content which
+  // should affect to computing text rect in the range.  mOffsetInNode is end
+  // offset in the frame.
+  FrameAndNodeOffset GetLastFrameInRangeForTextRect(nsRange* aRange);
+
+  struct MOZ_STACK_CLASS FrameRelativeRect final
+  {
+    // mRect is relative to the mBaseFrame's position.
+    nsRect mRect;
+    nsIFrame* mBaseFrame;
+
+    FrameRelativeRect()
+      : mBaseFrame(nullptr)
+    {
+    }
+
+    explicit FrameRelativeRect(nsIFrame* aBaseFrame)
+      : mBaseFrame(aBaseFrame)
+    {
+    }
+
+    FrameRelativeRect(const nsRect& aRect, nsIFrame* aBaseFrame)
+      : mRect(aRect)
+      , mBaseFrame(aBaseFrame)
+    {
+    }
+
+    bool IsValid() const { return mBaseFrame != nullptr; }
+
+    // Returns an nsRect relative to aBaseFrame instead of mBaseFrame.
+    nsRect RectRelativeTo(nsIFrame* aBaseFrame) const;
+  };
+
+  // Returns a rect for line breaker before the node of aFrame (If aFrame is
+  // a <br> frame or a block level frame, it causes a line break at its
+  // element's open tag, see also ShouldBreakLineBefore()).  Note that this
+  // doesn't check if aFrame should cause line break in non-debug build.
+  FrameRelativeRect GetLineBreakerRectBefore(nsIFrame* aFrame);
+
+  // Returns a line breaker rect after aTextContent as there is a line breaker
+  // immediately after aTextContent.  This is useful when following block
+  // element causes a line break before it and it needs to compute the line
+  // breaker's rect.  For example, if there is |<p>abc</p><p>def</p>|, the
+  // rect of 2nd <p>'s line breaker should be at right of "c" in the first
+  // <p>, not the start of 2nd <p>.  The result is relative to the last text
+  // frame which represents the last character of aTextContent.
+  FrameRelativeRect GuessLineBreakerRectAfter(nsIContent* aTextContent);
+
+  // Returns a guessed first rect.  I.e., it may be different from actual
+  // caret when selection is collapsed at start of aFrame.  For example, this
+  // guess the caret rect only with the content box of aFrame and its font
+  // height like:
+  // +-aFrame----------------- (border box)
+  // |
+  // |  +--------------------- (content box)
+  // |  | I
+  //      ^ guessed caret rect
+  // However, actual caret is computed with more information like line-height,
+  // child frames of aFrame etc.  But this does not emulate actual caret
+  // behavior exactly for simpler and faster code because it's difficult and
+  // we're not sure it's worthwhile to do it with complicated implementation.
+  FrameRelativeRect GuessFirstCaretRectIn(nsIFrame* aFrame);
+
+  // Make aRect non-empty.  If width and/or height is 0, these methods set them
+  // to 1.  Note that it doesn't set nsRect's width nor height to one device
+  // pixel because using nsRect::ToOutsidePixels() makes actual width or height
+  // to 2 pixels because x and y may not be aligned to device pixels.
+  void EnsureNonEmptyRect(nsRect& aRect) const;
+  void EnsureNonEmptyRect(LayoutDeviceIntRect& aRect) const;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_ContentEventHandler_h_
--- a/dom/html/nsGenericHTMLElement.cpp
+++ b/dom/html/nsGenericHTMLElement.cpp
@@ -281,25 +281,25 @@ nsGenericHTMLElement::GetOffsetRect(CSSI
   Element* docElement = GetComposedDoc()->GetRootElement();
   nsIContent* content = frame->GetContent();
 
   if (content && (content->IsHTMLElement(nsGkAtoms::body) ||
                   content == docElement)) {
     parent = frame;
   }
   else {
-    const bool isPositioned = frame->IsAbsPosContaininingBlock();
+    const bool isPositioned = frame->IsAbsPosContainingBlock();
     const bool isAbsolutelyPositioned = frame->IsAbsolutelyPositioned();
     origin += frame->GetPositionIgnoringScrolling();
 
     for ( ; parent ; parent = parent->GetParent()) {
       content = parent->GetContent();
 
       // Stop at the first ancestor that is positioned.
-      if (parent->IsAbsPosContaininingBlock()) {
+      if (parent->IsAbsPosContainingBlock()) {
         offsetParent = content;
         break;
       }
 
       // Add the parent's origin to our own to get to the
       // right coordinate system.
       const bool isOffsetParent = !isPositioned && IsOffsetParent(parent);
       if (!isAbsolutelyPositioned && !isOffsetParent) {
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -1033,36 +1033,49 @@ void MediaDecoderStateMachine::UpdatePla
   bool fragmentEnded = mFragmentEndTime >= 0 && GetMediaTime() >= mFragmentEndTime;
   mMetadataManager.DispatchMetadataIfNeeded(TimeUnit::FromMicroseconds(aTime));
 
   if (fragmentEnded) {
     StopPlayback();
   }
 }
 
-static const char* const gMachineStateStr[] = {
-  "DECODING_METADATA",
-  "WAIT_FOR_CDM",
-  "DORMANT",
-  "DECODING",
-  "SEEKING",
-  "BUFFERING",
-  "COMPLETED",
-  "SHUTDOWN",
-  "ERROR"
-};
+/* static */ const char*
+MediaDecoderStateMachine::ToStateStr(State aState)
+{
+  switch (aState) {
+    case DECODER_STATE_DECODING_METADATA: return "DECODING_METADATA";
+    case DECODER_STATE_WAIT_FOR_CDM:      return "WAIT_FOR_CDM";
+    case DECODER_STATE_DORMANT:           return "DORMANT";
+    case DECODER_STATE_DECODING:          return "DECODING";
+    case DECODER_STATE_SEEKING:           return "SEEKING";
+    case DECODER_STATE_BUFFERING:         return "BUFFERING";
+    case DECODER_STATE_COMPLETED:         return "COMPLETED";
+    case DECODER_STATE_SHUTDOWN:          return "SHUTDOWN";
+    case DECODER_STATE_ERROR:             return "ERROR";
+    default: MOZ_ASSERT_UNREACHABLE("Invalid state.");
+  }
+  return "UNKNOWN";
+}
+
+const char*
+MediaDecoderStateMachine::ToStateStr()
+{
+  MOZ_ASSERT(OnTaskQueue());
+  return ToStateStr(mState);
+}
 
 void MediaDecoderStateMachine::SetState(State aState)
 {
   MOZ_ASSERT(OnTaskQueue());
   if (mState == aState) {
     return;
   }
   DECODER_LOG("Change machine state from %s to %s",
-              gMachineStateStr[mState], gMachineStateStr[aState]);
+              ToStateStr(), ToStateStr(aState));
 
   mState = aState;
 
   mIsShutdown = mState == DECODER_STATE_ERROR || mState == DECODER_STATE_SHUTDOWN;
 
   // Clear state-scoped state.
   mSentPlaybackEndedEvent = false;
 }
@@ -1139,16 +1152,17 @@ MediaDecoderStateMachine::SetDormant(boo
         // Keep latest seek target
       } else if (mCurrentSeek.Exists()) {
         // Because both audio and video decoders are going to be reset in this
         // method later, we treat a VideoOnly seek task as a normal Accurate
         // seek task so that while it is resumed, both audio and video playback
         // are handled.
         if (mCurrentSeek.mTarget.IsVideoOnly()) {
           mCurrentSeek.mTarget.SetType(SeekTarget::Accurate);
+          mCurrentSeek.mTarget.SetVideoOnly(false);
         }
         mQueuedSeek = Move(mCurrentSeek);
         mSeekTaskRequest.DisconnectIfExists();
       } else {
         mQueuedSeek.mTarget = SeekTarget(mCurrentPosition,
                                          SeekTarget::Accurate,
                                          MediaDecoderEventVisibility::Suppressed);
         // XXXbholley - Nobody is listening to this promise. Do we need to pass it
@@ -1365,18 +1379,19 @@ void MediaDecoderStateMachine::Visibilit
     // one to catch up.
     if (mSeekTask || mQueuedSeek.Exists()) {
       return;
     }
 
     // Start video-only seek to the current time...
     SeekJob seekJob;
     seekJob.mTarget = SeekTarget(GetMediaTime(),
-                                 SeekTarget::Type::AccurateVideoOnly,
-                                 MediaDecoderEventVisibility::Suppressed);
+                                 SeekTarget::Type::Accurate,
+                                 MediaDecoderEventVisibility::Suppressed,
+                                 true /* aVideoOnly */);
     InitiateSeek(Move(seekJob));
   }
 }
 
 void MediaDecoderStateMachine::BufferedRangeUpdated()
 {
   MOZ_ASSERT(OnTaskQueue());
 
@@ -1393,25 +1408,17 @@ void MediaDecoderStateMachine::BufferedR
     }
   }
 }
 
 void MediaDecoderStateMachine::ReaderSuspendedChanged()
 {
   MOZ_ASSERT(OnTaskQueue());
   DECODER_LOG("ReaderSuspendedChanged: %d", mIsReaderSuspended.Ref());
-
-  if (IsShutdown()) {
-    return;
-  }
-  if (mIsReaderSuspended && mState != DECODER_STATE_DORMANT) {
-    SetDormant(true);
-  } else if (!mIsReaderSuspended && mState == DECODER_STATE_DORMANT) {
-    SetDormant(false);
-  }
+  SetDormant(mIsReaderSuspended);
 }
 
 void
 MediaDecoderStateMachine::ReadMetadata()
 {
   MOZ_ASSERT(OnTaskQueue());
   MOZ_ASSERT(!IsShutdown());
   MOZ_ASSERT(mState == DECODER_STATE_DECODING_METADATA);
@@ -1554,18 +1561,17 @@ MediaDecoderStateMachine::InitiateSeek(S
 
   mSeekTaskRequest.DisconnectIfExists();
 
   // SeekTask will register its callbacks to MediaDecoderReaderWrapper.
   CancelMediaDecoderReaderWrapperCallback();
 
   // Create a new SeekTask instance for the incoming seek task.
   if (aSeekJob.mTarget.IsAccurate() ||
-      aSeekJob.mTarget.IsFast() ||
-      aSeekJob.mTarget.IsVideoOnly()) {
+      aSeekJob.mTarget.IsFast()) {
     mSeekTask = new AccurateSeekTask(mDecoderID, OwnerThread(),
                                      mReader.get(), aSeekJob.mTarget,
                                      mInfo, Duration(), GetMediaTime());
   } else if (aSeekJob.mTarget.IsNextFrame()) {
     mSeekTask = new NextFrameSeekTask(mDecoderID, OwnerThread(), mReader.get(),
                                       aSeekJob.mTarget, mInfo, Duration(),
                                       GetMediaTime(), AudioQueue(), VideoQueue());
   } else {
@@ -2779,17 +2785,17 @@ MediaDecoderStateMachine::DumpDebugInfo(
   // this function before shutdown begins.
   nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([this] () {
     DUMP_LOG(
       "GetMediaTime=%lld GetClock=%lld "
       "mState=%s mPlayState=%d mDecodingFirstFrame=%d IsPlaying=%d "
       "mAudioStatus=%s mVideoStatus=%s mDecodedAudioEndTime=%lld mDecodedVideoEndTime=%lld "
       "mIsAudioPrerolling=%d mIsVideoPrerolling=%d",
       GetMediaTime(), mMediaSink->IsStarted() ? GetClock() : -1,
-      gMachineStateStr[mState], mPlayState.Ref(), mDecodingFirstFrame, IsPlaying(),
+      ToStateStr(), mPlayState.Ref(), mDecodingFirstFrame, IsPlaying(),
       AudioRequestStatus(), VideoRequestStatus(), mDecodedAudioEndTime, mDecodedVideoEndTime,
       mIsAudioPrerolling, mIsVideoPrerolling);
   });
 
   OwnerThread()->DispatchStateChange(r.forget());
 }
 
 void MediaDecoderStateMachine::AddOutputStream(ProcessedMediaStream* aStream,
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -250,16 +250,19 @@ public:
   // Immutable after construction - may be called on any thread.
   bool IsRealTime() const { return mRealTime; }
 
   size_t SizeOfVideoQueue() const;
 
   size_t SizeOfAudioQueue() const;
 
 private:
+  static const char* ToStateStr(State aState);
+  const char* ToStateStr();
+
   // Functions used by assertions to ensure we're calling things
   // on the appropriate threads.
   bool OnTaskQueue() const;
 
   // Initialization that needs to happen on the task queue. This is the first
   // task that gets run on the task queue, and is dispatched from the MDSM
   // constructor immediately after the task queue is created.
   void InitializationTask(MediaDecoder* aDecoder);
--- a/dom/media/SeekTarget.h
+++ b/dom/media/SeekTarget.h
@@ -19,86 +19,96 @@ enum class MediaDecoderEventVisibility :
 // Stores the seek target; the time to seek to, and whether an Accurate,
 // "Fast" (nearest keyframe), or "Video Only" (no audio seek) seek was
 // requested.
 struct SeekTarget {
   enum Type {
     Invalid,
     PrevSyncPoint,
     Accurate,
-    AccurateVideoOnly,
     NextFrame,
   };
   SeekTarget()
     : mEventVisibility(MediaDecoderEventVisibility::Observable)
     , mTime(media::TimeUnit::Invalid())
     , mType(SeekTarget::Invalid)
+    , mVideoOnly(false)
   {
   }
   SeekTarget(int64_t aTimeUsecs,
              Type aType,
              MediaDecoderEventVisibility aEventVisibility =
-               MediaDecoderEventVisibility::Observable)
+               MediaDecoderEventVisibility::Observable,
+             bool aVideoOnly = false)
     : mEventVisibility(aEventVisibility)
     , mTime(media::TimeUnit::FromMicroseconds(aTimeUsecs))
     , mType(aType)
+    , mVideoOnly(aVideoOnly)
   {
   }
   SeekTarget(const media::TimeUnit& aTime,
              Type aType,
              MediaDecoderEventVisibility aEventVisibility =
-               MediaDecoderEventVisibility::Observable)
+               MediaDecoderEventVisibility::Observable,
+             bool aVideoOnly = false)
     : mEventVisibility(aEventVisibility)
     , mTime(aTime)
     , mType(aType)
+    , mVideoOnly(aVideoOnly)
   {
   }
   SeekTarget(const SeekTarget& aOther)
     : mEventVisibility(aOther.mEventVisibility)
     , mTime(aOther.mTime)
     , mType(aOther.mType)
+    , mVideoOnly(aOther.mVideoOnly)
   {
   }
   bool IsValid() const {
     return mType != SeekTarget::Invalid;
   }
   void Reset() {
     mTime = media::TimeUnit::Invalid();
     mType = SeekTarget::Invalid;
+    mVideoOnly = false;
   }
   media::TimeUnit GetTime() const {
     NS_ASSERTION(mTime.IsValid(), "Invalid SeekTarget");
     return mTime;
   }
   void SetTime(const media::TimeUnit& aTime) {
     NS_ASSERTION(aTime.IsValid(), "Invalid SeekTarget destination");
     mTime = aTime;
   }
   void SetType(Type aType) {
     mType = aType;
   }
+  void SetVideoOnly(bool aVideoOnly) {
+    mVideoOnly = aVideoOnly;
+  }
   bool IsFast() const {
     return mType == SeekTarget::Type::PrevSyncPoint;
   }
   bool IsAccurate() const {
     return mType == SeekTarget::Type::Accurate;
   }
-  bool IsVideoOnly() const {
-    return mType == SeekTarget::Type::AccurateVideoOnly;
-  }
   bool IsNextFrame() const {
     return mType == SeekTarget::Type::NextFrame;
   }
+  bool IsVideoOnly() const {
+    return mVideoOnly;
+  }
 
   MediaDecoderEventVisibility mEventVisibility;
 
 private:
   // Seek target time.
   media::TimeUnit mTime;
   // Whether we should seek "Fast", or "Accurate".
   // "Fast" seeks to the seek point preceding mTime, whereas
   // "Accurate" seeks as close as possible to mTime.
   Type mType;
+  bool mVideoOnly;
 };
 
 } // namespace mozilla
 
 #endif /* SEEK_TARGET_H */
--- a/dom/media/mediasource/MediaSourceDecoder.cpp
+++ b/dom/media/mediasource/MediaSourceDecoder.cpp
@@ -109,16 +109,20 @@ MediaSourceDecoder::GetSeekable()
   return seekable;
 }
 
 media::TimeIntervals
 MediaSourceDecoder::GetBuffered()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  if (!mMediaSource) {
+    NS_WARNING("MediaSource element isn't attached");
+    return media::TimeIntervals::Invalid();
+  }
   dom::SourceBufferList* sourceBuffers = mMediaSource->ActiveSourceBuffers();
   if (!sourceBuffers) {
     // Media source object is shutting down.
     return TimeIntervals();
   }
   media::TimeUnit highestEndTime;
   nsTArray<media::TimeIntervals> activeRanges;
   media::TimeIntervals buffered;
--- a/dom/media/webaudio/AudioEventTimeline.cpp
+++ b/dom/media/webaudio/AudioEventTimeline.cpp
@@ -179,68 +179,74 @@ AudioEventTimeline::GetValuesAtTimeHelpe
                  next->mType == AudioTimelineEvent::SetValueCurve);
 #endif
 
       if (TimesEqual(aTime, TimeOf(*next))) {
         mLastComputedValue = mComputedValue;
         // Find the last event with the same time
         while (eventIndex < mEvents.Length() - 1 &&
                TimesEqual(aTime, TimeOf(mEvents[eventIndex + 1]))) {
+          mLastComputedValue = GetValueAtTimeOfEvent<TimeType>(&mEvents[eventIndex]);
           ++eventIndex;
         }
+
         timeMatchesEventIndex = true;
         break;
       }
 
       previous = next;
     }
 
     if (timeMatchesEventIndex) {
       // The time matches one of the events exactly.
       MOZ_ASSERT(TimesEqual(aTime, TimeOf(mEvents[eventIndex])));
-
-      switch (mEvents[eventIndex].mType) {
-        case AudioTimelineEvent::SetTarget:
-          // SetTarget nodes can be handled no matter what their next node is
-          // (if they have one).
-          // Follow the curve, without regard to the next event, starting at
-          // the last value of the last event.
-          mComputedValue =
-            ExponentialApproach(TimeOf(mEvents[eventIndex]),
-                                mLastComputedValue, mEvents[eventIndex].mValue,
-                                mEvents[eventIndex].mTimeConstant, aTime);
-          break;
-        case AudioTimelineEvent::SetValueCurve:
-          // SetValueCurve events can be handled no matter what their event
-          // node is (if they have one)
-          mComputedValue =
-            ExtractValueFromCurve(TimeOf(mEvents[eventIndex]),
-                                  mEvents[eventIndex].mCurve,
-                                  mEvents[eventIndex].mCurveLength,
-                                  mEvents[eventIndex].mDuration, aTime);
-          break;
-        default:
-          // For other event types
-          mComputedValue = mEvents[eventIndex].mValue;
-      }
+      mComputedValue = GetValueAtTimeOfEvent<TimeType>(&mEvents[eventIndex]);
     } else {
       mComputedValue = GetValuesAtTimeHelperInternal(aTime, previous, next);
     }
 
     aBuffer[bufferIndex] = mComputedValue;
   }
 }
 template void
 AudioEventTimeline::GetValuesAtTimeHelper(double aTime, float* aBuffer,
                                           const size_t aSize);
 template void
 AudioEventTimeline::GetValuesAtTimeHelper(int64_t aTime, float* aBuffer,
                                           const size_t aSize);
 
 template<class TimeType> float
+AudioEventTimeline::GetValueAtTimeOfEvent(const AudioTimelineEvent* aNext)
+{
+  TimeType time = aNext->template Time<TimeType>();
+  switch (aNext->mType) {
+    case AudioTimelineEvent::SetTarget:
+      // SetTarget nodes can be handled no matter what their next node is
+      // (if they have one).
+      // Follow the curve, without regard to the next event, starting at
+      // the last value of the last event.
+      return ExponentialApproach(time,
+                                 mLastComputedValue, aNext->mValue,
+                                 aNext->mTimeConstant, time);
+      break;
+    case AudioTimelineEvent::SetValueCurve:
+      // SetValueCurve events can be handled no matter what their event
+      // node is (if they have one)
+      return ExtractValueFromCurve(time,
+                                   aNext->mCurve,
+                                   aNext->mCurveLength,
+                                   aNext->mDuration, time);
+      break;
+    default:
+      // For other event types
+      return aNext->mValue;
+  }
+}
+
+template<class TimeType> float
 AudioEventTimeline::GetValuesAtTimeHelperInternal(TimeType aTime,
                                     const AudioTimelineEvent* aPrevious,
                                     const AudioTimelineEvent* aNext)
 {
   // If the requested time is before all of the existing events
   if (!aPrevious) {
      return mValue;
   }
--- a/dom/media/webaudio/AudioEventTimeline.h
+++ b/dom/media/webaudio/AudioEventTimeline.h
@@ -345,16 +345,19 @@ public:
     }
   }
 
 private:
   template<class TimeType>
   void GetValuesAtTimeHelper(TimeType aTime, float* aBuffer, const size_t aSize);
 
   template<class TimeType>
+  float GetValueAtTimeOfEvent(const AudioTimelineEvent* aNext);
+
+  template<class TimeType>
   float GetValuesAtTimeHelperInternal(TimeType aTime,
                                       const AudioTimelineEvent* aPrevious,
                                       const AudioTimelineEvent* aNext);
 
   const AudioTimelineEvent* GetPreviousEvent(double aTime) const;
 
   static bool IsValid(double value)
   {
--- a/dom/media/webaudio/test/mochitest.ini
+++ b/dom/media/webaudio/test/mochitest.ini
@@ -84,16 +84,17 @@ tags=capturestream
 [test_bug875221.html]
 [test_bug875402.html]
 [test_bug894150.html]
 [test_bug956489.html]
 [test_bug964376.html]
 [test_bug966247.html]
 tags=capturestream
 [test_bug972678.html]
+[test_bug1113634.html]
 [test_bug1118372.html]
 [test_bug1027864.html]
 [test_bug1056032.html]
 skip-if = toolkit == 'android' # bug 1056706
 [test_bug1255618.html]
 [test_bug1267579.html]
 [test_channelMergerNode.html]
 [test_channelMergerNodeWithVolume.html]
new file mode 100644
--- /dev/null
+++ b/dom/media/webaudio/test/test_bug1113634.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test AudioParam.setTargetAtTime where the target time is the same as the time of a previous event</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="webaudio.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var V0 = 0.9;
+var V1 = 0.1;
+var T0 = 0;
+var TimeConstant = 0.1;
+
+var gTest = {
+  length: 2048,
+  numberOfChannels: 1,
+  createGraph: function(context) {
+    var sourceBuffer = context.createBuffer(1, 2048, context.sampleRate);
+    for (var i = 0; i < 2048; ++i) {
+      sourceBuffer.getChannelData(0)[i] = 1;
+    }
+
+    var source = context.createBufferSource();
+    source.buffer = sourceBuffer;
+
+    var gain = context.createGain();
+    gain.gain.setValueAtTime(V0, T0);
+    gain.gain.setTargetAtTime(V1, T0, TimeConstant);
+
+    source.connect(gain);
+
+    source.start(0);
+    return gain;
+  },
+  createExpectedBuffers: function(context) {
+    var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate);
+    for (var i = 0; i < 2048; ++i) {
+      var t = i / context.sampleRate;
+      expectedBuffer.getChannelData(0)[i] = V1 + (V0 - V1) * Math.exp(-(t - T0) / TimeConstant);
+    }
+    return expectedBuffer;
+  },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/presentation/PresentationSessionInfo.cpp
+++ b/dom/presentation/PresentationSessionInfo.cpp
@@ -643,32 +643,25 @@ PresentationControllingInfo::GetAddress(
   RefPtr<PresentationNetworkHelper> networkHelper =
     new PresentationNetworkHelper(this,
                                   &PresentationControllingInfo::OnGetAddress);
   nsresult rv = networkHelper->GetWifiIPAddress();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-#elif defined(MOZ_MULET)
-  // In simulator,we need to use the "127.0.0.1" as target address.
-  NS_DispatchToMainThread(
-    NewRunnableMethod<nsCString>(
-      this,
-      &PresentationControllingInfo::OnGetAddress,
-      "127.0.0.1"));
+#else
+  nsCOMPtr<nsINetworkInfoService> networkInfo = do_GetService(NETWORKINFOSERVICE_CONTRACT_ID);
+  MOZ_ASSERT(networkInfo);
 
-#else
-  // TODO Get host IP via other platforms.
+  nsresult rv = networkInfo->ListNetworkAddresses(this);
 
-  NS_DispatchToMainThread(
-    NewRunnableMethod<nsCString>(
-      this,
-      &PresentationControllingInfo::OnGetAddress,
-      EmptyCString()));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 #endif
 
   return NS_OK;
 }
 
 nsresult
 PresentationControllingInfo::OnGetAddress(const nsACString& aAddress)
 {
@@ -947,16 +940,60 @@ PresentationControllingInfo::Reconnect(n
   rv = mControlChannel->Reconnect(mSessionId, GetUrl());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return mReconnectCallback->NotifyError(rv);
   }
 
   return NS_OK;
 }
 
+// nsIListNetworkAddressesListener
+NS_IMETHODIMP
+PresentationControllingInfo::OnListedNetworkAddresses(const char** aAddressArray,
+                                                      uint32_t aAddressArraySize)
+{
+  if (!aAddressArraySize) {
+    return OnListNetworkAddressesFailed();
+  }
+
+  // TODO bug 1228504 Take all IP addresses in PresentationChannelDescription
+  // into account. And at the first stage Presentation API is only exposed on
+  // Firefox OS where the first IP appears enough for most scenarios.
+
+  nsAutoCString ip;
+  ip.Assign(aAddressArray[0]);
+
+  // On Firefox desktop, the IP address is retrieved from a callback function.
+  // To make consistent code sequence, following function call is dispatched
+  // into main thread instead of calling it directly.
+  NS_DispatchToMainThread(
+    NewRunnableMethod<nsCString>(
+      this,
+      &PresentationControllingInfo::OnGetAddress,
+      ip));
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationControllingInfo::OnListNetworkAddressesFailed()
+{
+  PRES_ERROR("PresentationControllingInfo:OnListNetworkAddressesFailed");
+
+  // In 1-UA case, transport channel can still be established
+  // on loopback interface even if no network address available.
+  NS_DispatchToMainThread(
+    NewRunnableMethod<nsCString>(
+      this,
+      &PresentationControllingInfo::OnGetAddress,
+      "127.0.0.1"));
+
+  return NS_OK;
+}
+
 /**
  * Implementation of PresentationPresentingInfo
  *
  * During presentation session establishment, the receiver expects the following
  * after trying to launch the app by notifying "presentation-launch-receiver":
  * (The order between step 2 and 3 is not guaranteed.)
  * 1. |Observe| of |nsIObserver| is called with "presentation-receiver-launched".
  *    Then start listen to document |STATE_TRANSFERRING| event.
--- a/dom/presentation/PresentationSessionInfo.h
+++ b/dom/presentation/PresentationSessionInfo.h
@@ -8,16 +8,17 @@
 #define mozilla_dom_PresentationSessionInfo_h
 
 #include "base/process.h"
 #include "mozilla/dom/nsIContentParent.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/PromiseNativeHandler.h"
 #include "mozilla/RefPtr.h"
 #include "nsCOMPtr.h"
+#include "nsINetworkInfoService.h"
 #include "nsIPresentationControlChannel.h"
 #include "nsIPresentationDevice.h"
 #include "nsIPresentationListener.h"
 #include "nsIPresentationService.h"
 #include "nsIPresentationSessionTransport.h"
 #include "nsIPresentationSessionTransportBuilder.h"
 #include "nsIServerSocket.h"
 #include "nsITimer.h"
@@ -166,21 +167,23 @@ protected:
   nsCOMPtr<nsIPresentationSessionTransport> mTransport;
   nsCOMPtr<nsIPresentationControlChannel> mControlChannel;
   nsCOMPtr<nsIPresentationSessionTransportBuilder> mBuilder;
 };
 
 // Session info with controlling browsing context (sender side) behaviors.
 class PresentationControllingInfo final : public PresentationSessionInfo
                                         , public nsIServerSocketListener
+                                        , public nsIListNetworkAddressesListener
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIPRESENTATIONCONTROLCHANNELLISTENER
   NS_DECL_NSISERVERSOCKETLISTENER
+  NS_DECL_NSILISTNETWORKADDRESSESLISTENER
 
   PresentationControllingInfo(const nsAString& aUrl,
                               const nsAString& aSessionId)
     : PresentationSessionInfo(aUrl,
                               aSessionId,
                               nsIPresentationService::ROLE_CONTROLLER)
   {}
 
--- a/dom/presentation/PresentationTCPSessionTransport.cpp
+++ b/dom/presentation/PresentationTCPSessionTransport.cpp
@@ -323,16 +323,22 @@ PresentationTCPSessionTransport::GetCall
   callback.forget(aCallback);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationTCPSessionTransport::SetCallback(nsIPresentationSessionTransportCallback* aCallback)
 {
   mCallback = aCallback;
+
+  if (!!mCallback && ReadyState::OPEN == mReadyState) {
+    // Notify the transport channel is ready.
+    NS_WARN_IF(NS_FAILED(mCallback->NotifyTransportReady()));
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationTCPSessionTransport::GetSelfAddress(nsINetAddr** aSelfAddress)
 {
   if (NS_WARN_IF(!mTransport)) {
     return NS_ERROR_DOM_INVALID_STATE_ERR;
--- a/dom/presentation/tests/mochitest/PresentationSessionChromeScript.js
+++ b/dom/presentation/tests/mochitest/PresentationSessionChromeScript.js
@@ -369,17 +369,17 @@ function tearDown() {
   mockedDevicePrompt.request = null;
   mockedSessionTransport.callback = null;
 
   var deviceManager = Cc['@mozilla.org/presentation-device/manager;1']
                       .getService(Ci.nsIPresentationDeviceManager);
   deviceManager.QueryInterface(Ci.nsIPresentationDeviceListener).removeDevice(mockedDevice);
 
   // Register original factories.
-  for (var data in originalFactoryData) {
+  for (var data of originalFactoryData) {
     registerOriginalFactory(data.contractId, data.mockedClassId,
                             data.mockedFactory, data.originalClassId,
                             data.originalFactory);
   }
 
   sendAsyncMessage('teardown-complete');
 }
 
--- a/dom/presentation/tests/mochitest/PresentationSessionChromeScript1UA.js
+++ b/dom/presentation/tests/mochitest/PresentationSessionChromeScript1UA.js
@@ -3,163 +3,29 @@
  * 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 { classes: Cc, interfaces: Ci, manager: Cm, utils: Cu, results: Cr } = Components;
 
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import('resource://gre/modules/Services.jsm');
 
 const uuidGenerator = Cc["@mozilla.org/uuid-generator;1"]
                       .getService(Ci.nsIUUIDGenerator);
 
 function debug(str) {
   // dump('DEBUG -*- PresentationSessionChromeScript1UA -*-: ' + str + '\n');
 }
 
 const originalFactoryData = [];
 var sessionId; // Store the uuid generated by PresentationRequest.
-const address = Cc["@mozilla.org/supports-cstring;1"]
-                  .createInstance(Ci.nsISupportsCString);
-address.data = "127.0.0.1";
-const addresses = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
-addresses.appendElement(address, false);
 var triggerControlChannelError = false; // For simulating error during control channel establishment.
 
-function mockChannelDescription(role) {
-  this.QueryInterface = XPCOMUtils.generateQI([Ci.nsIPresentationChannelDescription]);
-  this.role = role;
-  this.type = Ci.nsIPresentationChannelDescription.TYPE_TCP;
-  this.tcpAddress = addresses;
-  this.tcpPort = (role === 'sender' ? 1234 : 4321); // either sender or receiver
-}
-
-const mockChannelDescriptionOfSender   = new mockChannelDescription('sender');
-const mockChannelDescriptionOfReceiver = new mockChannelDescription('receiver');
-
-const mockServerSocket = {
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIServerSocket,
-                                         Ci.nsIFactory]),
-  createInstance: function(aOuter, aIID) {
-    if (aOuter) {
-      throw Components.results.NS_ERROR_NO_AGGREGATION;
-    }
-    return this.QueryInterface(aIID);
-  },
-  get port() {
-    return this._port;
-  },
-  set listener(listener) {
-    this._listener = listener;
-  },
-  init: function(port, loopbackOnly, backLog) {
-    this._port = (port == -1 ? 5678 : port);
-  },
-  asyncListen: function(listener) {
-    this._listener = listener;
-  },
-  close: function() {
-    this._listener.onStopListening(this, Cr.NS_BINDING_ABORTED);
-  },
-  onSocketAccepted: function(serverSocket, socketTransport) {
-    this._listener.onSocketAccepted(serverSocket, socketTransport);
-  }
-};
-
-// mockSessionTransport
-var mockSessionTransportOfSender   = undefined;
-var mockSessionTransportOfReceiver = undefined;
-
-function mockSessionTransport() {}
-
-mockSessionTransport.prototype = {
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransport,
-                                         Ci.nsIPresentationTCPSessionTransportBuilder]),
-  set callback(callback) {
-    this._callback = callback;
-  },
-  get callback() {
-    return this._callback;
-  },
-  get selfAddress() {
-    return this._selfAddress;
-  },
-  buildTCPSenderTransport: function(transport, listener) {
-    mockSessionTransportOfSender = this;
-    this._listener = listener;
-    this._role = Ci.nsIPresentationService.ROLE_CONTROLLER;
-
-    this._listener.onSessionTransport(this);
-    this._listener = null;
-    this.simulateTransportReady();
-  },
-  buildTCPReceiverTransport: function(description, listener) {
-    mockSessionTransportOfReceiver = this;
-    this._listener = listener;
-    this._role = Ci.nsIPresentationService.ROLE_RECEIVER;
-
-    var addresses = description.QueryInterface(Ci.nsIPresentationChannelDescription)
-                               .tcpAddress;
-    this._selfAddress = {
-      QueryInterface: XPCOMUtils.generateQI([Ci.nsINetAddr]),
-      address: (addresses.length > 0) ?
-                addresses.queryElementAt(0, Ci.nsISupportsCString).data : '',
-      port: description.QueryInterface(Ci.nsIPresentationChannelDescription)
-                       .tcpPort,
-    };
-
-    this._listener.onSessionTransport(this);
-    this._listener = null;
-  },
-  enableDataNotification: function() {
-  },
-  send: function(data) {
-    debug('Send message: ' + data);
-    if (this._role === Ci.nsIPresentationService.ROLE_CONTROLLER) {
-      mockSessionTransportOfReceiver._callback.notifyData(data);
-    }
-    if (this._role === Ci.nsIPresentationService.ROLE_RECEIVER) {
-      mockSessionTransportOfSender._callback.notifyData(data);
-    }
-  },
-  close: function(reason) {
-    sendAsyncMessage('data-transport-closed', reason);
-    this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyTransportClosed(reason);
-    if (this._role === Ci.nsIPresentationService.ROLE_CONTROLLER) {
-      if (mockSessionTransportOfReceiver._callback) {
-        mockSessionTransportOfReceiver._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyTransportClosed(reason);
-      }
-    }
-    else if (this._role === Ci.nsIPresentationService.ROLE_RECEIVER) {
-      if (mockSessionTransportOfSender._callback) {
-        mockSessionTransportOfSender._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyTransportClosed(reason);
-      }
-    }
-  },
-  simulateTransportReady: function() {
-    this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyTransportReady();
-  },
-};
-
-const mockSessionTransportFactory = {
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory]),
-  createInstance: function(aOuter, aIID) {
-    if (aOuter) {
-      throw Components.results.NS_ERROR_NO_AGGREGATION;
-    }
-    var result = new mockSessionTransport();
-    return result.QueryInterface(aIID);
-  }
-}
-
-const mockSocketTransport = {
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsISocketTransport]),
-};
-
 // control channel of sender
 const mockControlChannelOfSender = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannel]),
   set listener(listener) {
     // PresentationControllingInfo::SetControlChannel
     if (listener) {
       debug('set listener for mockControlChannelOfSender without null');
     } else {
@@ -178,17 +44,19 @@ const mockControlChannelOfSender = {
   },
   notifyReconnected: function() {
     // send offer after notifyOpened immediately
     this._listener
         .QueryInterface(Ci.nsIPresentationControlChannelListener)
         .notifyReconnected();
   },
   sendOffer: function(offer) {
-    sendAsyncMessage('offer-sent');
+    Services.tm.mainThread.dispatch(() => {
+      mockControlChannelOfReceiver.onOffer(offer);
+    }, Ci.nsIThread.DISPATCH_NORMAL);
   },
   onAnswer: function(answer) {
     this._listener
         .QueryInterface(Ci.nsIPresentationControlChannelListener)
         .onAnswer(answer);
   },
   launch: function(presentationId, url) {
     sessionId = presentationId;
@@ -242,20 +110,19 @@ const mockControlChannelOfReceiver = {
         .notifyConnected();
   },
   onOffer: function(offer) {
     this._listener
         .QueryInterface(Ci.nsIPresentationControlChannelListener)
         .onOffer(offer);
   },
   sendAnswer: function(answer) {
-    this._listener
-        .QueryInterface(Ci.nsIPresentationSessionTransportCallback)
-        .notifyTransportReady();
-    sendAsyncMessage('answer-sent');
+    Services.tm.mainThread.dispatch(() => {
+      mockControlChannelOfSen