Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 02 Mar 2017 14:06:01 +0100
changeset 394596 5c8f4643a2e93f4860953b904890119b281ba65b
parent 394595 f89a27e84f5e2daa606d23d0721977afb4e0cb77 (current diff)
parent 394532 66535e831760421b270662aa8d0773b0fde7c9f3 (diff)
child 394597 59ebf8130c6d6b8d50cd1170e5f1b401dad2100c
push id1468
push userasasaki@mozilla.com
push dateMon, 05 Jun 2017 19:31:07 +0000
treeherdermozilla-release@0641fc6ee9d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone54.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 mozilla-central to mozilla-inbound
.eslintignore
browser/app/profile/firefox.js
browser/base/content/aboutaccounts/images/fox.png
browser/components/extensions/ext-theme.js
browser/components/extensions/schemas/theme.json
browser/components/extensions/test/browser/browser_ext_themes_chromeparity.js
browser/components/extensions/test/browser/browser_ext_themes_dynamic_updates.js
browser/components/extensions/test/browser/browser_ext_themes_lwtsupport.js
browser/modules/test/.eslintrc.js
browser/modules/test/browser.ini
browser/modules/test/browser_BrowserUITelemetry_buckets.js
browser/modules/test/browser_BrowserUITelemetry_defaults.js
browser/modules/test/browser_BrowserUITelemetry_sidebar.js
browser/modules/test/browser_BrowserUITelemetry_syncedtabs.js
browser/modules/test/browser_ContentSearch.js
browser/modules/test/browser_NetworkPrioritizer.js
browser/modules/test/browser_PermissionUI.js
browser/modules/test/browser_PermissionUI_prompts.js
browser/modules/test/browser_ProcessHangNotifications.js
browser/modules/test/browser_SelfSupportBackend.js
browser/modules/test/browser_SitePermissions.js
browser/modules/test/browser_SitePermissions_combinations.js
browser/modules/test/browser_SitePermissions_expiry.js
browser/modules/test/browser_SitePermissions_tab_urls.js
browser/modules/test/browser_UnsubmittedCrashHandler.js
browser/modules/test/browser_UsageTelemetry.js
browser/modules/test/browser_UsageTelemetry_content.js
browser/modules/test/browser_UsageTelemetry_content_aboutHome.js
browser/modules/test/browser_UsageTelemetry_private_and_restore.js
browser/modules/test/browser_UsageTelemetry_searchbar.js
browser/modules/test/browser_UsageTelemetry_urlbar.js
browser/modules/test/browser_bug1319078.js
browser/modules/test/browser_taskbar_preview.js
browser/modules/test/browser_urlBar_zoom.js
browser/modules/test/contentSearch.js
browser/modules/test/contentSearchBadImage.xml
browser/modules/test/contentSearchSuggestions.sjs
browser/modules/test/contentSearchSuggestions.xml
browser/modules/test/head.js
browser/modules/test/unit/social/.eslintrc.js
browser/modules/test/usageTelemetrySearchSuggestions.sjs
browser/modules/test/usageTelemetrySearchSuggestions.xml
browser/modules/test/xpcshell/.eslintrc.js
browser/modules/test/xpcshell/test_AttributionCode.js
browser/modules/test/xpcshell/test_DirectoryLinksProvider.js
browser/modules/test/xpcshell/test_E10SUtils_nested_URIs.js
browser/modules/test/xpcshell/test_LaterRun.js
browser/modules/test/xpcshell/test_SitePermissions.js
browser/modules/test/xpcshell/xpcshell.ini
browser/themes/linux/browser-lightweightTheme.css
browser/themes/osx/browser-lightweightTheme.css
browser/themes/windows/browser-lightweightTheme.css
gfx/thebes/gfxPrefs.h
js/src/wasm/WasmBaselineCompile.cpp
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testTabStripPrivacyMode.java
modules/libpref/init/all.js
services/sync/modules/engines/clients.js
taskcluster/ci/marionette-harness/kind.yml
taskcluster/scripts/tester/harness-test-linux.sh
testing/config/marionette_harness_test_requirements.txt
testing/marionette/harness/marionette_harness/tests/unit/test_window_type.py
testing/mozharness/scripts/marionette_harness_tests.py
tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-browserjs-globals.js
tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-test-globals.js
--- a/.eslintignore
+++ b/.eslintignore
@@ -57,17 +57,16 @@ browser/app/**
 browser/branding/**/firefox-branding.js
 browser/base/content/browser-social.js
 browser/base/content/nsContextMenu.js
 browser/base/content/sanitizeDialog.js
 browser/base/content/test/general/file_csp_block_all_mixedcontent.html
 browser/base/content/test/urlbar/file_blank_but_not_blank.html
 browser/base/content/newtab/**
 browser/components/downloads/**
-browser/components/privatebrowsing/**
 browser/components/sessionstore/**
 browser/components/tabview/**
 # generated files in cld2
 browser/components/translation/cld2/cld-worker.js
 browser/extensions/pdfjs/content/build**
 browser/extensions/pdfjs/content/web**
 # generated or library files in pocket
 browser/extensions/pocket/content/panels/js/tmpl.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1524,17 +1524,21 @@ pref("toolkit.pageThumbs.minHeight", 190
 
 // Enable speech synthesis
 pref("media.webspeech.synth.enabled", true);
 
 pref("browser.esedbreader.loglevel", "Error");
 
 pref("browser.laterrun.enabled", false);
 
+#ifdef EARLY_BETA_OR_EARLIER
+pref("browser.migrate.automigrate.enabled", true);
+#else
 pref("browser.migrate.automigrate.enabled", false);
+#endif
 // 4 here means the suggestion notification will be automatically
 // hidden the 4th day, so it will actually be shown on 3 different days.
 pref("browser.migrate.automigrate.daysToOfferUndo", 4);
 pref("browser.migrate.automigrate.ui.enabled", true);
 
 // See comments in bug 1340115 on how we got to these numbers.
 pref("browser.migrate.chrome.history.limit", 2000);
 pref("browser.migrate.chrome.history.maxAgeInDays", 180);
deleted file mode 100644
index 83af78d6c48476cbf19f517c4967642fcdcc42fd..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/base/content/browser-addons.js
+++ b/browser/base/content/browser-addons.js
@@ -784,86 +784,8 @@ var LightWeightThemeWebInstaller = {
     if (!uri.schemeIs("https")) {
       return false;
     }
 
     let pm = Services.perms;
     return pm.testPermission(uri, "install") == pm.ALLOW_ACTION;
   }
 };
-
-/*
- * Listen for Lightweight Theme styling changes and update the browser's theme accordingly.
- */
-var LightweightThemeListener = {
-  _modifiedStyles: [],
-
-  init() {
-    XPCOMUtils.defineLazyGetter(this, "styleSheet", function() {
-      for (let i = document.styleSheets.length - 1; i >= 0; i--) {
-        let sheet = document.styleSheets[i];
-        if (sheet.href == "chrome://browser/skin/browser-lightweightTheme.css")
-          return sheet;
-      }
-      return undefined;
-    });
-
-    Services.obs.addObserver(this, "lightweight-theme-styling-update", false);
-    Services.obs.addObserver(this, "lightweight-theme-optimized", false);
-    if (document.documentElement.hasAttribute("lwtheme"))
-      this.updateStyleSheet(document.documentElement.style.backgroundImage);
-  },
-
-  uninit() {
-    Services.obs.removeObserver(this, "lightweight-theme-styling-update");
-    Services.obs.removeObserver(this, "lightweight-theme-optimized");
-  },
-
-  /**
-   * Append the headerImage to the background-image property of all rulesets in
-   * browser-lightweightTheme.css.
-   *
-   * @param headerImage - a string containing a CSS image for the lightweight theme header.
-   */
-  updateStyleSheet(headerImage) {
-    if (!this.styleSheet)
-      return;
-    this.substituteRules(this.styleSheet.cssRules, headerImage);
-  },
-
-  substituteRules(ruleList, headerImage, existingStyleRulesModified = 0) {
-    let styleRulesModified = 0;
-    for (let i = 0; i < ruleList.length; i++) {
-      let rule = ruleList[i];
-      if (rule instanceof Ci.nsIDOMCSSGroupingRule) {
-        // Add the number of modified sub-rules to the modified count
-        styleRulesModified += this.substituteRules(rule.cssRules, headerImage, existingStyleRulesModified + styleRulesModified);
-      } else if (rule instanceof Ci.nsIDOMCSSStyleRule) {
-        if (!rule.style.backgroundImage)
-          continue;
-        let modifiedIndex = existingStyleRulesModified + styleRulesModified;
-        if (!this._modifiedStyles[modifiedIndex])
-          this._modifiedStyles[modifiedIndex] = { backgroundImage: rule.style.backgroundImage };
-
-        rule.style.backgroundImage = this._modifiedStyles[modifiedIndex].backgroundImage + ", " + headerImage;
-        styleRulesModified++;
-      } else {
-        Cu.reportError("Unsupported rule encountered");
-      }
-    }
-    return styleRulesModified;
-  },
-
-  // nsIObserver
-  observe(aSubject, aTopic, aData) {
-    if ((aTopic != "lightweight-theme-styling-update" && aTopic != "lightweight-theme-optimized") ||
-          !this.styleSheet)
-      return;
-
-    if (aTopic == "lightweight-theme-optimized" && aSubject != window)
-      return;
-
-    let themeData = JSON.parse(aData);
-    if (!themeData)
-      return;
-    this.updateStyleSheet("url(" + themeData.headerURL + ")");
-  },
-};
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -6,16 +6,25 @@
 @namespace html url("http://www.w3.org/1999/xhtml");
 @namespace svg url("http://www.w3.org/2000/svg");
 
 :root {
   --identity-popup-expander-width: 38px;
   --panelui-subview-transition-duration: 150ms;
 }
 
+:root:-moz-lwtheme {
+  color: var(--lwt-text-color) !important;
+}
+
+:root:-moz-lwtheme:not([customization-lwtheme]) {
+  background-color: var(--lwt-accent-color) !important;
+  background-image: var(--lwt-header-image) !important;
+}
+
 #main-window:not([chromehidden~="toolbar"]) {
 %ifdef XP_MACOSX
   min-width: 335px;
 %else
   min-width: 300px;
 %endif
 }
 
@@ -440,16 +449,18 @@ toolbar:not(#TabsToolbar) > #personal-bo
 #main-window[inFullscreen="true"] {
   padding-top: 0; /* override drawintitlebar="true" */
 }
 %endif
 
 #browser-bottombox[lwthemefooter="true"] {
   background-repeat: no-repeat;
   background-position: bottom left;
+  background-color: var(--lwt-accent-color);
+  background-image: var(--lwt-header-image);
 }
 
 .menuitem-iconic-tooltip {
   -moz-binding: url("chrome://browser/content/urlbarBindings.xml#menuitem-iconic-tooltip");
 }
 
 /* Hide menu elements intended for keyboard access support */
 #main-menubar[openedwithkey=false] .show-only-for-keyboard {
@@ -970,20 +981,24 @@ notification[value="translation"] {
 toolbarpaletteitem[place="palette"] > #downloads-button[indicator] > .toolbarbutton-badge-stack > image.toolbarbutton-icon {
   display: -moz-box;
 }
 
 toolbarpaletteitem[place="palette"] > #downloads-button[indicator] > #downloads-indicator-anchor {
   display: none;
 }
 
-#downloads-button:-moz-any([progress], [counter], [paused]) #downloads-indicator-icon,
-#downloads-button:not(:-moz-any([progress], [counter], [paused]))
-                                                   #downloads-indicator-progress-area
-{
+#downloads-button.withProgressBar:-moz-any([progress], [counter], [paused]) #downloads-indicator-icon,
+#downloads-button:not(:-moz-any([progress], [counter], [paused])) #downloads-indicator-progress-area {
+  visibility: hidden;
+}
+
+/* Hide elements for another type of progressmeter if it's not in use. */
+#downloads-button.withProgressBar #downloads-indicator-progress-icon,
+#downloads-button:not(.withProgressBar) #downloads-indicator-progress-area {
   visibility: hidden;
 }
 
 /* Combobox dropdown renderer */
 #ContentSelectDropdown > menupopup {
   /* The menupopup itself should always be rendered LTR to ensure the scrollbar aligns with
    * the dropdown arrow on the dropdown widget. If a menuitem is RTL, its style will be set accordingly */
   direction: ltr;
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -22,17 +22,18 @@ Cu.import("resource://gre/modules/Notifi
           GMPInstallManager:false, LightweightThemeManager:false, Log:false,
           LoginManagerParent:false, NewTabUtils:false, PageThumbs:false,
           PluralForm:false, Preferences:false, PrivateBrowsingUtils:false,
           ProcessHangMonitor:false, PromiseUtils:false, ReaderMode:false,
           ReaderParent:false, RecentWindow:false, SessionStore:false,
           ShortcutUtils:false, SimpleServiceDiscovery:false, SitePermissions:false,
           Social:false, TabCrashHandler:false, Task:false, TelemetryStopwatch:false,
           Translation:false, UITour:false, UpdateUtils:false, Weave:false,
-          fxAccounts:false, gDevTools:false, gDevToolsBrowser:false, webrtcUI:false
+          fxAccounts:false, gDevTools:false, gDevToolsBrowser:false, webrtcUI:false,
+          URLBarZoom:false
  */
 
 /**
  * IF YOU ADD OR REMOVE FROM THIS LIST, PLEASE UPDATE THE LIST ABOVE AS WELL.
  * XXX Bug 1325373 is for making eslint detect these automatically.
  */
 [
   ["AboutHome", "resource:///modules/AboutHome.jsm"],
@@ -69,21 +70,22 @@ Cu.import("resource://gre/modules/Notifi
   ["SitePermissions", "resource:///modules/SitePermissions.jsm"],
   ["Social", "resource:///modules/Social.jsm"],
   ["TabCrashHandler", "resource:///modules/ContentCrashHandlers.jsm"],
   ["Task", "resource://gre/modules/Task.jsm"],
   ["TelemetryStopwatch", "resource://gre/modules/TelemetryStopwatch.jsm"],
   ["Translation", "resource:///modules/translation/Translation.jsm"],
   ["UITour", "resource:///modules/UITour.jsm"],
   ["UpdateUtils", "resource://gre/modules/UpdateUtils.jsm"],
+  ["URLBarZoom", "resource:///modules/URLBarZoom.jsm"],
   ["Weave", "resource://services-sync/main.js"],
   ["fxAccounts", "resource://gre/modules/FxAccounts.jsm"],
   ["gDevTools", "resource://devtools/client/framework/gDevTools.jsm"],
   ["gDevToolsBrowser", "resource://devtools/client/framework/gDevTools.jsm"],
-  ["webrtcUI", "resource:///modules/webrtcUI.jsm", ]
+  ["webrtcUI", "resource:///modules/webrtcUI.jsm"],
 ].forEach(([name, resource]) => XPCOMUtils.defineLazyModuleGetter(this, name, resource));
 
 XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
   "resource://gre/modules/SafeBrowsing.jsm");
 
 if (AppConstants.MOZ_CRASHREPORTER) {
   XPCOMUtils.defineLazyModuleGetter(this, "PluginCrashReporter",
     "resource:///modules/ContentCrashHandlers.jsm");
@@ -1127,16 +1129,17 @@ var gBrowserInit = {
     LanguageDetectionListener.init();
     BrowserOnClick.init();
     FeedHandler.init();
     CompactTheme.init();
     AboutPrivateBrowsingListener.init();
     TrackingProtection.init();
     RefreshBlocker.init();
     CaptivePortalWatcher.init();
+    URLBarZoom.init(window);
 
     let mm = window.getGroupMessageManager("browsers");
     mm.loadFrameScript("chrome://browser/content/tab-content.js", true);
     mm.loadFrameScript("chrome://browser/content/content.js", true);
     mm.loadFrameScript("chrome://browser/content/content-UITour.js", true);
     mm.loadFrameScript("chrome://global/content/manifestMessages.js", true);
 
     // initialize observers and listeners
@@ -1362,17 +1365,16 @@ var gBrowserInit = {
     if (AppConstants.E10S_TESTING_ONLY)
       gRemoteTabsUI.init();
 
     // Initialize the full zoom setting.
     // We do this before the session restore service gets initialized so we can
     // apply full zoom settings to tabs restored by the session restore service.
     FullZoom.init();
     PanelUI.init();
-    LightweightThemeListener.init();
 
     UpdateUrlbarSearchSplitterState();
 
     if (!(isBlankPageURL(uriToLoad) || uriToLoad == "about:privatebrowsing") ||
         !focusAndSelectUrlBar()) {
       if (gBrowser.selectedBrowser.isRemoteBrowser) {
         // If the initial browser is remote, in order to optimize for first paint,
         // we'll defer switching focus to that browser until it has painted.
@@ -1699,17 +1701,16 @@ var gBrowserInit = {
       }
 
       if (this.gmpInstallManager) {
         this.gmpInstallManager.uninit();
       }
 
       BrowserOffline.uninit();
       IndexedDBPromptHelper.uninit();
-      LightweightThemeListener.uninit();
       PanelUI.uninit();
       AutoShowBookmarksToolbar.uninit();
     }
 
     // Final window teardown, do this last.
     window.XULBrowserWindow = null;
     window.QueryInterface(Ci.nsIInterfaceRequestor)
           .getInterface(Ci.nsIWebNavigation)
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -8,17 +8,16 @@
 
 <?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/content/places/places.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/content/usercontext/usercontext.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/devtools-browser.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/controlcenter/panel.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/customizableui/panelUI.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
-<?xml-stylesheet href="chrome://browser/skin/browser-lightweightTheme.css" type="text/css"?>
 
 <?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
 <?xul-overlay href="chrome://browser/content/baseMenuOverlay.xul"?>
 <?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
 
 # All DTD information is stored in a separate file so that it can be shared by
 # hiddenWindow.xul.
 #include browser-doctype.inc
--- a/browser/base/content/newtab/newTab.css
+++ b/browser/base/content/newtab/newTab.css
@@ -567,22 +567,22 @@ body.compact #newtab-search-container {
 
 .newtab-customize-panel-item,
 .newtab-customize-complex-option {
   display: block;
   text-align: start;
   background-color: #F9F9F9;
 }
 
-.newtab-customize-panel-item[selected]:-moz-locale-dir(rtl) {
+.newtab-customize-panel-item[selected]:dir(rtl){
   background-position: right 15px center;
 }
 
-.newtab-customize-complex-option:hover > .selectable:not([selected]):-moz-locale-dir(rtl),
-.selectable:not([selected]):hover:-moz-locale-dir(rtl) {
+.newtab-customize-complex-option:hover > .selectable:not([selected]):dir(rtl),
+.selectable:not([selected]):hover:dir(rtl) {
   background-position: right 15px center;
 }
 
 .newtab-customize-panel-item:not([selected]),
 .newtab-customize-panel-subitem:not([selected]){
   color: #7A7A7A;
 }
 
--- a/browser/base/content/test/general/browser_invalid_uri_back_forward_manipulation.js
+++ b/browser/base/content/test/general/browser_invalid_uri_back_forward_manipulation.js
@@ -5,21 +5,22 @@
  * Verify that loading an invalid URI does not clobber a previously-loaded page's history
  * entry, but that the invalid URI gets its own history entry instead. We're checking this
  * using nsIWebNavigation's canGoBack, as well as actually going back and then checking
  * canGoForward.
  */
 add_task(function* checkBackFromInvalidURI() {
   yield pushPrefs(["keyword.enabled", false]);
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:robots", true);
+  info("Loaded about:robots");
+
   gURLBar.value = "::2600";
-  gURLBar.focus();
 
   let promiseErrorPageLoaded = BrowserTestUtils.waitForErrorPage(tab.linkedBrowser);
-  EventUtils.synthesizeKey("VK_RETURN", {});
+  gURLBar.handleCommand();
   yield promiseErrorPageLoaded;
 
   ok(gBrowser.webNavigation.canGoBack, "Should be able to go back");
   if (gBrowser.webNavigation.canGoBack) {
     // Can't use DOMContentLoaded here because the page is bfcached. Can't use pageshow for
     // the error page because it doesn't seem to fire for those.
     let promiseOtherPageLoaded = BrowserTestUtils.waitForEvent(tab.linkedBrowser, "pageshow", false,
       // Be paranoid we *are* actually seeing this other page load, not some kind of race
--- a/browser/base/content/test/urlbar/browser_canonizeURL.js
+++ b/browser/base/content/test/urlbar/browser_canonizeURL.js
@@ -1,34 +1,40 @@
-var pairs = [
-  ["example", "http://www.example.net/"],
-  ["ex-ample", "http://www.ex-ample.net/"],
-  ["  example ", "http://www.example.net/"],
-  [" example/foo ", "http://www.example.net/foo"],
-  [" example/foo bar ", "http://www.example.net/foo%20bar"],
-  ["example.net", "http://example.net/"],
-  ["http://example", "http://example/"],
-  ["example:8080", "http://example:8080/"],
-  ["ex-ample.foo", "http://ex-ample.foo/"],
-  ["example.foo/bar ", "http://example.foo/bar"],
-  ["1.1.1.1", "http://1.1.1.1/"],
-  ["ftp://example", "ftp://example/"],
-  ["ftp.example.bar", "http://ftp.example.bar/"],
-  ["ex ample", Services.search.defaultEngine.getSubmission("ex ample", null, "keyword").uri.spec],
-];
+add_task(function*() {
+  let testcases = [
+    ["example", "http://www.example.net/", { shiftKey: true }],
+    // Check that a direct load is not overwritten by a previous canonization.
+    ["http://example.com/test/", "http://example.com/test/", {}],
+    ["ex-ample", "http://www.ex-ample.net/", { shiftKey: true }],
+    ["  example ", "http://www.example.net/", { shiftKey: true }],
+    [" example/foo ", "http://www.example.net/foo", { shiftKey: true }],
+    [" example/foo bar ", "http://www.example.net/foo%20bar", { shiftKey: true }],
+    ["example.net", "http://example.net/", { shiftKey: true }],
+    ["http://example", "http://example/", { shiftKey: true }],
+    ["example:8080", "http://example:8080/", { shiftKey: true }],
+    ["ex-ample.foo", "http://ex-ample.foo/", { shiftKey: true }],
+    ["example.foo/bar ", "http://example.foo/bar", { shiftKey: true }],
+    ["1.1.1.1", "http://1.1.1.1/", { shiftKey: true }],
+    ["ftp://example", "ftp://example/", { shiftKey: true }],
+    ["ftp.example.bar", "http://ftp.example.bar/", { shiftKey: true }],
+    ["ex ample", Services.search.defaultEngine.getSubmission("ex ample", null, "keyword").uri.spec, { shiftKey: true }],
+  ];
 
-add_task(function*() {
   // Disable autoFill for this test, since it could mess up the results.
   let autoFill = Preferences.get("browser.urlbar.autoFill");
   Preferences.set("browser.urlbar.autoFill", false);
   registerCleanupFunction(() => {
     Preferences.set("browser.urlbar.autoFill", autoFill);
   });
 
-  for (let [inputValue, expectedURL] of pairs) {
+  for (let [inputValue, expectedURL, options] of testcases) {
     let promiseLoad = waitForDocLoadAndStopIt(expectedURL);
     gURLBar.focus();
-    gURLBar.inputField.value = inputValue.slice(0, -1);
-    EventUtils.synthesizeKey(inputValue.slice(-1), {});
-    EventUtils.synthesizeKey("VK_RETURN", { shiftKey: true });
+    if (Object.keys(options).length > 0) {
+      gURLBar.inputField.value = inputValue.slice(0, -1);
+      EventUtils.synthesizeKey(inputValue.slice(-1), {});
+    } else {
+      gURLBar.textValue = inputValue;
+    }
+    EventUtils.synthesizeKey("VK_RETURN", options);
     yield promiseLoad;
   }
 });
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -1148,16 +1148,17 @@ file, You can obtain one at http://mozil
             searchString: this.mController.searchString,
             event
           };
 
           if (this.popup.selectedIndex != 0 || this.gotResultForCurrentQuery) {
             this.maybeCanonizeURL(event, this.value);
             let rv = this.mController.handleEnter(false, event);
             this.handleEnterInstance = null;
+            this.popup.overrideValue = null;
             return rv;
           }
 
           return true;
         ]]></body>
       </method>
 
       <method name="handleDelete">
@@ -1827,16 +1828,17 @@ file, You can obtain one at http://mozil
               // Don't handle this immediately or we could cause a recursive
               // loop where the controller sets popupOpen and re-enters here.
               setTimeout(() => {
                 // Safety check: handle only if the search string didn't change.
                 let { event, searchString } = instance;
                 if (this.input.mController.searchString == searchString) {
                   this.input.maybeCanonizeURL(event, searchString);
                   this.input.mController.handleEnter(false, event);
+                  this.overrideValue = null;
                 }
               }, 0);
             }
           ]]>
         </body>
       </method>
 
       <method name="_onSearchBegin">
@@ -1848,17 +1850,16 @@ file, You can obtain one at http://mozil
           // 1. if a search starts we set selectedIndex to 0 here, and it will
           //    be updated by onResultsAdded. Since selectedIndex is 0,
           //    handleEnter will delay the action if a result didn't arrive yet.
           // 2. if a search doesn't start (for example if autocomplete is
           //    disabled), this won't be called, and the selectedIndex will be
           //    the default -1 value. Then handleEnter will know it should not
           //    delay the action, cause a result wont't ever arrive.
           this.input.controller.setInitiallySelectedIndex(0);
-          this.overrideValue = null;
         ]]></body>
       </method>
 
       <field name="_addonIframe">null</field>
       <field name="_addonIframeOwner">null</field>
       <field name="_addonIframeOverriddenFunctionsByName">{}</field>
 
       <!-- These methods must be overridden and properly handled by the API
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -47,17 +47,16 @@ browser.jar:
         content/browser/abouthealthreport/abouthealth.js      (content/abouthealthreport/abouthealth.js)
         content/browser/abouthealthreport/abouthealth.css     (content/abouthealthreport/abouthealth.css)
 #endif
         content/browser/aboutaccounts/aboutaccounts.xhtml                     (content/aboutaccounts/aboutaccounts.xhtml)
         content/browser/aboutaccounts/aboutaccounts.js                        (content/aboutaccounts/aboutaccounts.js)
         content/browser/aboutaccounts/aboutaccounts.css                       (content/aboutaccounts/aboutaccounts.css)
         content/browser/aboutaccounts/main.css                                (content/aboutaccounts/main.css)
         content/browser/aboutaccounts/normalize.css                           (content/aboutaccounts/normalize.css)
-        content/browser/aboutaccounts/images/fox.png                          (content/aboutaccounts/images/fox.png)
 
 
         content/browser/aboutRobots-icon.png          (content/aboutRobots-icon.png)
         content/browser/aboutRobots-widget-left.png   (content/aboutRobots-widget-left.png)
         content/browser/aboutSocialError.xhtml        (content/aboutSocialError.xhtml)
         content/browser/aboutProviderDirectory.xhtml  (content/aboutProviderDirectory.xhtml)
         content/browser/aboutTabCrashed.css           (content/aboutTabCrashed.css)
         content/browser/aboutTabCrashed.js            (content/aboutTabCrashed.js)
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -442,17 +442,17 @@ CustomizeMode.prototype = {
     undoResetButton.hidden = resetButton.disabled = true;
 
     this._transitioning = true;
 
     Task.spawn(function*() {
       yield this.depopulatePalette();
 
       yield this._doTransition(false);
-      this.removeLWTStyling();
+      this.updateLWTStyling({});
 
       Services.obs.removeObserver(this, "lightweight-theme-window-updated");
 
       if (this.browser.selectedTab == gTab) {
         if (gTab.linkedBrowser.currentURI.spec == "about:blank") {
           closeGlobalTab();
         } else {
           unregisterGlobalTab();
@@ -606,78 +606,26 @@ CustomizeMode.prototype = {
   updateLWTStyling(aData) {
     let docElement = this.document.documentElement;
     if (!aData) {
       let lwt = docElement._lightweightTheme;
       aData = lwt.getData();
     }
     let headerURL = aData && aData.headerURL;
     if (!headerURL) {
-      this.removeLWTStyling();
+      docElement.removeAttribute("customization-lwtheme");
       return;
     }
+    docElement.setAttribute("customization-lwtheme", "true");
 
     let deck = this.document.getElementById("tab-view-deck");
-    let headerImageRef = this._getHeaderImageRef(aData);
-    docElement.setAttribute("customization-lwtheme", "true");
-
     let toolboxRect = this.window.gNavToolbox.getBoundingClientRect();
     let height = toolboxRect.bottom;
-
-    if (AppConstants.platform == "macosx") {
-      let drawingInTitlebar = !docElement.hasAttribute("drawtitle");
-      let titlebar = this.document.getElementById("titlebar");
-      if (drawingInTitlebar) {
-        titlebar.style.backgroundImage = headerImageRef;
-      } else {
-        titlebar.style.removeProperty("background-image");
-      }
-    }
-
-    let limitedBG = "-moz-image-rect(" + headerImageRef + ", 0, 100%, " +
-                    height + ", 0)";
-
-    let ridgeStart = height - 1;
-    let ridgeCenter = (ridgeStart + 1) + "px";
-    let ridgeEnd = (ridgeStart + 2) + "px";
-    ridgeStart = ridgeStart + "px";
-
-    let ridge = "linear-gradient(to bottom, " +
-                                 "transparent " + ridgeStart +
-                                 ", rgba(0,0,0,0.25) " + ridgeStart +
-                                 ", rgba(0,0,0,0.25) " + ridgeCenter +
-                                 ", rgba(255,255,255,0.5) " + ridgeCenter +
-                                 ", rgba(255,255,255,0.5) " + ridgeEnd + ", " +
-                                 "transparent " + ridgeEnd + ")";
-    deck.style.backgroundImage = ridge + ", " + limitedBG;
-
-    /* Remove the background styles from the <window> so we can style it instead. */
-    docElement.style.removeProperty("background-image");
-    docElement.style.removeProperty("background-color");
-  },
-
-  removeLWTStyling() {
-    let affectedNodes = AppConstants.platform == "macosx" ?
-                          ["tab-view-deck", "titlebar"] :
-                          ["tab-view-deck"];
-    for (let id of affectedNodes) {
-      let node = this.document.getElementById(id);
-      node.style.removeProperty("background-image");
-    }
-    let docElement = this.document.documentElement;
-    docElement.removeAttribute("customization-lwtheme");
-    let data = docElement._lightweightTheme.getData();
-    if (data && data.headerURL) {
-      docElement.style.backgroundImage = this._getHeaderImageRef(data);
-      docElement.style.backgroundColor = data.accentcolor || "white";
-    }
-  },
-
-  _getHeaderImageRef(aData) {
-    return "url(\"" + aData.headerURL.replace(/"/g, '\\"') + "\")";
+    deck.style.setProperty("--toolbox-rect-height", `${height}`);
+    deck.style.setProperty("--toolbox-rect-height-with-unit", `${height}px`);
   },
 
   maybeShowTip(aAnchor) {
     let shown = false;
     const kShownPref = "browser.customizemode.tip0.shown";
     try {
       shown = Services.prefs.getBoolPref(kShownPref);
     } catch (ex) {}
@@ -1539,21 +1487,17 @@ CustomizeMode.prototype = {
         this._updateUndoResetButton();
         if (AppConstants.CAN_DRAW_IN_TITLEBAR) {
           this._updateTitlebarButton();
         }
         break;
       case "lightweight-theme-window-updated":
         if (aSubject == this.window) {
           aData = JSON.parse(aData);
-          if (!aData) {
-            this.removeLWTStyling();
-          } else {
-            this.updateLWTStyling(aData);
-          }
+          this.updateLWTStyling(aData);
         }
         break;
     }
   },
 
   _updateTitlebarButton() {
     if (!AppConstants.CAN_DRAW_IN_TITLEBAR) {
       return;
--- a/browser/components/downloads/DownloadsCommon.jsm
+++ b/browser/components/downloads/DownloadsCommon.jsm
@@ -142,16 +142,23 @@ PrefObserver.register({
  */
 this.DownloadsCommon = {
   ATTENTION_NONE: "",
   ATTENTION_SUCCESS: "success",
   ATTENTION_WARNING: "warning",
   ATTENTION_SEVERE: "severe",
 
   /**
+   * This can be used by add-on experiments as a killswitch for the new style
+   * progress indication. This will be removed in bug 1329109 after the new
+   * indicator is released.
+   **/
+  arrowStyledIndicator: true,
+
+  /**
    * Returns an object whose keys are the string names from the downloads string
    * bundle, and whose values are either the translated strings or functions
    * returning formatted strings.
    */
   get strings() {
     let strings = {};
     let sb = Services.strings.createBundle(kDownloadsStringBundleUrl);
     let enumerator = sb.getSimpleEnumeration();
--- a/browser/components/downloads/content/indicator.js
+++ b/browser/components/downloads/content/indicator.js
@@ -212,20 +212,31 @@ const DownloadsIndicatorView = {
    * Prepares the downloads indicator to be displayed.
    */
   ensureInitialized() {
     if (this._initialized) {
       return;
     }
     this._initialized = true;
 
+    this._setIndicatorType();
     window.addEventListener("unload", this.onWindowUnload);
     DownloadsCommon.getIndicatorData(window).addView(this);
   },
 
+  _setIndicatorType() {
+    // We keep a killerswitch for old-styled progressbar for now. Corresponding
+    // css class is added here to reflect the type chosen for showing progress.
+    let node = CustomizableUI.getWidget("downloads-button")
+                             .forWindow(window).node;
+
+    node.classList.toggle("withProgressBar",
+                          !DownloadsCommon.arrowStyledIndicator);
+  },
+
   /**
    * Frees the internal resources related to the indicator.
    */
   ensureTerminated() {
     if (!this._initialized) {
       return;
     }
     this._initialized = false;
@@ -406,31 +417,50 @@ const DownloadsIndicatorView = {
       // XBL binding isn't applied if the element is invisible for any reason.
       this._indicatorCounter.setAttribute("value", aValue);
     }
     return aValue;
   },
   _counter: null,
 
   /**
-   * Progress indication to display, from 0 to 100, or -1 if unknown.  The
-   * progress bar is hidden if the current progress is unknown and no status
-   * text is set in the "counter" property.
+   * Progress indication to display, from 0 to 100, or -1 if unknown.
+   * Bar-type:
+   *   The progress bar is hidden if the current progress is unknown and no
+   *   status text is set in the "counter" property.
+   * Arrow-type:
+   *   progress is not visible if the current progress is unknown.
    */
   set percentComplete(aValue) {
+    // For arrow type only:
+    // We show portion of the success icon in propotional with the download
+    // progress as an indicator. the PROGRESS_ICON_EMPTY_HEIGHT_PERCENT and
+    // PROGRESS_ICON_FULL_HEIGHT_PERCENT correspond to how much portion of the
+    // icon should be displayed in 0% and 100%.
+    const PROGRESS_ICON_EMPTY_HEIGHT_PERCENT = 35;
+    const PROGRESS_ICON_FULL_HEIGHT_PERCENT = 87;
+
     if (!this._operational) {
       return this._percentComplete;
     }
 
     if (this._percentComplete !== aValue) {
       this._percentComplete = aValue;
-      if (this._percentComplete >= 0)
+      this._refreshAttention();
+
+      if (this._percentComplete >= 0) {
         this.indicator.setAttribute("progress", "true");
-      else
+        this._progressIcon.style.height = (this._percentComplete *
+          (PROGRESS_ICON_FULL_HEIGHT_PERCENT -
+           PROGRESS_ICON_EMPTY_HEIGHT_PERCENT) / 100 +
+           PROGRESS_ICON_EMPTY_HEIGHT_PERCENT) + "%";
+      } else {
         this.indicator.removeAttribute("progress");
+        this._progressIcon.style.height = "0";
+      }
       // We have to set the attribute instead of using the property because the
       // XBL binding isn't applied if the element is invisible for any reason.
       this._indicatorProgress.setAttribute("value", Math.max(aValue, 0));
     }
     return aValue;
   },
   _percentComplete: null,
 
@@ -458,39 +488,49 @@ const DownloadsIndicatorView = {
 
   /**
    * Set when the indicator should draw user attention to itself.
    */
   set attention(aValue) {
     if (!this._operational) {
       return this._attention;
     }
-
     if (this._attention != aValue) {
       this._attention = aValue;
+      this._refreshAttention();
+    }
+    return this._attention;
+  },
 
-      // Check if the downloads button is in the menu panel, to determine which
-      // button needs to get a badge.
-      let widgetGroup = CustomizableUI.getWidget("downloads-button");
-      let inMenu = widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL;
+  _refreshAttention() {
+    // Check if the downloads button is in the menu panel, to determine which
+    // button needs to get a badge.
+    let widgetGroup = CustomizableUI.getWidget("downloads-button");
+    let inMenu = widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL;
 
-      if (aValue == DownloadsCommon.ATTENTION_NONE) {
-        this.indicator.removeAttribute("attention");
-        if (inMenu) {
-          gMenuButtonBadgeManager.removeBadge(gMenuButtonBadgeManager.BADGEID_DOWNLOAD);
-        }
-      } else {
-        this.indicator.setAttribute("attention", aValue);
-        if (inMenu) {
-          let badgeClass = "download-" + aValue;
-          gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_DOWNLOAD, badgeClass);
-        }
+    // For arrow-Styled indicator, suppress success attention if we have
+    // progress in toolbar
+    let suppressAttention = DownloadsCommon.arrowStyledIndicator && !inMenu &&
+      this._attention == DownloadsCommon.ATTENTION_SUCCESS &&
+      this._percentComplete >= 0;
+
+    if (suppressAttention || this._attention == DownloadsCommon.ATTENTION_NONE) {
+      this.indicator.removeAttribute("attention");
+      if (inMenu) {
+        gMenuButtonBadgeManager.removeBadge(
+                                      gMenuButtonBadgeManager.BADGEID_DOWNLOAD);
+      }
+    } else {
+      this.indicator.setAttribute("attention", this._attention);
+      if (inMenu) {
+        let badgeClass = "download-" + this._attention;
+        gMenuButtonBadgeManager.addBadge(
+                          gMenuButtonBadgeManager.BADGEID_DOWNLOAD, badgeClass);
       }
     }
-    return aValue;
   },
   _attention: DownloadsCommon.ATTENTION_NONE,
 
   //////////////////////////////////////////////////////////////////////////////
   //// User interface event functions
 
   onWindowUnload() {
     // This function is registered as an event listener, we can't use "this".
@@ -534,16 +574,17 @@ const DownloadsIndicatorView = {
     if (handled) {
       aEvent.preventDefault();
     }
   },
 
   _indicator: null,
   __indicatorCounter: null,
   __indicatorProgress: null,
+  __progressIcon: null,
 
   /**
    * Returns a reference to the main indicator element, or null if the element
    * is not present in the browser window yet.
    */
   get indicator() {
     if (this._indicator) {
       return this._indicator;
@@ -571,23 +612,29 @@ const DownloadsIndicatorView = {
       (this.__indicatorCounter = document.getElementById("downloads-indicator-counter"));
   },
 
   get _indicatorProgress() {
     return this.__indicatorProgress ||
       (this.__indicatorProgress = document.getElementById("downloads-indicator-progress"));
   },
 
+  get _progressIcon() {
+    return this.__progressIcon ||
+      (this.__progressIcon = document.getElementById("downloads-indicator-progress-icon"));
+  },
+
   get notifier() {
     return this._notifier ||
       (this._notifier = document.getElementById("downloads-notification-anchor"));
   },
 
   _onCustomizedAway() {
     this._indicator = null;
+    this.__progressIcon = null;
     this.__indicatorCounter = null;
     this.__indicatorProgress = null;
   },
 
   afterCustomize() {
     // If the cached indicator is not the one currently in the document,
     // invalidate our references
     if (this._indicator != document.getElementById("downloads-button")) {
--- a/browser/components/downloads/content/indicatorOverlay.xul
+++ b/browser/components/downloads/content/indicatorOverlay.xul
@@ -20,17 +20,19 @@
        downloads-button. -->
   <toolbarbutton id="downloads-button" indicator="true">
     <!-- The panel's anchor area is smaller than the outer button, but must
          always be visible and must not move or resize when the indicator
          state changes, otherwise the panel could change its position or lose
          its arrow unexpectedly. -->
     <stack id="downloads-indicator-anchor"
            consumeanchor="downloads-button">
+      <vbox id="downloads-indicator-icon">
+        <vbox id="downloads-indicator-progress-icon"/>
+      </vbox>
       <vbox id="downloads-indicator-progress-area" pack="center">
         <description id="downloads-indicator-counter"/>
         <progressmeter id="downloads-indicator-progress" class="plain"
                        min="0" max="100"/>
       </vbox>
-      <vbox id="downloads-indicator-icon"/>
     </stack>
   </toolbarbutton>
 </overlay>
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -27,17 +27,17 @@ XPCOMUtils.defineLazyServiceGetter(this,
           FileUtils:false, FormValidationHandler:false, Integration:false,
           LightweightThemeManager:false, LoginHelper:false, LoginManagerParent:false,
           NetUtil:false, NewTabMessages:false, NewTabUtils:false, OS:false,
           PageThumbs:false, PdfJs:false, PermissionUI:false, PlacesBackups:false,
           PlacesUtils:false, PluralForm:false, PrivateBrowsingUtils:false,
           ProcessHangMonitor:false, ReaderParent:false, RecentWindow:false,
           RemotePrompt:false, SelfSupportBackend:false, SessionStore:false,
           ShellService:false, SimpleServiceDiscovery:false, TabCrashHandler:false,
-          Task:false, UITour:false, URLBarZoom:false, WebChannel:false,
+          Task:false, UITour:false, WebChannel:false,
           WindowsRegistry:false, webrtcUI:false */
 
 /**
  * IF YOU ADD OR REMOVE FROM THIS LIST, PLEASE UPDATE THE LIST ABOVE AS WELL.
  * XXX Bug 1325373 is for making eslint detect these automatically.
  */
 
 [
@@ -81,17 +81,16 @@ XPCOMUtils.defineLazyServiceGetter(this,
   ["RemotePrompt", "resource:///modules/RemotePrompt.jsm"],
   ["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"],
   ["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");
@@ -626,17 +625,16 @@ 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.INSTALL_COMPACT_THEMES) {
       let vendorShortName = gBrandBundle.GetStringFromName("vendorShortName");
--- a/browser/components/privatebrowsing/content/aboutPrivateBrowsing.js
+++ b/browser/components/privatebrowsing/content/aboutPrivateBrowsing.js
@@ -13,17 +13,17 @@ const FAVICON_PRIVACY = "chrome://browse
 
 var stringBundle = Services.strings.createBundle(
                     "chrome://browser/locale/aboutPrivateBrowsing.properties");
 
 var prefBranch = Services.prefs.getBranch("privacy.trackingprotection.");
 var prefObserver = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                         Ci.nsISupportsWeakReference]),
- observe: function () {
+ observe() {
    let tpSubHeader = document.getElementById("tpSubHeader");
    let tpToggle = document.getElementById("tpToggle");
    let tpButton = document.getElementById("tpButton");
    let title = document.getElementById("title");
    let titleTracking = document.getElementById("titleTracking");
    let globalTrackingEnabled = prefBranch.getBoolPref("enabled");
    let trackingEnabled = globalTrackingEnabled ||
                          prefBranch.getBoolPref("pbmode.enabled");
@@ -37,30 +37,30 @@ var prefObserver = {
 };
 prefBranch.addObserver("pbmode.enabled", prefObserver, true);
 prefBranch.addObserver("enabled", prefObserver, true);
 
 function setFavIcon(url) {
  document.getElementById("favicon").setAttribute("href", url);
 }
 
-document.addEventListener("DOMContentLoaded", function () {
+document.addEventListener("DOMContentLoaded", function() {
  if (!PrivateBrowsingUtils.isContentWindowPrivate(window)) {
    document.documentElement.classList.remove("private");
    document.documentElement.classList.add("normal");
    document.title = stringBundle.GetStringFromName("title.normal");
    document.getElementById("favicon")
            .setAttribute("href", FAVICON_QUESTION);
    document.getElementById("startPrivateBrowsing")
            .addEventListener("command", openPrivateWindow);
    return;
  }
 
  let tpToggle = document.getElementById("tpToggle");
- document.getElementById("tpButton").addEventListener('click', () => {
+ document.getElementById("tpButton").addEventListener("click", () => {
    tpToggle.click();
  });
 
  document.title = stringBundle.GetStringFromName("title.head");
  document.getElementById("favicon")
          .setAttribute("href", FAVICON_PRIVACY);
  tpToggle.addEventListener("change", toggleTrackingProtection);
  document.getElementById("startTour")
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_DownloadLastDirWithCPS.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_DownloadLastDirWithCPS.js
@@ -52,17 +52,17 @@ async function runTest() {
   let dir3 = newDirectory();
 
   let uri1 = Services.io.newURI("http://test1.com/");
   let uri2 = Services.io.newURI("http://test2.com/");
   let uri3 = Services.io.newURI("http://test3.com/");
   let uri4 = Services.io.newURI("http://test4.com/");
 
   // cleanup functions registration
-  registerCleanupFunction(function () {
+  registerCleanupFunction(function() {
     Services.prefs.clearUserPref("browser.download.lastDir.savePerSite");
     Services.prefs.clearUserPref("browser.download.lastDir");
     [dir1, dir2, dir3].forEach(dir => dir.remove(true));
     win.close();
     pbWin.close();
   });
 
   function checkDownloadLastDir(gDownloadLastDir, aLastDir) {
@@ -168,18 +168,16 @@ async function runTest() {
      "uri3 should return dir3"); // set in CPS
   is((await getFile(downloadLastDir, uri4)).path, dir2.path,
      "uri4 should return dir2"); // fallback
 
   await clearHistoryAndWait();
 
   // check clearHistory removes all data
   is(downloadLastDir.file, null, "clearHistory removes all data");
-  //is(Services.contentPrefs.hasPref(uri1, "browser.download.lastDir", null),
-  //   false, "LastDir preference should be absent");
   is((await getFile(downloadLastDir, uri1)), null, "uri1 should point to null");
   is((await getFile(downloadLastDir, uri2)), null, "uri2 should point to null");
   is((await getFile(downloadLastDir, uri3)), null, "uri3 should point to null");
   is((await getFile(downloadLastDir, uri4)), null, "uri4 should point to null");
 
   await setFile(downloadLastDir, null, tmpDir);
 
   // check data set outside PB mode is remembered
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about.js
@@ -13,48 +13,48 @@ function* openAboutPrivateBrowsing() {
   return { win, tab };
 }
 
 /**
  * Clicks the given link and checks this opens a new tab with the given URI.
  */
 function* testLinkOpensTab({ win, tab, elementId, expectedUrl }) {
   let newTabPromise = BrowserTestUtils.waitForNewTab(win.gBrowser, expectedUrl);
-  yield ContentTask.spawn(tab, { elementId }, function* ({ elementId }) {
-    content.document.getElementById(elementId).click();
+  yield ContentTask.spawn(tab, elementId, function* (elemId) {
+    content.document.getElementById(elemId).click();
   });
   let newTab = yield newTabPromise;
   ok(true, `Clicking ${elementId} opened ${expectedUrl} in a new tab.`);
   yield BrowserTestUtils.removeTab(newTab);
 }
 
 /**
  * Clicks the given link and checks this opens the given URI in the same tab.
  *
  * This function does not return to the previous page.
  */
 function* testLinkOpensUrl({ win, tab, elementId, expectedUrl }) {
   let loadedPromise = BrowserTestUtils.browserLoaded(tab);
-  yield ContentTask.spawn(tab, { elementId }, function* ({ elementId }) {
-    content.document.getElementById(elementId).click();
+  yield ContentTask.spawn(tab, elementId, function* (elemId) {
+    content.document.getElementById(elemId).click();
   });
   yield loadedPromise;
   is(tab.currentURI.spec, expectedUrl,
      `Clicking ${elementId} opened ${expectedUrl} in the same tab.`);
 }
 
 /**
  * Tests the links in "about:privatebrowsing".
  */
 add_task(function* test_links() {
   // Use full version and change the remote URLs to prevent network access.
   Services.prefs.setCharPref("app.support.baseURL", "https://example.com/");
   Services.prefs.setCharPref("privacy.trackingprotection.introURL",
                              "https://example.com/tour");
-  registerCleanupFunction(function () {
+  registerCleanupFunction(function() {
     Services.prefs.clearUserPref("privacy.trackingprotection.introURL");
     Services.prefs.clearUserPref("app.support.baseURL");
   });
 
   let { win, tab } = yield openAboutPrivateBrowsing();
 
   yield testLinkOpensTab({ win, tab,
     elementId: "learnMore",
@@ -72,29 +72,29 @@ add_task(function* test_links() {
 /**
  * Tests the action to disable and re-enable Tracking Protection in
  * "about:privatebrowsing".
  */
 add_task(function* test_toggleTrackingProtection() {
   // Use tour version but disable Tracking Protection.
   Services.prefs.setBoolPref("privacy.trackingprotection.pbmode.enabled",
                              true);
-  registerCleanupFunction(function () {
+  registerCleanupFunction(function() {
     Services.prefs.clearUserPref("privacy.trackingprotection.pbmode.enabled");
   });
 
   let { win, tab } = yield openAboutPrivateBrowsing();
 
   // Set up the observer for the preference change before triggering the action.
   let prefBranch =
       Services.prefs.getBranch("privacy.trackingprotection.pbmode.");
   let waitForPrefChanged = () => new Promise(resolve => {
     let prefObserver = {
       QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
-      observe: function () {
+      observe() {
         prefBranch.removeObserver("enabled", prefObserver);
         resolve();
       },
     };
     prefBranch.addObserver("enabled", prefObserver, false);
   });
 
   let promisePrefChanged = waitForPrefChanged();
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cache.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cache.js
@@ -17,17 +17,17 @@ Cc["@mozilla.org/moz/jssubscript-loader;
 var Sanitizer = tmp.Sanitizer;
 
 function test() {
 
   waitForExplicitFinish();
 
   sanitizeCache();
 
-  let nrEntriesR1 = getStorageEntryCount("regular", function(nrEntriesR1) {
+  getStorageEntryCount("regular", function(nrEntriesR1) {
     is(nrEntriesR1, 0, "Disk cache reports 0KB and has no entries");
 
     get_cache_for_private_window();
   });
 }
 
 function cleanup() {
   let prefs = Services.prefs.getBranch("privacy.cpd.");
@@ -80,35 +80,33 @@ function getStorageEntryCount(device, go
     storage = cs.diskCacheStorage(LoadContextInfo.default, false);
     break;
   default:
     throw "Unknown device " + device + " at getStorageEntryCount";
   }
 
   var visitor = {
     entryCount: 0,
-    onCacheStorageInfo: function (aEntryCount, aConsumption) {
+    onCacheStorageInfo(aEntryCount, aConsumption) {
     },
-    onCacheEntryInfo: function(uri)
-    {
+    onCacheEntryInfo(uri) {
       var urispec = uri.asciiSpec;
       info(device + ":" + urispec + "\n");
       if (urispec.match(/^http:\/\/example.org\//))
         ++this.entryCount;
     },
-    onCacheEntryVisitCompleted: function()
-    {
+    onCacheEntryVisitCompleted() {
       goon(this.entryCount);
     }
   };
 
   storage.asyncVisitStorage(visitor, true);
 }
 
-function get_cache_for_private_window () {
+function get_cache_for_private_window() {
   let win = whenNewWindowLoaded({private: true}, function() {
 
     executeSoon(function() {
 
       ok(true, "The private window got loaded");
 
       let tab = win.gBrowser.addTab("http://example.org");
       win.gBrowser.selectedTab = tab;
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_certexceptionsui.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_certexceptionsui.js
@@ -2,19 +2,19 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // This test makes sure that certificate exceptions UI behaves correctly
 // in private browsing windows, based on whether it's opened from the prefs
 // window or from the SSL error page (see bug 461627).
 
 function test() {
-  const EXCEPTIONS_DLG_URL = 'chrome://pippki/content/exceptionDialog.xul';
-  const EXCEPTIONS_DLG_FEATURES = 'chrome,centerscreen';
-  const INVALID_CERT_LOCATION = 'https://nocert.example.com/';
+  const EXCEPTIONS_DLG_URL = "chrome://pippki/content/exceptionDialog.xul";
+  const EXCEPTIONS_DLG_FEATURES = "chrome,centerscreen";
+  const INVALID_CERT_LOCATION = "https://nocert.example.com/";
   waitForExplicitFinish();
 
   // open a private browsing window
   var pbWin = OpenBrowserWindow({private: true});
   pbWin.addEventListener("load", function() {
     doTest();
   }, {once: true});
 
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent.js
@@ -13,76 +13,76 @@
 
 add_task(function* setup() {
   yield SpecialPowers.pushPrefEnv({
     set: [["dom.ipc.processCount", 1]]
   });
 });
 
 add_task(function* test() {
-  let prefix = 'http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent_page.html';
+  let prefix = "http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent_page.html";
 
   function getElts(browser) {
-    return browser.contentTitle.split('|');
-  };
+    return browser.contentTitle.split("|");
+  }
 
   // Step 1
   let non_private_browser = gBrowser.selectedBrowser;
-  non_private_browser.loadURI(prefix + '?action=set&name=test&value=value&initial=true');
+  non_private_browser.loadURI(prefix + "?action=set&name=test&value=value&initial=true");
   yield BrowserTestUtils.browserLoaded(non_private_browser);
 
 
   // Step 2
   let private_window = yield BrowserTestUtils.openNewBrowserWindow({ private : true });
   let private_browser = private_window.getBrowser().selectedBrowser;
-  private_browser.loadURI(prefix + '?action=set&name=test2&value=value2');
+  private_browser.loadURI(prefix + "?action=set&name=test2&value=value2");
   yield BrowserTestUtils.browserLoaded(private_browser);
 
 
   // Step 3
-  non_private_browser.loadURI(prefix + '?action=get&name=test2');
+  non_private_browser.loadURI(prefix + "?action=get&name=test2");
   yield BrowserTestUtils.browserLoaded(non_private_browser);
   let elts = yield getElts(non_private_browser);
-  isnot(elts[0], 'value2', "public window shouldn't see private storage");
-  is(elts[1], '1', "public window should only see public items");
+  isnot(elts[0], "value2", "public window shouldn't see private storage");
+  is(elts[1], "1", "public window should only see public items");
 
 
   // Step 4
-  private_browser.loadURI(prefix + '?action=get&name=test');
+  private_browser.loadURI(prefix + "?action=get&name=test");
   yield BrowserTestUtils.browserLoaded(private_browser);
   elts = yield getElts(private_browser);
-  isnot(elts[0], 'value', "private window shouldn't see public storage");
-  is(elts[1], '1', "private window should only see private items");
+  isnot(elts[0], "value", "private window shouldn't see public storage");
+  is(elts[1], "1", "private window should only see private items");
 
 
   // Reopen the private window again, without privateBrowsing, which should clear the
   // the private storage.
   private_window.close();
   private_window = yield BrowserTestUtils.openNewBrowserWindow({ private : false });
   private_browser = null;
   yield new Promise(resolve => Cu.schedulePreciseGC(resolve));
   private_browser = private_window.getBrowser().selectedBrowser;
 
-  private_browser.loadURI(prefix + '?action=get&name=test2');
+  private_browser.loadURI(prefix + "?action=get&name=test2");
   yield BrowserTestUtils.browserLoaded(private_browser);
   elts = yield getElts(private_browser);
-  isnot(elts[0], 'value2', "public window shouldn't see cleared private storage");
-  is(elts[1], '1', "public window should only see public items");
+  isnot(elts[0], "value2", "public window shouldn't see cleared private storage");
+  is(elts[1], "1", "public window should only see public items");
 
 
   // Making it private again should clear the storage and it shouldn't
   // be able to see the old private storage as well.
   private_window.close();
   private_window = yield BrowserTestUtils.openNewBrowserWindow({ private : true });
   private_browser = null;
   yield new Promise(resolve => Cu.schedulePreciseGC(resolve));
   private_browser = private_window.getBrowser().selectedBrowser;
 
-  private_browser.loadURI(prefix + '?action=set&name=test3&value=value3');
+  private_browser.loadURI(prefix + "?action=set&name=test3&value=value3");
   yield BrowserTestUtils.browserLoaded(private_browser);
   elts = yield getElts(private_browser);
-  is(elts[1], '1', "private window should only see new private items");
+  is(elts[1], "1", "private window should only see new private items");
 
   // Cleanup.
-  non_private_browser.loadURI(prefix + '?final=true');
+  non_private_browser.loadURI(prefix + "?final=true");
   yield BrowserTestUtils.browserLoaded(non_private_browser);
   private_window.close();
 });
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent_page.html
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent_page.html
@@ -1,33 +1,33 @@
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
 <script type="text/javascript">
-  var oGetVars = {};  
-      
-  if (window.location.search.length > 1) {  
+  var oGetVars = {};
+
+  if (window.location.search.length > 1) {
     for (var aItKey, nKeyId = 0, aCouples = window.location.search.substr(1).split("&");
          nKeyId < aCouples.length;
-         nKeyId++) {  
-      aItKey = aCouples[nKeyId].split("=");  
-      oGetVars[unescape(aItKey[0])] = aItKey.length > 1 ? unescape(aItKey[1]) : "";  
-    }  
+         nKeyId++) {
+      aItKey = aCouples[nKeyId].split("=");
+      oGetVars[unescape(aItKey[0])] = aItKey.length > 1 ? unescape(aItKey[1]) : "";
+    }
   }
 
-  if (oGetVars.initial == 'true') {
+  if (oGetVars.initial == "true") {
     localStorage.clear();
   }
 
-  if (oGetVars.action == 'set') {
+  if (oGetVars.action == "set") {
     localStorage.setItem(oGetVars.name, oGetVars.value);
     document.title = localStorage.getItem(oGetVars.name) + "|" + localStorage.length;
-  } else if (oGetVars.action == 'get') {
+  } else if (oGetVars.action == "get") {
     document.title = localStorage.getItem(oGetVars.name) + "|" + localStorage.length;
   }
 
-  if (oGetVars.final == 'true') {
+  if (oGetVars.final == "true") {
     localStorage.clear();
   }
 </script>
 </head>
 <body>
 </body>
 </html>
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_context_and_chromeFlags.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_context_and_chromeFlags.js
@@ -25,18 +25,18 @@ function assertWindowIsPrivate(win) {
               "Should have the private window chrome flag");
   }
 
   let loadContext = winDocShell.QueryInterface(Ci.nsILoadContext);
   Assert.ok(loadContext.usePrivateBrowsing,
             "The parent window should be using private browsing");
 
   return ContentTask.spawn(win.gBrowser.selectedBrowser, null, function*() {
-    let loadContext = docShell.QueryInterface(Ci.nsILoadContext);
-    Assert.ok(loadContext.usePrivateBrowsing,
+    let contentLoadContext = docShell.QueryInterface(Ci.nsILoadContext);
+    Assert.ok(contentLoadContext.usePrivateBrowsing,
               "Content docShell should be using private browsing");
   });
 }
 
 /**
  * Tests that chromeFlags bits and the nsILoadContext.usePrivateBrowsing
  * attribute are properly set when opening a new private browsing
  * window.
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_crh.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_crh.js
@@ -1,25 +1,25 @@
 /* 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/. */
 
-// This test makes sure that the Clear Recent History menu item and command 
+// This test makes sure that the Clear Recent History menu item and command
 // is disabled inside the private browsing mode.
 
 add_task(function* test() {
   function checkDisableOption(aPrivateMode, aWindow) {
     let crhCommand = aWindow.document.getElementById("Tools:Sanitize");
     ok(crhCommand, "The clear recent history command should exist");
 
     is(PrivateBrowsingUtils.isWindowPrivate(aWindow), aPrivateMode,
       "PrivateBrowsingUtils should report the correct per-window private browsing status");
     is(crhCommand.hasAttribute("disabled"), aPrivateMode,
       "Clear Recent History command should be disabled according to the private browsing mode");
-  };
+  }
 
   let testURI = "http://mochi.test:8888/";
 
   let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private: true});
   let privateBrowser = privateWin.gBrowser.selectedBrowser;
   privateBrowser.loadURI(testURI);
   yield BrowserTestUtils.browserLoaded(privateBrowser);
 
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir.js
@@ -26,17 +26,17 @@ function test() {
   let dir1 = newDirectory();
   let dir2 = newDirectory();
   let dir3 = newDirectory();
   let file1 = newFileInDirectory(dir1);
   let file2 = newFileInDirectory(dir2);
   let file3 = newFileInDirectory(dir3);
 
   // cleanup functions registration
-  registerCleanupFunction(function () {
+  registerCleanupFunction(function() {
     Services.prefs.clearUserPref("browser.download.lastDir");
     [dir1, dir2, dir3].forEach(dir => dir.remove(true));
     MockFilePicker.cleanup();
   });
   prefs.setComplexValue("lastDir", Ci.nsIFile, tmpDir);
 
   function testOnWindow(aPrivate, aCallback) {
     whenNewWindowLoaded({private: aPrivate}, function(win) {
@@ -53,17 +53,17 @@ function test() {
        "LastDir should be the expected display dir");
     // Check gDownloadLastDir value.
     is(gDownloadLastDir.file.path, aDisplayDir.path,
        "gDownloadLastDir should be the expected display dir");
 
     MockFilePicker.returnFiles = [aFile];
     MockFilePicker.displayDirectory = null;
 
-    launcher.saveDestinationAvailable = function (file) {
+    launcher.saveDestinationAvailable = function(file) {
       ok(!!file, "promptForSaveToFile correctly returned a file");
 
       // File picker should start with expected display dir.
       is(MockFilePicker.displayDirectory.path, aDisplayDir.path,
         "File picker should start with browser.download.lastDir");
       // browser.download.lastDir should be modified on not private windows
       is(prefs.getComplexValue("lastDir", Ci.nsIFile).path, aLastDir.path,
          "LastDir should be the expected last dir");
@@ -76,18 +76,18 @@ function test() {
       aCallback();
     };
 
     launcherDialog.promptForSaveToFileAsync(launcher, aWin, null, null, null);
   }
 
   testOnWindow(false, function(win, downloadDir) {
     testDownloadDir(win, downloadDir, file1, tmpDir, dir1, dir1, function() {
-      testOnWindow(true, function(win, downloadDir) {
-        testDownloadDir(win, downloadDir, file2, dir1, dir1, dir2, function() {
-          testOnWindow(false, function(win, downloadDir) {
-            testDownloadDir(win, downloadDir, file3, dir1, dir3, dir3, finish);
+      testOnWindow(true, function(win1, downloadDir1) {
+        testDownloadDir(win1, downloadDir1, file2, dir1, dir1, dir2, function() {
+          testOnWindow(false, function(win2, downloadDir2) {
+            testDownloadDir(win2, downloadDir2, file3, dir1, dir3, dir3, finish);
           });
         });
       });
     });
   });
 }
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_c.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_c.js
@@ -21,17 +21,17 @@ function test() {
   let dir1 = newDirectory();
   let dir2 = newDirectory();
   let dir3 = newDirectory();
   let file1 = newFileInDirectory(dir1);
   let file2 = newFileInDirectory(dir2);
   let file3 = newFileInDirectory(dir3);
 
   // cleanup function registration
-  registerCleanupFunction(function () {
+  registerCleanupFunction(function() {
     Services.prefs.clearUserPref("browser.download.lastDir");
     [dir1, dir2, dir3].forEach(dir => dir.remove(true));
     MockFilePicker.cleanup();
     validateFileName = validateFileNameToRestore;
   });
 
   // Overwrite validateFileName to validate everything
   validateFileName = foo => foo;
@@ -78,18 +78,18 @@ function test() {
       gDownloadLastDir.cleanupPrivateFile();
       aWin.close();
       aCallback();
     }).then(null, function() { ok(false); });
   }
 
   testOnWindow(false, function(win, downloadDir) {
     testDownloadDir(win, downloadDir, file1, tmpDir, dir1, dir1, function() {
-      testOnWindow(true, function(win, downloadDir) {
-        testDownloadDir(win, downloadDir, file2, dir1, dir1, dir2, function() {
-          testOnWindow(false, function(win, downloadDir) {
-            testDownloadDir(win, downloadDir, file3, dir1, dir3, dir3, finish);
+      testOnWindow(true, function(win1, downloadDir1) {
+        testDownloadDir(win1, downloadDir1, file2, dir1, dir1, dir2, function() {
+          testOnWindow(false, function(win2, downloadDir2) {
+            testDownloadDir(win2, downloadDir2, file3, dir1, dir3, dir3, finish);
           });
         });
       });
     });
   });
 }
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_toggle.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_toggle.js
@@ -5,17 +5,17 @@ Cu.import("resource://gre/modules/Downlo
  * Tests how the browser remembers the last download folder
  * from download to download, with a particular emphasis
  * on how it behaves when private browsing windows open.
  */
 add_task(function* test_downloads_last_dir_toggle() {
   let tmpDir = FileUtils.getDir("TmpD", [], true);
   let dir1 = newDirectory();
 
-  registerCleanupFunction(function () {
+  registerCleanupFunction(function() {
     Services.prefs.clearUserPref("browser.download.lastDir");
     dir1.remove(true);
   });
 
   let win = yield BrowserTestUtils.openNewBrowserWindow();
   let gDownloadLastDir = new DownloadLastDir(win);
   is(typeof gDownloadLastDir, "object",
      "gDownloadLastDir should be a valid object");
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_favicon.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_favicon.js
@@ -157,17 +157,17 @@ function waitOnFaviconLoaded(aFaviconURL
         }
       },
     };
 
     PlacesUtils.history.addObserver(observer, false);
   });
 }
 
-function* assignCookies(aBrowser, aURL, aCookieValue){
+function* assignCookies(aBrowser, aURL, aCookieValue) {
   let tabInfo = yield openTab(aBrowser, aURL);
 
   yield ContentTask.spawn(tabInfo.browser, aCookieValue, function* (value) {
     content.document.cookie = value;
   });
 
   yield BrowserTestUtils.removeTab(tabInfo.tab);
 }
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_geoprompt.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_geoprompt.js
@@ -12,32 +12,32 @@ add_task(function* test() {
   function checkGeolocation(aPrivateMode, aWindow) {
     return Task.spawn(function* () {
       aWindow.gBrowser.selectedTab = aWindow.gBrowser.addTab(testPageURL);
       yield BrowserTestUtils.browserLoaded(aWindow.gBrowser.selectedBrowser);
 
       let notification = aWindow.PopupNotifications.getNotification("geolocation");
 
       // Wait until the notification is available.
-      while (!notification){
+      while (!notification) {
         yield new Promise(resolve => { executeSoon(resolve); });
-        let notification = aWindow.PopupNotifications.getNotification("geolocation");
+        notification = aWindow.PopupNotifications.getNotification("geolocation");
       }
 
       if (aPrivateMode) {
         // Make sure the notification is correctly displayed without a remember control
         ok(!notification.options.checkbox.show, "Secondary actions should exist (always/never remember)");
       } else {
         ok(notification.options.checkbox.show, "Secondary actions should exist (always/never remember)");
       }
       notification.remove();
 
       aWindow.gBrowser.removeCurrentTab();
     });
-  };
+  }
 
   let win = yield BrowserTestUtils.openNewBrowserWindow();
   let browser = win.gBrowser.selectedBrowser;
   browser.loadURI(testPageURL);
   yield BrowserTestUtils.browserLoaded(browser);
 
   yield checkGeolocation(false, win);
 
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_geoprompt_page.html
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_geoprompt_page.html
@@ -1,13 +1,13 @@
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
 <html>
   <head>
     <title>Geolocation invoker</title>
   </head>
   <body>
     <script type="text/javascript">
-      navigator.geolocation.getCurrentPosition(function (pos) {
+      navigator.geolocation.getCurrentPosition(function(pos) {
         // ignore
       });
     </script>
   </body>
 </html>
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_lastpbcontextexited.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_lastpbcontextexited.js
@@ -6,25 +6,25 @@ function test() {
   // We need to open a new window for this so that its docshell would get destroyed
   // when clearing the PB mode flag.
   function runTest(aCloseWindow, aCallback) {
     let newWin = OpenBrowserWindow({private: true});
     SimpleTest.waitForFocus(function() {
       let expectedExiting = true;
       let expectedExited = false;
       let observerExiting = {
-        observe: function(aSubject, aTopic, aData) {
+        observe(aSubject, aTopic, aData) {
           is(aTopic, "last-pb-context-exiting", "Correct topic should be dispatched (exiting)");
           is(expectedExiting, true, "notification not expected yet (exiting)");
           expectedExited = true;
           Services.obs.removeObserver(observerExiting, "last-pb-context-exiting");
         }
       };
       let observerExited = {
-        observe: function(aSubject, aTopic, aData) {
+        observe(aSubject, aTopic, aData) {
           is(aTopic, "last-pb-context-exited", "Correct topic should be dispatched (exited)");
           is(expectedExited, true, "notification not expected yet (exited)");
           Services.obs.removeObserver(observerExited, "last-pb-context-exited");
           aCallback();
         }
       };
       Services.obs.addObserver(observerExiting, "last-pb-context-exiting", false);
       Services.obs.addObserver(observerExited, "last-pb-context-exited", false);
@@ -35,15 +35,15 @@ function test() {
     }, newWin);
   }
 
   waitForExplicitFinish();
 
   runTest(function(newWin) {
       // Simulate pressing the window close button
       newWin.document.getElementById("cmd_closeWindow").doCommand();
-    }, function () {
+    }, function() {
       runTest(function(newWin) {
           // Simulate closing the last tab
           newWin.document.getElementById("cmd_close").doCommand();
         }, finish);
     });
 }
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage.js
@@ -1,25 +1,25 @@
 /* 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/. */
 
  add_task(function* test() {
   requestLongerTimeout(2);
-  const page1 = 'http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/' +
-                'browser_privatebrowsing_localStorage_page1.html'
+  const page1 = "http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/" +
+                "browser_privatebrowsing_localStorage_page1.html"
 
   let win = yield BrowserTestUtils.openNewBrowserWindow({private: true});
 
-  let tab = win.gBrowser.selectedTab = win.gBrowser.addTab(page1);
+  win.gBrowser.selectedTab = win.gBrowser.addTab(page1);
   let browser = win.gBrowser.selectedBrowser;
   yield BrowserTestUtils.browserLoaded(browser);
 
   browser.loadURI(
-    'http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/' +
-    'browser_privatebrowsing_localStorage_page2.html');
+    "http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/" +
+    "browser_privatebrowsing_localStorage_page2.html");
   yield BrowserTestUtils.browserLoaded(browser);
 
-  is(browser.contentTitle, '2', "localStorage should contain 2 items");
+  is(browser.contentTitle, "2", "localStorage should contain 2 items");
 
   // Cleanup
   yield BrowserTestUtils.closeWindow(win);
  });
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after.js
@@ -6,31 +6,30 @@
 // allow any data to leak due to cached values.
 
 // Step 1: Load browser_privatebrowsing_localStorage_before_after_page.html in a private tab, causing a storage
 //   item to exist. Close the tab.
 // Step 2: Load the same page in a non-private tab, ensuring that the storage instance reports only one item
 //   existing.
 
 add_task(function* test() {
-  let testURI = "about:blank";
-  let prefix = 'http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/';
+  let prefix = "http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/";
 
   // Step 1.
   let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private: true});
   let privateBrowser = privateWin.gBrowser.addTab(
-    prefix + 'browser_privatebrowsing_localStorage_before_after_page.html').linkedBrowser;
+    prefix + "browser_privatebrowsing_localStorage_before_after_page.html").linkedBrowser;
   yield BrowserTestUtils.browserLoaded(privateBrowser);
 
-  is(privateBrowser.contentTitle, '1', "localStorage should contain 1 item");
+  is(privateBrowser.contentTitle, "1", "localStorage should contain 1 item");
 
   // Step 2.
   let win = yield BrowserTestUtils.openNewBrowserWindow();
   let browser = win.gBrowser.addTab(
-    prefix + 'browser_privatebrowsing_localStorage_before_after_page2.html').linkedBrowser;
+    prefix + "browser_privatebrowsing_localStorage_before_after_page2.html").linkedBrowser;
   yield BrowserTestUtils.browserLoaded(browser);
 
-  is(browser.contentTitle, 'null|0', 'localStorage should contain 0 items');
+  is(browser.contentTitle, "null|0", "localStorage should contain 0 items");
 
   // Cleanup
   yield BrowserTestUtils.closeWindow(privateWin);
   yield BrowserTestUtils.closeWindow(win);
 });
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after_page.html
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after_page.html
@@ -1,11 +1,11 @@
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
 <script type="text/javascript">
   localStorage.clear();
-  localStorage.setItem('zzztest', 'zzzvalue');
+  localStorage.setItem("zzztest", "zzzvalue");
   document.title = localStorage.length;
 </script>
 </head>
 <body>
 </body>
 </html>
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after_page2.html
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after_page2.html
@@ -1,10 +1,10 @@
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
 <script type="text/javascript">
-  document.title = localStorage.getItem('zzztest', 'zzzvalue') + '|' + localStorage.length;
+  document.title = localStorage.getItem("zzztest", "zzzvalue") + "|" + localStorage.length;
   localStorage.clear();
 </script>
 </head>
 <body>
 </body>
 </html>
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_page1.html
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_page1.html
@@ -1,10 +1,10 @@
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
 <script type="text/javascript">
   localStorage.clear();
-  localStorage.setItem('test1', 'value1');
+  localStorage.setItem("test1", "value1");
 </script>
 </head>
 <body>
 </body>
 </html>
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_page2.html
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_page2.html
@@ -1,10 +1,10 @@
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
 <script type="text/javascript">
-  localStorage.setItem('test2', 'value2');
+  localStorage.setItem("test2", "value2");
   document.title = localStorage.length;
 </script>
 </head>
 <body>
 </body>
 </html>
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_opendir.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_opendir.js
@@ -76,17 +76,17 @@ function test() {
       setupCleanSlate(privateWindow);
 
       // Test 2: the user first tries to open a file inside the private browsing mode
 
       // test the private window
       ok(!privateWindow.gLastOpenDirectory.path,
          "No original path should exist inside the private browsing mode");
       privateWindow.gLastOpenDirectory.path = dir1;
-      is(privateWindow.gLastOpenDirectory.path.path, dir1.path, 
+      is(privateWindow.gLastOpenDirectory.path.path, dir1.path,
          "The path should be successfully set inside the private browsing mode");
       // test the non-private window
       ok(!nonPrivateWindow.gLastOpenDirectory.path,
          "The path set inside the private browsing mode should not leak when leaving that mode");
 
       setupCleanSlate(nonPrivateWindow);
       setupCleanSlate(privateWindow);
 
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.js
@@ -13,33 +13,33 @@ add_task(function* test() {
   const TEST_URL = "http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.html"
   const TEST_URI = Services.io.newURI(TEST_URL);
   const TITLE_1 = "Title 1";
   const TITLE_2 = "Title 2";
 
   function waitForTitleChanged() {
     return new Promise(resolve => {
       let historyObserver = {
-        onTitleChanged: function(uri, pageTitle) {
+        onTitleChanged(uri, pageTitle) {
           PlacesUtils.history.removeObserver(historyObserver, false);
-          resolve({uri: uri, pageTitle: pageTitle});
+          resolve({uri, pageTitle});
         },
-        onBeginUpdateBatch: function () {},
-        onEndUpdateBatch: function () {},
-        onVisit: function () {},
-        onDeleteURI: function () {},
-        onClearHistory: function () {},
-        onPageChanged: function () {},
-        onDeleteVisits: function() {},
+        onBeginUpdateBatch() {},
+        onEndUpdateBatch() {},
+        onVisit() {},
+        onDeleteURI() {},
+        onClearHistory() {},
+        onPageChanged() {},
+        onDeleteVisits() {},
         QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver])
       };
 
       PlacesUtils.history.addObserver(historyObserver, false);
     });
-  };
+  }
 
   yield PlacesTestUtils.clearHistory();
 
   let tabToClose = gBrowser.selectedTab = gBrowser.addTab(TEST_URL);
   yield waitForTitleChanged();
   is(PlacesUtils.history.getPageTitle(TEST_URI), TITLE_1, "The title matches the orignal title after first visit");
 
   let place = {
@@ -47,18 +47,18 @@ add_task(function* test() {
     title: TITLE_2,
     visits: [{
       visitDate: Date.now() * 1000,
       transitionType: Ci.nsINavHistoryService.TRANSITION_LINK
     }]
   };
   PlacesUtils.asyncHistory.updatePlaces(place, {
     handleError: () => ok(false, "Unexpected error in adding visit."),
-    handleResult: function () { },
-    handleCompletion: function () {}
+    handleResult() { },
+    handleCompletion() {}
   });
 
   yield waitForTitleChanged();
   is(PlacesUtils.history.getPageTitle(TEST_URI), TITLE_2, "The title matches the updated title after updating visit");
 
   let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private:true});
   yield BrowserTestUtils.browserLoaded(privateWin.gBrowser.addTab(TEST_URL).linkedBrowser);
 
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placestitle.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placestitle.js
@@ -20,17 +20,17 @@ add_task(function* test() {
   yield cleanup();
 
   let deferredFirst = PromiseUtils.defer();
   let deferredSecond = PromiseUtils.defer();
   let deferredThird = PromiseUtils.defer();
 
   let testNumber = 0;
   let historyObserver = {
-    onTitleChanged: function(aURI, aPageTitle) {
+    onTitleChanged(aURI, aPageTitle) {
       if (aURI.spec != TEST_URL)
         return;
       switch (++testNumber) {
         case 1:
           // The first time that the page is loaded
           deferredFirst.resolve(aPageTitle);
           break;
         case 2:
@@ -43,23 +43,23 @@ add_task(function* test() {
           break;
         default:
           // Checks that opening the page in a private window should not fire a
           // title change.
           ok(false, "Title changed. Unexpected pass: " + testNumber);
       }
     },
 
-    onBeginUpdateBatch: function () {},
-    onEndUpdateBatch: function () {},
-    onVisit: function () {},
-    onDeleteURI: function () {},
-    onClearHistory: function () {},
-    onPageChanged: function () {},
-    onDeleteVisits: function() {},
+    onBeginUpdateBatch() {},
+    onEndUpdateBatch() {},
+    onVisit() {},
+    onDeleteURI() {},
+    onClearHistory() {},
+    onPageChanged() {},
+    onDeleteVisits() {},
     QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver])
   };
   PlacesUtils.history.addObserver(historyObserver, false);
 
 
   let win = yield BrowserTestUtils.openNewBrowserWindow();
   win.gBrowser.selectedTab = win.gBrowser.addTab(TEST_URL);
   let aPageTitle = yield deferredFirst.promise;
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_sidebar.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_sidebar.js
@@ -15,55 +15,55 @@ function test() {
     return win.SidebarUI.show("viewBookmarksSidebar").then(() => win);
   }
 
   let windowCache = [];
   function cacheWindow(w) {
     windowCache.push(w);
     return w;
   }
-  function closeCachedWindows () {
+  function closeCachedWindows() {
     windowCache.forEach(w => w.close());
   }
 
   // Part 1: NON PRIVATE WINDOW -> PRIVATE WINDOW
   openWindow(window, {}, 1).
     then(cacheWindow).
     then(openSidebar).
     then(win => openWindow(win, { private: true })).
     then(cacheWindow).
     then(function({ document }) {
       let sidebarBox = document.getElementById("sidebar-box");
-      is(sidebarBox.hidden, true, 'Opening a private window from reg window does not open the sidebar');
+      is(sidebarBox.hidden, true, "Opening a private window from reg window does not open the sidebar");
     }).
     // Part 2: NON PRIVATE WINDOW -> NON PRIVATE WINDOW
     then(() => openWindow(window)).
     then(cacheWindow).
     then(openSidebar).
     then(win => openWindow(win)).
     then(cacheWindow).
     then(function({ document }) {
       let sidebarBox = document.getElementById("sidebar-box");
-      is(sidebarBox.hidden, false, 'Opening a reg window from reg window does open the sidebar');
+      is(sidebarBox.hidden, false, "Opening a reg window from reg window does open the sidebar");
     }).
     // Part 3: PRIVATE WINDOW -> NON PRIVATE WINDOW
     then(() => openWindow(window, { private: true })).
     then(cacheWindow).
     then(openSidebar).
     then(win => openWindow(win)).
     then(cacheWindow).
     then(function({ document }) {
       let sidebarBox = document.getElementById("sidebar-box");
-      is(sidebarBox.hidden, true, 'Opening a reg window from a private window does not open the sidebar');
+      is(sidebarBox.hidden, true, "Opening a reg window from a private window does not open the sidebar");
     }).
     // Part 4: PRIVATE WINDOW -> PRIVATE WINDOW
     then(() => openWindow(window, { private: true })).
     then(cacheWindow).
     then(openSidebar).
     then(win => openWindow(win, { private: true })).
     then(cacheWindow).
     then(function({ document }) {
       let sidebarBox = document.getElementById("sidebar-box");
-      is(sidebarBox.hidden, false, 'Opening a private window from private window does open the sidebar');
+      is(sidebarBox.hidden, false, "Opening a private window from private window does open the sidebar");
     }).
     then(closeCachedWindows).
     then(finish);
 }
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_ui.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_ui.js
@@ -28,41 +28,41 @@ function test() {
       is(PrivateBrowsingUtils.isWindowPrivate(aWindow), aIsPrivateMode,
         "PrivateBrowsingUtils should report the correct per-window private browsing status (privateBrowsing should be " +
         aIsPrivateMode + ")");
 
       aCallback();
     }, {capture: true, once: true});
 
     aWindow.gBrowser.selectedBrowser.loadURI(testURI);
-  };
+  }
 
   function openPrivateBrowsingModeByUI(aWindow, aCallback) {
     Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
       aSubject.addEventListener("load", function() {
         Services.obs.removeObserver(observer, "domwindowopened");
           windowsToClose.push(aSubject);
           aCallback(aSubject);
       }, {once: true});
     }, "domwindowopened", false);
 
     cmd = aWindow.document.getElementById("Tools:PrivateBrowsing");
     var func = new Function("", cmd.getAttribute("oncommand"));
     func.call(cmd);
-  };
+  }
 
   function testOnWindow(aOptions, aCallback) {
     whenNewWindowLoaded(aOptions, function(aWin) {
       windowsToClose.push(aWin);
       // execute should only be called when need, like when you are opening
       // web pages on the test. If calling executeSoon() is not necesary, then
       // call whenNewWindowLoaded() instead of testOnWindow() on your test.
       executeSoon(() => aCallback(aWin));
     });
-  };
+  }
 
    // this function is called after calling finish() on the test.
   registerCleanupFunction(function() {
     windowsToClose.forEach(function(aWin) {
       aWin.close();
     });
   });
 
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_windowtitle.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_windowtitle.js
@@ -22,18 +22,17 @@ add_task(function* test() {
   let pb_about_pb_title;
   if (isOSX) {
     page_with_title = test_title;
     page_without_title = app_name;
     about_pb_title = "Open a private window?";
     pb_page_with_title = test_title + " - (Private Browsing)";
     pb_page_without_title = app_name + " - (Private Browsing)";
     pb_about_pb_title = "Private Browsing - (Private Browsing)";
-  }
-  else {
+  } else {
     page_with_title = test_title + " - " + app_name;
     page_without_title = app_name;
     about_pb_title = "Open a private window?" + " - " + app_name;
     pb_page_with_title = test_title + " - " + app_name + " (Private Browsing)";
     pb_page_without_title = app_name + " (Private Browsing)";
     pb_about_pb_title = "Private Browsing - " + app_name + " (Private Browsing)";
   }
 
--- a/browser/components/search/content/search.xml
+++ b/browser/components/search/content/search.xml
@@ -1560,28 +1560,29 @@
             this.settingsButton.setAttribute("width", buttonWidth);
             if (rowCount == 1 && hasDummyItems) {
               // When there's only one row, make the compact settings button
               // hug the right edge of the panel.  It may not due to the panel's
               // width not being an integral multiple of the button width.  (See
               // the "There will be an emtpy area" comment above.)  Increase the
               // width of the last dummy item by the remainder.
               //
-              // There's one weird thing to guard against.  When layout pixels
-              // aren't an integral multiple of device pixels, the calculated
-              // remainder can end up being ~1px too big, at least on Windows,
-              // which pushes the settings button to a new row.  The remainder
-              // is integral, not a fraction, so that's not the problem.  To
-              // work around that, unscale the remainder, floor it, scale it
-              // back, and then floor that.
+              // There's one weird thing to guard against: when layout pixels
+              // aren't an integral multiple of device pixels, the settings
+              // button sometimes gets pushed to a new row, depending on the
+              // panel and button widths.  It's as if `remainder` is somehow
+              // too big, even though it's an integer.  To work around that,
+              // decrement the remainder if the scale is not an integer.
               let scale = window.QueryInterface(Ci.nsIInterfaceRequestor)
                                 .getInterface(Ci.nsIDOMWindowUtils)
                                 .screenPixelsPerCSSPixel;
               let remainder = panelWidth - (enginesPerRow * buttonWidth);
-              remainder = Math.floor(Math.floor(remainder * scale) / scale);
+              if (Math.floor(scale) != scale) {
+                remainder--;
+              }
               let width = remainder + buttonWidth;
               let lastDummyItem = this.settingsButton.previousSibling;
               lastDummyItem.setAttribute("width", width);
             }
           }
         ]]></body>
       </method>
 
--- a/browser/confvars.sh
+++ b/browser/confvars.sh
@@ -51,15 +51,14 @@ MOZ_APP_ID={ec8030f7-c20a-464f-9b0e-13a3
 # This should usually be the same as the value MAR_CHANNEL_ID.
 # If more than one ID is needed, then you should use a comma separated list
 # of values.
 ACCEPTED_MAR_CHANNEL_IDS=firefox-mozilla-central
 # The MAR_CHANNEL_ID must not contain the following 3 characters: ",\t "
 MAR_CHANNEL_ID=firefox-mozilla-central
 MOZ_PROFILE_MIGRATOR=1
 MOZ_JSDOWNLOADS=1
-MOZ_RUST_MP4PARSE=1
 
 # Enable checking that add-ons are signed by the trusted root
 MOZ_ADDON_SIGNING=1
 
 # Include the DevTools client, not just the server (which is the default)
 MOZ_DEVTOOLS=all
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -228,30 +228,19 @@ let ProfileAutocomplete = {
 
     if (selectedIndex == -1 ||
         !this._lastAutoCompleteResult ||
         this._lastAutoCompleteResult.getStyleAt(selectedIndex) != "autofill-profile") {
       return;
     }
 
     let profile = JSON.parse(this._lastAutoCompleteResult.getCommentAt(selectedIndex));
+    let formHandler = FormAutofillContent.getFormHandler(focusedInput);
 
-    // TODO: FormAutofillHandler.autofillFormFields will be used for filling
-    // fields logic eventually.
-    for (let inputInfo of formDetails) {
-      // Skip filling the value of focused input which is filled in
-      // FormFillController.
-      if (inputInfo.element === focusedInput) {
-        continue;
-      }
-      let value = profile[inputInfo.fieldName];
-      if (value) {
-        inputInfo.element.setUserInput(value);
-      }
-    }
+    formHandler.autofillFormFields(profile, focusedInput);
   },
 };
 
 /**
  * Handles content's interactions for the process.
  *
  * NOTE: Declares it by "var" to make it accessible in unit tests.
  */
@@ -291,28 +280,41 @@ var FormAutofillContent = {
       if (element == detail.element) {
         return detail;
       }
     }
     return null;
   },
 
   /**
+   * Get the form's handler from cache which is created after page identified.
+   *
+   * @param {HTMLInputElement} element Focused input which triggered profile searching
+   * @returns {Array<Object>|null}
+   *          Return target form's handler from content cache
+   *          (or return null if the information is not found in the cache).
+   *
+   */
+  getFormHandler(element) {
+    let rootElement = FormLikeFactory.findRootForField(element);
+    return this._formsDetails.get(rootElement);
+  },
+
+  /**
    * Get the form's information from cache which is created after page identified.
    *
    * @param {HTMLInputElement} element Focused input which triggered profile searching
    * @returns {Array<Object>|null}
-   *          Return target form's information that cloned from content cache
+   *          Return target form's information from content cache
    *          (or return null if the information is not found in the cache).
    *
    */
   getFormDetails(element) {
-    let rootElement = FormLikeFactory.findRootForField(element);
-    let formDetails = this._formsDetails.get(rootElement);
-    return formDetails ? formDetails.fieldDetails : null;
+    let formHandler = this.getFormHandler(element);
+    return formHandler ? formHandler.fieldDetails : null;
   },
 
   getAllFieldNames(element) {
     let formDetails = this.getFormDetails(element);
     return formDetails.map(record => record.fieldName);
   },
 
   identifyAutofillFields(doc) {
--- a/browser/extensions/formautofill/FormAutofillHandler.jsm
+++ b/browser/extensions/formautofill/FormAutofillHandler.jsm
@@ -98,50 +98,35 @@ FormAutofillHandler.prototype = {
 
     log.debug("Collected details on", autofillData.length, "fields");
 
     return autofillData;
   },
 
   /**
    * Processes form fields that can be autofilled, and populates them with the
-   * data provided by backend.
+   * profile provided by backend.
    *
-   * @param {Array<Object>} autofillResult
-   *        Data returned by the user interface.
-   *        [{
-   *          section: Value originally provided to the user interface.
-   *          addressType: Value originally provided to the user interface.
-   *          contactType: Value originally provided to the user interface.
-   *          fieldName: Value originally provided to the user interface.
-   *          value: String with which the field should be updated.
-   *          index: Index to match the input in fieldDetails
-   *        }],
-   *        }
+   * @param {Object} profile
+   *        A profile to be filled in.
+   * @param {Object} focusedInput
+   *        A focused input element which is skipped for filling.
    */
-  autofillFormFields(autofillResult) {
-    log.debug("autofillFormFields:", autofillResult);
-    for (let field of autofillResult) {
-      // TODO: Skip filling the value of focused input which is filled in
-      // FormFillController.
+  autofillFormFields(profile, focusedInput) {
+    log.debug("profile in autofillFormFields:", profile);
+    for (let fieldDetail of this.fieldDetails) {
+      // Avoid filling field value in the following cases:
+      // 1. the focused input which is filled in FormFillController.
+      // 2. a non-empty input field
+      // 3. the invalid value set
 
-      // Get the field details, if it was processed by the user interface.
-      let fieldDetail = this.fieldDetails[field.index];
-
-      // Avoid the invalid value set
-      if (!fieldDetail || !field.value) {
+      if (fieldDetail.element === focusedInput ||
+          fieldDetail.element.value) {
         continue;
       }
 
-      let info = FormAutofillHeuristics.getInfo(fieldDetail.element);
-      if (!info ||
-          field.section != info.section ||
-          field.addressType != info.addressType ||
-          field.contactType != info.contactType ||
-          field.fieldName != info.fieldName) {
-        Cu.reportError("Autocomplete tokens mismatched");
-        continue;
+      let value = profile[fieldDetail.fieldName];
+      if (value) {
+        fieldDetail.element.setUserInput(value);
       }
-
-      fieldDetail.element.setUserInput(field.value);
     }
   },
 };
--- a/browser/extensions/formautofill/test/unit/test_autofillFormFields.js
+++ b/browser/extensions/formautofill/test/unit/test_autofillFormFields.js
@@ -8,17 +8,17 @@ Cu.import("resource://formautofill/FormA
 
 const TESTCASES = [
   {
     description: "Form without autocomplete property",
     document: `<form><input id="given-name"><input id="family-name">
                <input id="street-addr"><input id="city"><input id="country">
                <input id='email'><input id="tel"></form>`,
     fieldDetails: [],
-    profileData: [],
+    profileData: {},
     expectedResult: {
       "street-addr": "",
       "city": "",
       "country": "",
       "email": "",
       "tel": "",
     },
   },
@@ -35,25 +35,23 @@ const TESTCASES = [
       {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name", "element": {}},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name", "element": {}},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address", "element": {}},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2", "element": {}},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "country", "element": {}},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "email", "element": {}},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "tel", "element": {}},
     ],
-    profileData: [
-      {"section": "", "addressType": "", "fieldName": "given-name", "contactType": "", "index": 0, "value": "foo"},
-      {"section": "", "addressType": "", "fieldName": "family-name", "contactType": "", "index": 1, "value": "bar"},
-      {"section": "", "addressType": "", "fieldName": "street-address", "contactType": "", "index": 2, "value": "2 Harrison St"},
-      {"section": "", "addressType": "", "fieldName": "address-level2", "contactType": "", "index": 3, "value": "San Francisco"},
-      {"section": "", "addressType": "", "fieldName": "country", "contactType": "", "index": 4, "value": "US"},
-      {"section": "", "addressType": "", "fieldName": "email", "contactType": "", "index": 5, "value": "foo@mozilla.com"},
-      {"section": "", "addressType": "", "fieldName": "tel", "contactType": "", "index": 6, "value": "1234567"},
-    ],
+    profileData: {
+      "street-address": "2 Harrison St",
+      "address-level2": "San Francisco",
+      "country": "US",
+      "email": "foo@mozilla.com",
+      "tel": "1234567",
+    },
     expectedResult: {
       "street-addr": "2 Harrison St",
       "city": "San Francisco",
       "country": "US",
       "email": "foo@mozilla.com",
       "tel": "1234567",
     },
   },
@@ -70,25 +68,23 @@ const TESTCASES = [
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel", "element": {}},
     ],
-    profileData: [
-      {"section": "", "addressType": "shipping", "fieldName": "given-name", "contactType": "", "index": 0, "value": "foo"},
-      {"section": "", "addressType": "shipping", "fieldName": "family-name", "contactType": "", "index": 1, "value": "bar"},
-      {"section": "", "addressType": "shipping", "fieldName": "street-address", "contactType": "", "index": 2, "value": "2 Harrison St"},
-      {"section": "", "addressType": "shipping", "fieldName": "address-level2", "contactType": "", "index": 3, "value": "San Francisco"},
-      {"section": "", "addressType": "shipping", "fieldName": "country", "contactType": "", "index": 4, "value": "US"},
-      {"section": "", "addressType": "shipping", "fieldName": "email", "contactType": "", "index": 5, "value": "foo@mozilla.com"},
-      {"section": "", "addressType": "shipping", "fieldName": "tel", "contactType": "", "index": 6, "value": "1234567"},
-    ],
+    profileData: {
+      "street-address": "2 Harrison St",
+      "address-level2": "San Francisco",
+      "country": "US",
+      "email": "foo@mozilla.com",
+      "tel": "1234567",
+    },
     expectedResult: {
       "street-addr": "2 Harrison St",
       "city": "San Francisco",
       "country": "US",
       "email": "foo@mozilla.com",
       "tel": "1234567",
     },
   },
@@ -105,25 +101,23 @@ const TESTCASES = [
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel", "element": {}},
     ],
-    profileData: [
-      {"section": "", "addressType": "shipping", "fieldName": "given-name", "contactType": "", "index": 0, "value": "foo"},
-      {"section": "", "addressType": "shipping", "fieldName": "family-name", "contactType": "", "index": 1, "value": "bar"},
-      {"section": "", "addressType": "shipping", "fieldName": "street-address", "contactType": "", "index": 2, "value": "2 Harrison St"},
-      {"section": "", "addressType": "shipping", "fieldName": "address-level2", "contactType": "", "index": 3, "value": "San Francisco"},
-      {"section": "", "addressType": "shipping", "fieldName": "country", "contactType": "", "index": 4, "value": "US"},
-      {"section": "", "addressType": "shipping", "fieldName": "email", "contactType": "", "index": 5},
-      {"section": "", "addressType": "shipping", "fieldName": "tel", "contactType": "", "index": 6},
-    ],
+    profileData: {
+      "street-address": "2 Harrison St",
+      "address-level2": "San Francisco",
+      "country": "US",
+      "email": "",
+      "tel": "",
+    },
     expectedResult: {
       "street-addr": "2 Harrison St",
       "city": "San Francisco",
       "country": "US",
       "email": "",
       "tel": "",
     },
   },
@@ -140,25 +134,23 @@ const TESTCASES = [
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel", "element": {}},
     ],
-    profileData: [
-      {"section": "", "addressType": "shipping", "fieldName": "given-name", "contactType": "", "index": 0, "value": "foo"},
-      {"section": "", "addressType": "shipping", "fieldName": "family-name", "contactType": "", "index": 1, "value": "bar"},
-      {"section": "", "addressType": "shipping", "fieldName": "street-address", "contactType": "", "index": 2, "value": "2 Harrison St"},
-      {"section": "", "addressType": "shipping", "fieldName": "address-level2", "contactType": "", "index": 3, "value": "San Francisco"},
-      {"section": "", "addressType": "shipping", "fieldName": "country", "contactType": "", "index": 4, "value": "US"},
-      {"section": "", "addressType": "shipping", "fieldName": "email", "contactType": "", "index": 5, "value": "foo@mozilla.com"},
-      {"section": "", "addressType": "shipping", "fieldName": "tel", "contactType": "", "index": 6, "value": "1234567"},
-    ],
+    profileData: {
+      "street-address": "",
+      "address-level2": "",
+      "country": "",
+      "email": "foo@mozilla.com",
+      "tel": "1234567",
+    },
     expectedResult: {
       "street-addr": "",
       "city": "",
       "country": "",
       "email": "foo@mozilla.com",
       "tel": "1234567",
     },
   },
@@ -169,22 +161,34 @@ for (let tc of TESTCASES) {
     let testcase = tc;
     add_task(function* () {
       do_print("Starting testcase: " + testcase.description);
 
       let doc = MockDocument.createTestDocument("http://localhost:8080/test/",
                                                 testcase.document);
       let form = doc.querySelector("form");
       let handler = new FormAutofillHandler(form);
+      let onChangePromises = [];
 
       handler.fieldDetails = testcase.fieldDetails;
       handler.fieldDetails.forEach((field, index) => {
-        field.element = doc.querySelectorAll("input")[index];
+        let element = doc.querySelectorAll("input")[index];
+        field.element = element;
+        if (!testcase.profileData[field.fieldName]) {
+          // Avoid waiting for `change` event of a input with a blank value to
+          // be filled.
+          return;
+        }
+        onChangePromises.push(new Promise(resolve => {
+          element.addEventListener("change", () => {
+            let id = element.id;
+            Assert.equal(element.value, testcase.expectedResult[id],
+                        "Check the " + id + " fields were filled with correct data");
+            resolve();
+          }, {once: true});
+        }));
       });
 
       handler.autofillFormFields(testcase.profileData);
-      for (let id in testcase.expectedResult) {
-        Assert.equal(doc.getElementById(id).value, testcase.expectedResult[id],
-                    "Check the " + id + " fields were filled with correct data");
-      }
+      yield Promise.all(onChangePromises);
     });
   })();
 }
--- a/browser/extensions/pocket/content/main.js
+++ b/browser/extensions/pocket/content/main.js
@@ -37,17 +37,17 @@
 
 // TODO : Get the toolbar icons from Firefox's build (Nikki needs to give us a red saved icon)
 // TODO : [needs clarificaiton from Fx] Firefox's plan was to hide Pocket from context menus until the user logs in. Now that it's an extension I'm wondering if we still need to do this.
 // TODO : [needs clarificaiton from Fx] Reader mode (might be a something they need to do since it's in html, need to investigate their code)
 // TODO : [needs clarificaiton from Fx] Move prefs within pktApi.s to sqlite or a local file so it's not editable (and is safer)
 // TODO : [nice to have] - Immediately save, buffer the actions in a local queue and send (so it works offline, works like our native extensions)
 
 /* eslint-disable no-shadow */
-/* eslint "mozilla/import-browserjs-globals": "error" */
+/* eslint-env mozilla/browser-window */
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
   "resource://gre/modules/ReaderMode.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "pktApi",
   "chrome://pocket/content/pktApi.jsm");
--- a/browser/installer/windows/nsis/installer.nsi
+++ b/browser/installer/windows/nsis/installer.nsi
@@ -382,58 +382,22 @@ Section "-Application" APP_IDX
                                  "${AppRegName} Document" ""
   ${AddDisabledDDEHandlerValues} "FirefoxURL-$AppUserModelID" "$2" "$8,1" \
                                  "${AppRegName} URL" "true"
 
   ; For pre win8, the following keys should only be set if we can write to HKLM.
   ; For post win8, the keys below can be set in HKCU if needed.
   ${If} $TmpVal == "HKLM"
     ; Set the Start Menu Internet and Registered App HKLM registry keys.
-    ; If we're upgrading an existing install, replacing the old registry entries
-    ; (without a path hash) would cause the default browser to be reset.
-    ReadRegStr $0 HKLM "Software\Clients\StartMenuInternet\FIREFOX.EXE\DefaultIcon" ""
-    StrCpy $0 $0 -2
-    ${If} $0 != "$INSTDIR\${FileMainEXE}"
-      ${SetStartMenuInternet} "HKLM"
-      ${FixShellIconHandler} "HKLM"
-    ${EndIf}
-
-    ; If we are writing to HKLM and create either the desktop or start menu
-    ; shortcuts set IconsVisible to 1 otherwise to 0.
-    ; Taskbar shortcuts imply having a start menu shortcut.
-    StrCpy $0 "Software\Clients\StartMenuInternet\${AppRegName}-$AppUserModelID\InstallInfo"
-    ${If} $AddDesktopSC == 1
-    ${OrIf} $AddStartMenuSC == 1
-    ${OrIf} $AddTaskbarSC == 1
-      WriteRegDWORD HKLM "$0" "IconsVisible" 1
-    ${Else}
-      WriteRegDWORD HKLM "$0" "IconsVisible" 0
-    ${EndIf}
+    ${SetStartMenuInternet} "HKLM"
+    ${FixShellIconHandler} "HKLM"
   ${ElseIf} ${AtLeastWin8}
     ; Set the Start Menu Internet and Registered App HKCU registry keys.
-    ; If we're upgrading an existing install, replacing the old registry entries
-    ; (without a path hash) would cause the default browser to be reset.
-    ReadRegStr $0 HKCU "Software\Clients\StartMenuInternet\FIREFOX.EXE\DefaultIcon" ""
-    StrCpy $0 $0 -2
-    ${If} $0 != "$INSTDIR\${FileMainEXE}"
-      ${SetStartMenuInternet} "HKCU"
-      ${FixShellIconHandler} "HKCU"
-    ${EndIf}
-
-    ; If we create either the desktop or start menu shortcuts, then
-    ; set IconsVisible to 1 otherwise to 0.
-    ; Taskbar shortcuts imply having a start menu shortcut.
-    StrCpy $0 "Software\Clients\StartMenuInternet\${AppRegName}-$AppUserModelID\InstallInfo"
-    ${If} $AddDesktopSC == 1
-    ${OrIf} $AddStartMenuSC == 1
-    ${OrIf} $AddTaskbarSC == 1
-      WriteRegDWORD HKCU "$0" "IconsVisible" 1
-    ${Else}
-      WriteRegDWORD HKCU "$0" "IconsVisible" 0
-    ${EndIf}
+    ${SetStartMenuInternet} "HKCU"
+    ${FixShellIconHandler} "HKCU"
   ${EndIf}
 
 !ifdef MOZ_MAINTENANCE_SERVICE
   ; If the maintenance service page was displayed then a value was already
   ; explicitly selected for installing the maintenance service and
   ; and so InstallMaintenanceService will already be 0 or 1.
   ; If the maintenance service page was not displayed then
   ; InstallMaintenanceService will be equal to "".
--- a/browser/installer/windows/nsis/shared.nsh
+++ b/browser/installer/windows/nsis/shared.nsh
@@ -145,27 +145,30 @@
 !macro SetAsDefaultAppGlobal
   ${RemoveDeprecatedKeys} ; Does not use SHCTX
 
   SetShellVarContext all      ; Set SHCTX to all users (e.g. HKLM)
   ${SetHandlers} ; Uses SHCTX
   ${SetStartMenuInternet} "HKLM"
   ${FixShellIconHandler} "HKLM"
   ${ShowShortcuts}
-  ${StrFilter} "${FileMainEXE}" "+" "" "" $R9
-  WriteRegStr HKLM "Software\Clients\StartMenuInternet" "" "$R9-$AppUserModelID"
 !macroend
 !define SetAsDefaultAppGlobal "!insertmacro SetAsDefaultAppGlobal"
 
 ; Removes shortcuts for this installation. This should also remove the
 ; application from Open With for the file types the application handles
 ; (bug 370480).
 !macro HideShortcuts
-  ${StrFilter} "${FileMainEXE}" "+" "" "" $0
-  StrCpy $R1 "Software\Clients\StartMenuInternet\$0\InstallInfo"
+  ; Find the correct registry path to clear IconsVisible.
+  StrCpy $R1 "Software\Clients\StartMenuInternet\${AppRegName}-$AppUserModelID\InstallInfo"
+  ReadRegDWORD $0 HKLM "$R1" "ShowIconsCommand"
+  ${If} ${Errors}
+    ${StrFilter} "${FileMainEXE}" "+" "" "" $0
+    StrCpy $R1 "Software\Clients\StartMenuInternet\$0\InstallInfo"
+  ${EndIf}
   WriteRegDWORD HKLM "$R1" "IconsVisible" 0
   ${If} ${AtLeastWin8}
     WriteRegDWORD HKCU "$R1" "IconsVisible" 0
   ${EndIf}
 
   SetShellVarContext all  ; Set $DESKTOP to All Users
   ${Unless} ${FileExists} "$DESKTOP\${BrandFullName}.lnk"
     SetShellVarContext current  ; Set $DESKTOP to the current user's desktop
@@ -216,17 +219,23 @@
     ${EndIf}
   ${EndIf}
 !macroend
 !define HideShortcuts "!insertmacro HideShortcuts"
 
 ; Adds shortcuts for this installation. This should also add the application
 ; to Open With for the file types the application handles (bug 370480).
 !macro ShowShortcuts
+  ; Find the correct registry path to set IconsVisible.
   StrCpy $R1 "Software\Clients\StartMenuInternet\${AppRegName}-$AppUserModelID\InstallInfo"
+  ReadRegDWORD $0 HKLM "$R1" "ShowIconsCommand"
+  ${If} ${Errors}
+    ${StrFilter} "${FileMainEXE}" "+" "" "" $0
+    StrCpy $R1 "Software\Clients\StartMenuInternet\$0\InstallInfo"
+  ${EndIf}
   WriteRegDWORD HKLM "$R1" "IconsVisible" 1
   ${If} ${AtLeastWin8}
     WriteRegDWORD HKCU "$R1" "IconsVisible" 1
   ${EndIf}
 
   SetShellVarContext all  ; Set $DESKTOP to All Users
   ${Unless} ${FileExists} "$DESKTOP\${BrandFullName}.lnk"
     CreateShortCut "$DESKTOP\${BrandFullName}.lnk" "$INSTDIR\${FileMainEXE}"
@@ -287,78 +296,87 @@
     ${If} ${FileExists} "$QUICKLAUNCH\${BrandFullName}.lnk"
       ShellLink::SetShortCutWorkingDirectory "$QUICKLAUNCH\${BrandFullName}.lnk" \
                                              "$INSTDIR"
     ${EndIf}
   ${EndUnless}
 !macroend
 !define ShowShortcuts "!insertmacro ShowShortcuts"
 
-!macro AddAssociationIfNoneExist FILE_TYPE
+!macro AddAssociationIfNoneExist FILE_TYPE KEY
   ClearErrors
   EnumRegKey $7 HKCR "${FILE_TYPE}" 0
   ${If} ${Errors}
-    WriteRegStr SHCTX "SOFTWARE\Classes\${FILE_TYPE}"  "" "FirefoxHTML-$AppUserModelID"
+    WriteRegStr SHCTX "SOFTWARE\Classes\${FILE_TYPE}"  "" ${KEY}
   ${EndIf}
-  WriteRegStr SHCTX "SOFTWARE\Classes\${FILE_TYPE}\OpenWithProgids" "FirefoxHTML-$AppUserModelID" ""
+  WriteRegStr SHCTX "SOFTWARE\Classes\${FILE_TYPE}\OpenWithProgids" ${KEY} ""
 !macroend
 !define AddAssociationIfNoneExist "!insertmacro AddAssociationIfNoneExist"
 
 ; Adds the protocol and file handler registry entries for making Firefox the
 ; default handler (uses SHCTX).
 !macro SetHandlers
   ${GetLongPath} "$INSTDIR\${FileMainEXE}" $8
 
+  ; See if we're using path hash suffixed registry keys for this install.
+  StrCpy $5 ""
+  ${StrFilter} "${FileMainEXE}" "+" "" "" $2
+  ReadRegStr $0 SHCTX "Software\Clients\StartMenuInternet\$2\DefaultIcon" ""
+  StrCpy $0 $0 -2
+  ${If} $0 != $8
+    StrCpy $5 "-$AppUserModelID"
+  ${EndIf}
+
   StrCpy $0 "SOFTWARE\Classes"
   StrCpy $2 "$\"$8$\" -osint -url $\"%1$\""
 
   ; Associate the file handlers with FirefoxHTML, if they aren't already.
   ReadRegStr $6 SHCTX "$0\.htm" ""
   ${WordFind} "$6" "-" "+1{" $6
   ${If} "$6" != "FirefoxHTML"
-    WriteRegStr SHCTX "$0\.htm"   "" "FirefoxHTML-$AppUserModelID"
+    WriteRegStr SHCTX "$0\.htm"   "" "FirefoxHTML$5"
   ${EndIf}
 
   ReadRegStr $6 SHCTX "$0\.html" ""
   ${WordFind} "$6" "-" "+1{" $6
   ${If} "$6" != "FirefoxHTML"
-    WriteRegStr SHCTX "$0\.html"  "" "FirefoxHTML-$AppUserModelID"
+    WriteRegStr SHCTX "$0\.html"  "" "FirefoxHTML$5"
   ${EndIf}
 
   ReadRegStr $6 SHCTX "$0\.shtml" ""
   ${WordFind} "$6" "-" "+1{" $6
   ${If} "$6" != "FirefoxHTML"
-    WriteRegStr SHCTX "$0\.shtml" "" "FirefoxHTML-$AppUserModelID"
+    WriteRegStr SHCTX "$0\.shtml" "" "FirefoxHTML$5"
   ${EndIf}
 
   ReadRegStr $6 SHCTX "$0\.xht" ""
   ${WordFind} "$6" "-" "+1{" $6
   ${If} "$6" != "FirefoxHTML"
-    WriteRegStr SHCTX "$0\.xht"   "" "FirefoxHTML-$AppUserModelID"
+    WriteRegStr SHCTX "$0\.xht"   "" "FirefoxHTML$5"
   ${EndIf}
 
   ReadRegStr $6 SHCTX "$0\.xhtml" ""
   ${WordFind} "$6" "-" "+1{" $6
   ${If} "$6" != "FirefoxHTML"
-    WriteRegStr SHCTX "$0\.xhtml" "" "FirefoxHTML-$AppUserModelID"
+    WriteRegStr SHCTX "$0\.xhtml" "" "FirefoxHTML$5"
   ${EndIf}
 
-  ${AddAssociationIfNoneExist} ".pdf"
-  ${AddAssociationIfNoneExist} ".oga"
-  ${AddAssociationIfNoneExist} ".ogg"
-  ${AddAssociationIfNoneExist} ".ogv"
-  ${AddAssociationIfNoneExist} ".pdf"
-  ${AddAssociationIfNoneExist} ".webm"
+  ${AddAssociationIfNoneExist} ".pdf" "FirefoxHTML$5"
+  ${AddAssociationIfNoneExist} ".oga" "FirefoxHTML$5"
+  ${AddAssociationIfNoneExist} ".ogg" "FirefoxHTML$5"
+  ${AddAssociationIfNoneExist} ".ogv" "FirefoxHTML$5"
+  ${AddAssociationIfNoneExist} ".pdf" "FirefoxHTML$5"
+  ${AddAssociationIfNoneExist} ".webm" "FirefoxHTML$5"
 
   ; An empty string is used for the 5th param because FirefoxHTML is not a
   ; protocol handler
-  ${AddDisabledDDEHandlerValues} "FirefoxHTML-$AppUserModelID" "$2" "$8,1" \
+  ${AddDisabledDDEHandlerValues} "FirefoxHTML$5" "$2" "$8,1" \
                                  "${AppRegName} HTML Document" ""
 
-  ${AddDisabledDDEHandlerValues} "FirefoxURL-$AppUserModelID" "$2" "$8,1" "${AppRegName} URL" \
+  ${AddDisabledDDEHandlerValues} "FirefoxURL$5" "$2" "$8,1" "${AppRegName} URL" \
                                  "true"
   ; An empty string is used for the 4th & 5th params because the following
   ; protocol handlers already have a display name and the additional keys
   ; required for a protocol handler.
   ${AddDisabledDDEHandlerValues} "ftp" "$2" "$8,1" "" ""
   ${AddDisabledDDEHandlerValues} "http" "$2" "$8,1" "" ""
   ${AddDisabledDDEHandlerValues} "https" "$2" "$8,1" "" ""
 !macroend
@@ -381,82 +399,87 @@
 ;
 ; This function also writes our RegisteredApplications entry, which gets us
 ; listed in the Settings app's default browser options on Windows 8+, and in
 ; Set Program Access and Defaults on earlier versions.
 !macro SetStartMenuInternet RegKey
   ${GetLongPath} "$INSTDIR\${FileMainEXE}" $8
   ${GetLongPath} "$INSTDIR\uninstall\helper.exe" $7
 
-  StrCpy $0 "Software\Clients\StartMenuInternet\${AppRegName}-$AppUserModelID"
-
-  WriteRegStr ${RegKey} "$0" "" "${BrandFullName}"
-
-  WriteRegStr ${RegKey} "$0\DefaultIcon" "" "$8,0"
+  ; Avoid writing new keys at the hash-suffixed path if this installation
+  ; already has keys at the old FIREFOX.EXE path. Otherwise we would create a
+  ; second entry in Default Apps for the same installation.
+  ${StrFilter} "${FileMainEXE}" "+" "" "" $1
+  ReadRegStr $0 ${RegKey} "Software\Clients\StartMenuInternet\$1\DefaultIcon" ""
+  StrCpy $0 $0 -2
+  ${If} $0 != $8
+    StrCpy $0 "Software\Clients\StartMenuInternet\${AppRegName}-$AppUserModelID"
 
-  ; The Reinstall Command is defined at
-  ; http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/shell/programmersguide/shell_adv/registeringapps.asp
-  WriteRegStr ${RegKey} "$0\InstallInfo" "HideIconsCommand" "$\"$7$\" /HideShortcuts"
-  WriteRegStr ${RegKey} "$0\InstallInfo" "ShowIconsCommand" "$\"$7$\" /ShowShortcuts"
-  WriteRegStr ${RegKey} "$0\InstallInfo" "ReinstallCommand" "$\"$7$\" /SetAsDefaultAppGlobal"
+    WriteRegStr ${RegKey} "$0" "" "${BrandFullName}"
 
-  ClearErrors
-  ReadRegDWORD $1 ${RegKey} "$0\InstallInfo" "IconsVisible"
-  ; If the IconsVisible name value pair doesn't exist add it otherwise the
-  ; application won't be displayed in Set Program Access and Defaults.
-  ${If} ${Errors}
-    ${If} ${FileExists} "$QUICKLAUNCH\${BrandFullName}.lnk"
-      WriteRegDWORD ${RegKey} "$0\InstallInfo" "IconsVisible" 1
-    ${Else}
-      WriteRegDWORD ${RegKey} "$0\InstallInfo" "IconsVisible" 0
-    ${EndIf}
-  ${EndIf}
+    WriteRegStr ${RegKey} "$0\DefaultIcon" "" "$8,0"
 
-  WriteRegStr ${RegKey} "$0\shell\open\command" "" "$\"$8$\""
+    ; The Reinstall Command is defined at
+    ; http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/shell/programmersguide/shell_adv/registeringapps.asp
+    WriteRegStr ${RegKey} "$0\InstallInfo" "HideIconsCommand" "$\"$7$\" /HideShortcuts"
+    WriteRegStr ${RegKey} "$0\InstallInfo" "ShowIconsCommand" "$\"$7$\" /ShowShortcuts"
+    WriteRegStr ${RegKey} "$0\InstallInfo" "ReinstallCommand" "$\"$7$\" /SetAsDefaultAppGlobal"
+    WriteRegDWORD ${RegKey} "$0\InstallInfo" "IconsVisible" 1
+
+    WriteRegStr ${RegKey} "$0\shell\open\command" "" "$\"$8$\""
 
-  WriteRegStr ${RegKey} "$0\shell\properties" "" "$(CONTEXT_OPTIONS)"
-  WriteRegStr ${RegKey} "$0\shell\properties\command" "" "$\"$8$\" -preferences"
+    WriteRegStr ${RegKey} "$0\shell\properties" "" "$(CONTEXT_OPTIONS)"
+    WriteRegStr ${RegKey} "$0\shell\properties\command" "" "$\"$8$\" -preferences"
 
-  WriteRegStr ${RegKey} "$0\shell\safemode" "" "$(CONTEXT_SAFE_MODE)"
-  WriteRegStr ${RegKey} "$0\shell\safemode\command" "" "$\"$8$\" -safe-mode"
+    WriteRegStr ${RegKey} "$0\shell\safemode" "" "$(CONTEXT_SAFE_MODE)"
+    WriteRegStr ${RegKey} "$0\shell\safemode\command" "" "$\"$8$\" -safe-mode"
 
-  ; Capabilities registry keys
-  WriteRegStr ${RegKey} "$0\Capabilities" "ApplicationDescription" "$(REG_APP_DESC)"
-  WriteRegStr ${RegKey} "$0\Capabilities" "ApplicationIcon" "$8,0"
-  WriteRegStr ${RegKey} "$0\Capabilities" "ApplicationName" "${BrandShortName}"
+    ; Capabilities registry keys
+    WriteRegStr ${RegKey} "$0\Capabilities" "ApplicationDescription" "$(REG_APP_DESC)"
+    WriteRegStr ${RegKey} "$0\Capabilities" "ApplicationIcon" "$8,0"
+    WriteRegStr ${RegKey} "$0\Capabilities" "ApplicationName" "${BrandShortName}"
 
-  WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".htm"   "FirefoxHTML-$AppUserModelID"
-  WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".html"  "FirefoxHTML-$AppUserModelID"
-  WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".shtml" "FirefoxHTML-$AppUserModelID"
-  WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".xht"   "FirefoxHTML-$AppUserModelID"
-  WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".xhtml" "FirefoxHTML-$AppUserModelID"
+    WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".htm"   "FirefoxHTML-$AppUserModelID"
+    WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".html"  "FirefoxHTML-$AppUserModelID"
+    WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".shtml" "FirefoxHTML-$AppUserModelID"
+    WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".xht"   "FirefoxHTML-$AppUserModelID"
+    WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".xhtml" "FirefoxHTML-$AppUserModelID"
+
+    WriteRegStr ${RegKey} "$0\Capabilities\StartMenu" "StartMenuInternet" "${AppRegName}-$AppUserModelID"
 
-  WriteRegStr ${RegKey} "$0\Capabilities\StartMenu" "StartMenuInternet" "${AppRegName}-$AppUserModelID"
+    WriteRegStr ${RegKey} "$0\Capabilities\URLAssociations" "ftp"    "FirefoxURL-$AppUserModelID"
+    WriteRegStr ${RegKey} "$0\Capabilities\URLAssociations" "http"   "FirefoxURL-$AppUserModelID"
+    WriteRegStr ${RegKey} "$0\Capabilities\URLAssociations" "https"  "FirefoxURL-$AppUserModelID"
 
-  WriteRegStr ${RegKey} "$0\Capabilities\URLAssociations" "ftp"    "FirefoxURL-$AppUserModelID"
-  WriteRegStr ${RegKey} "$0\Capabilities\URLAssociations" "http"   "FirefoxURL-$AppUserModelID"
-  WriteRegStr ${RegKey} "$0\Capabilities\URLAssociations" "https"  "FirefoxURL-$AppUserModelID"
-
-  ; Registered Application
-  WriteRegStr ${RegKey} "Software\RegisteredApplications" "${AppRegName}-$AppUserModelID" "$0\Capabilities"
+    ; Registered Application
+    WriteRegStr ${RegKey} "Software\RegisteredApplications" "${AppRegName}-$AppUserModelID" "$0\Capabilities"
+  ${EndIf}
 !macroend
 !define SetStartMenuInternet "!insertmacro SetStartMenuInternet"
 
 ; The IconHandler reference for FirefoxHTML can end up in an inconsistent state
 ; due to changes not being detected by the IconHandler for side by side
 ; installs (see bug 268512). The symptoms can be either an incorrect icon or no
 ; icon being displayed for files associated with Firefox (does not use SHCTX).
 !macro FixShellIconHandler RegKey
+  ; Find the correct key to update, either FirefoxHTML or FirefoxHTML-[PathHash]
+  StrCpy $3 "FirefoxHTML-$AppUserModelID"
   ClearErrors
-  ReadRegStr $1 ${RegKey} "Software\Classes\FirefoxHTML-$AppUserModelID\ShellEx\IconHandler" ""
+  ReadRegStr $0 ${RegKey} "Software\Classes\$3\DefaultIcon" ""
+  ${If} ${Errors}
+    StrCpy $3 "FirefoxHTML"
+  ${EndIf}
+
+  ClearErrors
+  ReadRegStr $1 ${RegKey} "Software\Classes\$3\ShellEx\IconHandler" ""
   ${Unless} ${Errors}
-    ReadRegStr $1 ${RegKey} "Software\Classes\FirefoxHTML-$AppUserModelID\DefaultIcon" ""
+    ReadRegStr $1 ${RegKey} "Software\Classes\$3\DefaultIcon" ""
     ${GetLongPath} "$INSTDIR\${FileMainEXE}" $2
     ${If} "$1" != "$2,1"
-      WriteRegStr ${RegKey} "Software\Classes\FirefoxHTML-$AppUserModelID\DefaultIcon" "" "$2,1"
+      WriteRegStr ${RegKey} "Software\Classes\$3\DefaultIcon" "" "$2,1"
     ${EndIf}
   ${EndUnless}
 !macroend
 !define FixShellIconHandler "!insertmacro FixShellIconHandler"
 
 ; Add Software\Mozilla\ registry entries (uses SHCTX).
 !macro SetAppKeys
   ; Check if this is an ESR release and if so add registry values so it is
@@ -601,17 +624,17 @@
     ; value from both HKCU and HKLM if it set to FirefoxHTML.
     ${If} "$0" == "FirefoxHTML"
       DeleteRegValue HKCU "Software\Classes\${FILE_TYPE}" ""
     ${EndIf}
     ${If} "$1" == "FirefoxHTML"
       DeleteRegValue HKLM "Software\Classes\${FILE_TYPE}" ""
     ${EndIf}
   ${ElseIf} "$0" == "FirefoxHTML"
-    ; Since KHCU is set to FirefoxHTML remove FirefoxHTML as the default value
+    ; Since HKCU is set to FirefoxHTML remove FirefoxHTML as the default value
     ; from HKCU if HKLM is set to a value other than an empty string.
     ${If} "$1" != ""
       DeleteRegValue HKCU "Software\Classes\${FILE_TYPE}" ""
     ${EndIf}
   ${EndIf}
 !macroend
 !define FixBadFileAssociation "!insertmacro FixBadFileAssociation"
 
@@ -663,22 +686,34 @@
   ; for this install location.
 
   ${IsHandlerForInstallDir} "FirefoxHTML-$AppUserModelID" $R9
   ${If} "$R9" == "true"
     ; An empty string is used for the 5th param because FirefoxHTML is not a
     ; protocol handler.
     ${AddDisabledDDEHandlerValues} "FirefoxHTML-$AppUserModelID" "$2" "$8,1" \
                                    "${AppRegName} HTML Document" ""
+  ${Else}
+    ${IsHandlerForInstallDir} "FirefoxHTML" $R9
+    ${If} "$R9" == "true"
+      ${AddDisabledDDEHandlerValues} "FirefoxHTML" "$2" "$8,1" \
+                                     "${AppRegName} HTML Document" ""
+    ${EndIf}
   ${EndIf}
 
   ${IsHandlerForInstallDir} "FirefoxURL-$AppUserModelID" $R9
   ${If} "$R9" == "true"
     ${AddDisabledDDEHandlerValues} "FirefoxURL-$AppUserModelID" "$2" "$8,1" \
                                    "${AppRegName} URL" "true"
+  ${Else}
+    ${IsHandlerForInstallDir} "FirefoxURL" $R9
+    ${If} "$R9" == "true"
+      ${AddDisabledDDEHandlerValues} "FirefoxURL" "$2" "$8,1" \
+                                     "${AppRegName} URL" "true"
+    ${EndIf}
   ${EndIf}
 
   ; An empty string is used for the 4th & 5th params because the following
   ; protocol handlers already have a display name and the additional keys
   ; required for a protocol handler.
   ${IsHandlerForInstallDir} "ftp" $R9
   ${If} "$R9" == "true"
     ${AddDisabledDDEHandlerValues} "ftp" "$2" "$8,1" "" ""
@@ -1210,33 +1245,45 @@
 ; Sets this installation as the default browser by setting the registry keys
 ; under HKEY_CURRENT_USER via registry calls and using the AppAssocReg NSIS
 ; plugin. This is a function instead of a macro so it is
 ; easily called from an elevated instance of the binary. Since this can be
 ; called by an elevated instance logging is not performed in this function.
 Function SetAsDefaultAppUserHKCU
   ; Only set as the user's StartMenuInternet browser if the StartMenuInternet
   ; registry keys are for this install.
-  ${StrFilter} "${FileMainEXE}" "+" "" "" $R9
+  StrCpy $R9 "${AppRegName}-$AppUserModelID"
   ClearErrors
-  ReadRegStr $0 HKCU "Software\Clients\StartMenuInternet\$R9-$AppUserModelID\DefaultIcon" ""
+  ReadRegStr $0 HKCU "Software\Clients\StartMenuInternet\$R9\DefaultIcon" ""
   ${If} ${Errors}
   ${OrIf} ${AtMostWin2008R2}
     ClearErrors
-    ReadRegStr $0 HKLM "Software\Clients\StartMenuInternet\$R9-$AppUserModelID\DefaultIcon" ""
+    ReadRegStr $0 HKLM "Software\Clients\StartMenuInternet\$R9\DefaultIcon" ""
   ${EndIf}
   ${Unless} ${Errors}
-    ${GetPathFromString} "$0" $0
-    ${GetParent} "$0" $0
-    ${If} ${FileExists} "$0"
-      ${GetLongPath} "$0" $0
-      ${If} "$0" == "$INSTDIR"
-        WriteRegStr HKCU "Software\Clients\StartMenuInternet" "" "$R9-$AppUserModelID"
+    WriteRegStr HKCU "Software\Clients\StartMenuInternet" "" "$R9"
+  ${Else}
+    ${StrFilter} "${FileMainEXE}" "+" "" "" $R9
+    ClearErrors
+    ReadRegStr $0 HKCU "Software\Clients\StartMenuInternet\$R9\DefaultIcon" ""
+    ${If} ${Errors}
+    ${OrIf} ${AtMostWin2008R2}
+      ClearErrors
+      ReadRegStr $0 HKLM "Software\Clients\StartMenuInternet\$R9\DefaultIcon" ""
+    ${EndIf}
+    ${Unless} ${Errors}
+      ${GetPathFromString} "$0" $0
+      ${GetParent} "$0" $0
+      ${If} ${FileExists} "$0"
+        ${GetLongPath} "$0" $0
+        ${If} "$0" == "$INSTDIR"
+          WriteRegStr HKCU "Software\Clients\StartMenuInternet" "" "$R9"
+        ${EndIf}
       ${EndIf}
-    ${EndIf}
+    ${EndUnless}
   ${EndUnless}
 
   SetShellVarContext current  ; Set SHCTX to the current user (e.g. HKCU)
 
   ${If} ${AtLeastWin8}
     ${SetStartMenuInternet} "HKCU"
     ${FixShellIconHandler} "HKCU"
     ${FixClassKeys} ; Does not use SHCTX
@@ -1244,21 +1291,21 @@ Function SetAsDefaultAppUserHKCU
 
   ${SetHandlers}
 
   ; Only register as the handler if the app registry name
   ; exists under the RegisteredApplications registry key. The protocol and
   ; file handlers set previously at the user level will associate this install
   ; as the default browser.
   ClearErrors
-  ReadRegStr $0 HKLM "Software\RegisteredApplications" "${AppRegName}-$AppUserModelID"
+  ReadRegStr $0 HKLM "Software\RegisteredApplications" "$R9"
   ${Unless} ${Errors}
     ; This is all protected by a user choice hash in Windows 8 so it won't
     ; help, but it also won't hurt.
-    AppAssocReg::SetAppAsDefaultAll "${AppRegName}-$AppUserModelID"
+    AppAssocReg::SetAppAsDefaultAll "$R9"
   ${EndUnless}
   ${RemoveDeprecatedKeys}
   ${MigrateTaskBarShortcut}
 FunctionEnd
 
 ; Helper for updating the shortcut application model IDs.
 Function FixShortcutAppModelIDs
   ${If} ${AtLeastWin7}
@@ -1300,24 +1347,34 @@ Function SetAsDefaultAppUser
     Return ; Nothing more needs to be done
   ${EndIf}
 
   ; Before Win8, it is only possible to set this installation of the application
   ; as the StartMenuInternet handler if it was added to the HKLM
   ; StartMenuInternet registry keys.
   ; http://support.microsoft.com/kb/297878
 
-  ; Check if this install location registered as the StartMenuInternet client
+  ; Check if this install location registered as a StartMenuInternet client
   ClearErrors
   ReadRegStr $0 HKCU "Software\Clients\StartMenuInternet\${AppRegName}-$AppUserModelID\DefaultIcon" ""
   ${If} ${Errors}
   ${OrIf} ${AtMostWin2008R2}
     ClearErrors
     ReadRegStr $0 HKLM "Software\Clients\StartMenuInternet\${AppRegName}-$AppUserModelID\DefaultIcon" ""
   ${EndIf}
+  ${If} ${Errors}
+    ${StrFilter} "${FileMainEXE}" "+" "" "" $R9
+    ClearErrors
+    ReadRegStr $0 HKCU "Software\Clients\StartMenuInternet\$R9\DefaultIcon" ""
+  ${EndIf}
+  ${If} ${Errors}
+  ${OrIf} ${AtMostWin2008R2}
+    ClearErrors
+    ReadRegStr $0 HKLM "Software\Clients\StartMenuInternet\$R9\DefaultIcon" ""
+  ${EndIf}
 
   ${Unless} ${Errors}
     ${GetPathFromString} "$0" $0
     ${GetParent} "$0" $0
     ${If} ${FileExists} "$0"
       ${GetLongPath} "$0" $0
       ${If} "$0" == "$INSTDIR"
         ; Check if this is running in an elevated process
--- a/browser/modules/URLBarZoom.jsm
+++ b/browser/modules/URLBarZoom.jsm
@@ -5,47 +5,71 @@
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = [ "URLBarZoom" ];
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 var URLBarZoom = {
-
   init(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);
+    aWindow.addEventListener("EndSwapDocShells", onEndSwapDocShells, true);
+    aWindow.addEventListener("unload", () => {
+      aWindow.removeEventListener("EndSwapDocShells", onEndSwapDocShells, true);
+    }, {once: true});
   },
 }
 
-function updateZoomButton(aSubject, aTopic) {
-  let win = aSubject.ownerGlobal;
+function fullZoomObserver(aSubject, aTopic) {
+  // If the tab was the last one in its window and has been dragged to another
+  // window, the original browser's window will be unavailable here. Since that
+  // window is closing, we can just ignore this notification.
+  if (!aSubject.ownerGlobal) {
+    return;
+  }
+
+  // Only allow pulse animation for zoom changes, not tab switching.
+  let animate = (aTopic != "browser-fullZoom:location-change");
+  updateZoomButton(aSubject, animate);
+}
+
+function onEndSwapDocShells(event) {
+  updateZoomButton(event.originalTarget);
+}
+
+function updateZoomButton(aBrowser, aAnimate = false) {
+  let win = aBrowser.ownerGlobal;
+  if (aBrowser != win.gBrowser.selectedBrowser) {
+    return;
+  }
+
   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;
   }
+
+  let zoomFactor = Math.round(win.ZoomManager.zoom * 100);
   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") {
+    if (aAnimate) {
       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;
+    // Hide button if zoom is at 100%
+    zoomResetButton.hidden = true;
   }
 }
+
+Services.obs.addObserver(fullZoomObserver, "browser-fullZoom:zoomChange", false);
+Services.obs.addObserver(fullZoomObserver, "browser-fullZoom:zoomReset", false);
+Services.obs.addObserver(fullZoomObserver, "browser-fullZoom:location-change", false);
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -1,18 +1,18 @@
 # -*- 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/.
 
-BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
+BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
 XPCSHELL_TESTS_MANIFESTS += [
     'test/unit/social/xpcshell.ini',
-    'test/xpcshell/xpcshell.ini',
+    'test/unit/xpcshell.ini',
 ]
 
 EXTRA_JS_MODULES += [
     'AboutHome.jsm',
     'AboutNewTab.jsm',
     'AttributionCode.jsm',
     'BrowserUITelemetry.jsm',
     'BrowserUsageTelemetry.jsm',
rename from browser/modules/test/.eslintrc.js
rename to browser/modules/test/browser/.eslintrc.js
--- a/browser/modules/test/.eslintrc.js
+++ b/browser/modules/test/browser/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../testing/mochitest/browser.eslintrc.js"
+    "../../../../testing/mochitest/browser.eslintrc.js"
   ]
 };
rename from browser/modules/test/browser.ini
rename to browser/modules/test/browser/browser.ini
--- a/browser/modules/test/browser.ini
+++ b/browser/modules/test/browser/browser.ini
@@ -16,18 +16,18 @@ support-files =
   !/browser/components/search/test/testEngine.xml
 [browser_NetworkPrioritizer.js]
 [browser_PermissionUI.js]
 [browser_PermissionUI_prompts.js]
 [browser_ProcessHangNotifications.js]
 skip-if = !e10s
 [browser_SelfSupportBackend.js]
 support-files =
-  ../../components/uitour/test/uitour.html
-  ../../components/uitour/UITour-lib.js
+  ../../../components/uitour/test/uitour.html
+  ../../../components/uitour/UITour-lib.js
 [browser_SitePermissions.js]
 [browser_SitePermissions_combinations.js]
 [browser_SitePermissions_expiry.js]
 [browser_SitePermissions_tab_urls.js]
 [browser_taskbar_preview.js]
 skip-if = os != "win"
 [browser_UnsubmittedCrashHandler.js]
 run-if = crashreporter
rename from browser/modules/test/browser_BrowserUITelemetry_buckets.js
rename to browser/modules/test/browser/browser_BrowserUITelemetry_buckets.js
rename from browser/modules/test/browser_BrowserUITelemetry_defaults.js
rename to browser/modules/test/browser/browser_BrowserUITelemetry_defaults.js
rename from browser/modules/test/browser_BrowserUITelemetry_sidebar.js
rename to browser/modules/test/browser/browser_BrowserUITelemetry_sidebar.js
rename from browser/modules/test/browser_BrowserUITelemetry_syncedtabs.js
rename to browser/modules/test/browser/browser_BrowserUITelemetry_syncedtabs.js
rename from browser/modules/test/browser_ContentSearch.js
rename to browser/modules/test/browser/browser_ContentSearch.js
--- a/browser/modules/test/browser_ContentSearch.js
+++ b/browser/modules/test/browser/browser_ContentSearch.js
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const TEST_MSG = "ContentSearchTest";
 const CONTENT_SEARCH_MSG = "ContentSearch";
 const TEST_CONTENT_SCRIPT_BASENAME = "contentSearch.js";
 
 var gMsgMan;
 /* eslint no-undef:"error" */
-/* import-globals-from ../../components/search/test/head.js */
+/* import-globals-from ../../../components/search/test/head.js */
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/browser/components/search/test/head.js",
   this);
 
 let originalEngine = Services.search.currentEngine;
 
 add_task(function* setup() {
   yield promiseNewEngine("testEngine.xml", {
rename from browser/modules/test/browser_NetworkPrioritizer.js
rename to browser/modules/test/browser/browser_NetworkPrioritizer.js
rename from browser/modules/test/browser_PermissionUI.js
rename to browser/modules/test/browser/browser_PermissionUI.js
rename from browser/modules/test/browser_PermissionUI_prompts.js
rename to browser/modules/test/browser/browser_PermissionUI_prompts.js
rename from browser/modules/test/browser_ProcessHangNotifications.js
rename to browser/modules/test/browser/browser_ProcessHangNotifications.js
rename from browser/modules/test/browser_SelfSupportBackend.js
rename to browser/modules/test/browser/browser_SelfSupportBackend.js
rename from browser/modules/test/browser_SitePermissions.js
rename to browser/modules/test/browser/browser_SitePermissions.js
rename from browser/modules/test/browser_SitePermissions_combinations.js
rename to browser/modules/test/browser/browser_SitePermissions_combinations.js
rename from browser/modules/test/browser_SitePermissions_expiry.js
rename to browser/modules/test/browser/browser_SitePermissions_expiry.js
rename from browser/modules/test/browser_SitePermissions_tab_urls.js
rename to browser/modules/test/browser/browser_SitePermissions_tab_urls.js
rename from browser/modules/test/browser_UnsubmittedCrashHandler.js
rename to browser/modules/test/browser/browser_UnsubmittedCrashHandler.js
rename from browser/modules/test/browser_UsageTelemetry.js
rename to browser/modules/test/browser/browser_UsageTelemetry.js
rename from browser/modules/test/browser_UsageTelemetry_content.js
rename to browser/modules/test/browser/browser_UsageTelemetry_content.js
rename from browser/modules/test/browser_UsageTelemetry_content_aboutHome.js
rename to browser/modules/test/browser/browser_UsageTelemetry_content_aboutHome.js
rename from browser/modules/test/browser_UsageTelemetry_private_and_restore.js
rename to browser/modules/test/browser/browser_UsageTelemetry_private_and_restore.js
rename from browser/modules/test/browser_UsageTelemetry_searchbar.js
rename to browser/modules/test/browser/browser_UsageTelemetry_searchbar.js
rename from browser/modules/test/browser_UsageTelemetry_urlbar.js
rename to browser/modules/test/browser/browser_UsageTelemetry_urlbar.js
rename from browser/modules/test/browser_bug1319078.js
rename to browser/modules/test/browser/browser_bug1319078.js
rename from browser/modules/test/browser_taskbar_preview.js
rename to browser/modules/test/browser/browser_taskbar_preview.js
rename from browser/modules/test/browser_urlBar_zoom.js
rename to browser/modules/test/browser/browser_urlBar_zoom.js
rename from browser/modules/test/contentSearch.js
rename to browser/modules/test/browser/contentSearch.js
rename from browser/modules/test/contentSearchBadImage.xml
rename to browser/modules/test/browser/contentSearchBadImage.xml
rename from browser/modules/test/contentSearchSuggestions.sjs
rename to browser/modules/test/browser/contentSearchSuggestions.sjs
rename from browser/modules/test/contentSearchSuggestions.xml
rename to browser/modules/test/browser/contentSearchSuggestions.xml
--- a/browser/modules/test/contentSearchSuggestions.xml
+++ b/browser/modules/test/browser/contentSearchSuggestions.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
 <ShortName>browser_ContentSearch contentSearchSuggestions.xml</ShortName>
-<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/modules/test/contentSearchSuggestions.sjs?{searchTerms}"/>
+<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/modules/test/browser/contentSearchSuggestions.sjs?{searchTerms}"/>
 <Url type="text/html" method="GET" template="http://browser-ContentSearch.com/contentSearchSuggestions" rel="searchform"/>
 </SearchPlugin>
rename from browser/modules/test/head.js
rename to browser/modules/test/browser/head.js
rename from browser/modules/test/usageTelemetrySearchSuggestions.sjs
rename to browser/modules/test/browser/usageTelemetrySearchSuggestions.sjs
rename from browser/modules/test/usageTelemetrySearchSuggestions.xml
rename to browser/modules/test/browser/usageTelemetrySearchSuggestions.xml
--- a/browser/modules/test/usageTelemetrySearchSuggestions.xml
+++ b/browser/modules/test/browser/usageTelemetrySearchSuggestions.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
 <ShortName>browser_UsageTelemetry usageTelemetrySearchSuggestions.xml</ShortName>
-<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/modules/test/usageTelemetrySearchSuggestions.sjs?{searchTerms}"/>
+<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/modules/test/browser/usageTelemetrySearchSuggestions.sjs?{searchTerms}"/>
 <Url type="text/html" method="GET" template="http://example.com" rel="searchform"/>
 </SearchPlugin>
rename from browser/modules/test/xpcshell/.eslintrc.js
rename to browser/modules/test/unit/.eslintrc.js
deleted file mode 100644
--- a/browser/modules/test/unit/social/.eslintrc.js
+++ /dev/null
@@ -1,7 +0,0 @@
-"use strict";
-
-module.exports = {
-  "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
-  ]
-};
--- a/browser/modules/test/unit/social/head.js
+++ b/browser/modules/test/unit/social/head.js
@@ -30,17 +30,16 @@ const gProfD = do_get_profile();
 
 function createAppInfo(ID, name, version, platformVersion = "1.0") {
   let tmp = {};
   Cu.import("resource://testing-common/AppInfo.jsm", tmp);
   tmp.updateAppInfo({
     ID, name, version, platformVersion,
     crashReporter: true,
   });
-  gAppInfo = tmp.getAppInfo();
 }
 
 function initApp() {
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
   // prepare a blocklist file for the blocklist service
   var blocklistFile = gProfD.clone();
   blocklistFile.append("blocklist.xml");
   if (blocklistFile.exists())
rename from browser/modules/test/xpcshell/test_AttributionCode.js
rename to browser/modules/test/unit/test_AttributionCode.js
rename from browser/modules/test/xpcshell/test_DirectoryLinksProvider.js
rename to browser/modules/test/unit/test_DirectoryLinksProvider.js
rename from browser/modules/test/xpcshell/test_E10SUtils_nested_URIs.js
rename to browser/modules/test/unit/test_E10SUtils_nested_URIs.js
rename from browser/modules/test/xpcshell/test_LaterRun.js
rename to browser/modules/test/unit/test_LaterRun.js
rename from browser/modules/test/xpcshell/test_SitePermissions.js
rename to browser/modules/test/unit/test_SitePermissions.js
rename from browser/modules/test/xpcshell/xpcshell.ini
rename to browser/modules/test/unit/xpcshell.ini
deleted file mode 100644
--- a/browser/themes/linux/browser-lightweightTheme.css
+++ /dev/null
@@ -1,31 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-%include linuxShared.inc
-%filter substitution
-
-/*
- * LightweightThemeListener will append the current lightweight theme's header
- * image to the background-image for each of the following rulesets.
- */
-
-/* Lightweight theme on tabs */
-#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-start[selected=true]:-moz-lwtheme::before,
-#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-end[selected=true]:-moz-lwtheme::before {
-  background-attachment: scroll, fixed;
-  background-color: transparent;
-  background-image: @fgTabTextureLWT@;/*, lwtHeader;*/
-  background-position: 0 0, right top;
-  background-repeat: repeat-x, no-repeat;
-}
-
-#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-middle[selected=true]:-moz-lwtheme {
-  background-attachment: scroll, scroll, fixed;
-  background-color: transparent;
-  background-image: url(chrome://browser/skin/tabbrowser/tab-active-middle.png),
-                    @fgTabTextureLWT@;/*,
-                    lwtHeader;*/
-  background-position: 0 0, 0 0, right top;
-  background-repeat: repeat-x, repeat-x, no-repeat;
-}
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -1519,25 +1519,16 @@ notification.pluginVulnerable > .notific
 %include ../shared/customizableui/customizeMode.inc.css
 
 #main-window[customize-entered] > #tab-view-deck {
   background-image: url("chrome://browser/skin/customizableui/customizeMode-gridTexture.png"),
                     linear-gradient(to bottom, #bcbcbc, #b5b5b5);
   background-attachment: fixed;
 }
 
-#main-window[customization-lwtheme] > #tab-view-deck:-moz-lwtheme {
-  background-repeat: no-repeat;
-  background-position: right top;
-  background-attachment: fixed;
-  /* The image will get set from CustomizeMode.jsm */
-  background-image: none;
-  background-color: transparent;
-}
-
 #main-window[customization-lwtheme]:-moz-lwtheme {
   background-image: url("chrome://browser/skin/customizableui/customizeMode-gridTexture.png"),
                     url("chrome://browser/skin/customizableui/background-noise-toolbar.png"),
                     linear-gradient(to bottom, #bcbcbc, #b5b5b5);
   background-color: #b5b5b5;
   background-repeat: repeat;
   background-attachment: fixed;
   background-position: left top;
--- a/browser/themes/linux/downloads/indicator.css
+++ b/browser/themes/linux/downloads/indicator.css
@@ -1,12 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+%include ../../shared/downloads/indicator.inc.css
+
 /*** Status and progress indicator ***/
 
 #downloads-animation-container {
   min-height: 1px;
   min-width: 1px;
   height: 1px;
   margin-bottom: -1px;
   /* Makes the outermost animation container element positioned, so that its
@@ -16,41 +18,41 @@
   /* The selected tab may overlap #downloads-indicator-notification */
   z-index: 5;
 }
 
 /*** Main indicator icon ***/
 
 @media not all and (min-resolution: 1.1dppx) {
   #downloads-button {
-    --downloads-indicator-icon: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 0, 198, 18, 180);
-    --downloads-indicator-icon-attention: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 18, 198, 36, 180);
-    --downloads-indicator-icon-inverted: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 0, 198, 18, 180);
-    --downloads-indicator-icon-attention-inverted: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 18, 198, 36, 180);
+    --downloads-indicator-image: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 0, 198, 18, 180);
+    --downloads-indicator-image-attention: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 18, 198, 36, 180);
+    --downloads-indicator-image-inverted: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 0, 198, 18, 180);
+    --downloads-indicator-image-attention-inverted: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 18, 198, 36, 180);
   }
 }
 
 @media (min-resolution: 1.1dppx) {
   #downloads-button {
-    --downloads-indicator-icon: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 0, 396, 36, 360);
-    --downloads-indicator-icon-attention: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 36, 396, 72, 360);
-    --downloads-indicator-icon-inverted: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"), 0, 396, 36, 360);
-    --downloads-indicator-icon-attention-inverted: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"), 36, 396, 72, 360);
+    --downloads-indicator-image: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 0, 396, 36, 360);
+    --downloads-indicator-image-attention: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 36, 396, 72, 360);
+    --downloads-indicator-image-inverted: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"), 0, 396, 36, 360);
+    --downloads-indicator-image-attention-inverted: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"), 36, 396, 72, 360);
   }
 }
 
 #downloads-button[cui-areatype="toolbar"] > #downloads-indicator-anchor > #downloads-indicator-icon {
-  background: var(--downloads-indicator-icon) center no-repeat;
+  background: var(--downloads-indicator-image) center no-repeat;
   width: 18px;
   height: 18px;
   background-size: 18px;
 }
 
 toolbar[brighttext] #downloads-button[cui-areatype="toolbar"]:not([attention="success"]) > #downloads-indicator-anchor > #downloads-indicator-icon {
-  background-image: var(--downloads-indicator-icon-inverted);
+  background-image: var(--downloads-indicator-image-inverted);
 }
 
 #downloads-button[attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
 #downloads-button[attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
   display: -moz-box;
   height: 8px;
   width: 8px;
   min-width: 0;
@@ -74,46 +76,46 @@ toolbar[brighttext] #downloads-button[cu
 }
 
 #downloads-button[cui-areatype="toolbar"][attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive,
 #downloads-button[cui-areatype="toolbar"][attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive {
   filter: none;
 }
 
 #downloads-button[cui-areatype="toolbar"][attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
-  background-image: var(--downloads-indicator-icon-attention);
+  background-image: var(--downloads-indicator-image-attention);
 }
 
 toolbar[brighttext] #downloads-button[cui-areatype="toolbar"][attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
-  background-image: var(--downloads-indicator-icon-attention-inverted);
+  background-image: var(--downloads-indicator-image-attention-inverted);
 }
 
 #downloads-button[cui-areatype="menu-panel"][attention="success"] {
   list-style-image: url("chrome://browser/skin/downloads/download-glow-menuPanel.png");
   -moz-image-region: auto;
 }
 
 /* In the next few rules, we use :not([counter]) as a shortcut that is
    equivalent to -moz-any([progress], [paused]). */
 
 #downloads-button:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
-  background: var(--downloads-indicator-icon) center no-repeat;
+  background: var(--downloads-indicator-image) center no-repeat;
   background-size: 12px;
 }
 
 toolbar[brighttext] #downloads-button:not([counter]):not([attention="success"]) > #downloads-indicator-anchor > #downloads-button-progress-area > #downloads-indicator-counter {
-  background-image: var(--downloads-indicator-icon-inverted);
+  background-image: var(--downloads-indicator-image-inverted);
 }
 
 #downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
-  background-image: var(--downloads-indicator-icon-attention);
+  background-image: var(--downloads-indicator-image-attention);
 }
 
 toolbar[brighttext] #downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
-  background-image: var(--downloads-indicator-icon-attention-inverted);
+  background-image: var(--downloads-indicator-image-attention-inverted);
 }
 
 /*** Download notifications ***/
 
 #downloads-indicator-notification {
   opacity: 0;
   background-size: 16px;
   background-position: center;
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -7,17 +7,16 @@ browser.jar:
 % override chrome://global/skin/icons/warning-16.png moz-icon://stock/gtk-dialog-warning?size=menu
 #include ../shared/jar.inc.mn
   skin/classic/browser/sanitizeDialog.css
   skin/classic/browser/aboutSessionRestore-window-icon.png
   skin/classic/browser/aboutSyncTabs.css
 * skin/classic/browser/syncedtabs/sidebar.css     (syncedtabs/sidebar.css)
 * skin/classic/browser/browser.css
 * skin/classic/browser/compacttheme.css
-* skin/classic/browser/browser-lightweightTheme.css
   skin/classic/browser/click-to-play-warning-stripes.png
   skin/classic/browser/Info.png
   skin/classic/browser/menuPanel-customize.png
   skin/classic/browser/menuPanel-customize@2x.png
   skin/classic/browser/menuPanel-exit.png
   skin/classic/browser/menuPanel-exit@2x.png
   skin/classic/browser/menuPanel-help.png
   skin/classic/browser/menuPanel-help@2x.png
deleted file mode 100644
--- a/browser/themes/osx/browser-lightweightTheme.css
+++ /dev/null
@@ -1,38 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-%include shared.inc
-
-/*
- * LightweightThemeListener will append the current lightweight theme's header
- * image to the background-image for each of the following rulesets.
- */
-
-/* Lightweight theme on tabs */
-#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-start[selected=true]:-moz-lwtheme::before,
-#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-end[selected=true]:-moz-lwtheme::before {
-  background-attachment: scroll, fixed;
-  background-color: transparent;
-  background-image: @fgTabTextureLWT@;/*, lwtHeader;*/
-  background-position: 0 0, right top;
-  background-repeat: repeat-x, no-repeat;
-}
-
-#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-middle[selected=true]:-moz-lwtheme {
-  background-attachment: scroll, scroll, fixed;
-  background-color: transparent;
-  background-image: url(chrome://browser/skin/tabbrowser/tab-active-middle.png),
-                    @fgTabTextureLWT@;/*,
-                    lwtHeader;*/
-  background-position: 0 0, 0 0, right top;
-  background-repeat: repeat-x, repeat-x, no-repeat;
-}
-
-@media (min-resolution: 2dppx) {
-  #tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-middle[selected=true]:-moz-lwtheme {
-    background-image: url(chrome://browser/skin/tabbrowser/tab-active-middle@2x.png),
-                      @fgTabTextureLWT@;/*,
-                      lwtHeader;*/
-  }
-}
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -3185,24 +3185,22 @@ menulist.translate-infobar-element > .me
 #main-window[privatebrowsingmode=temporary]:not([tabsintitlebar])[customize-entered] > #titlebar,
 #main-window[customize-entered] > #tab-view-deck {
   background-image: url("chrome://browser/skin/customizableui/customizeMode-gridTexture.png"),
                     url("chrome://browser/skin/customizableui/background-noise-toolbar.png"),
                     linear-gradient(to bottom, rgb(233,233,233), rgb(178,178,178) 40px);
   background-attachment: fixed;
 }
 
-#main-window[tabsintitlebar][customization-lwtheme] > #titlebar:-moz-lwtheme,
-#main-window[customization-lwtheme] > #tab-view-deck:-moz-lwtheme {
+#main-window[tabsintitlebar][customization-lwtheme] > #titlebar:-moz-lwtheme {
   background-repeat: no-repeat;
   background-position: right top;
   background-attachment: fixed;
-  /* The image will get set from CustomizeMode.jsm */
-  background-image: none;
-  background-color: transparent;
+  background-image: var(--lwt-header-image);
+  background-color: var(--lwt-accent-color);
 }
 
 #main-window[customization-lwtheme]:-moz-lwtheme {
   background-image: url("chrome://browser/skin/customizableui/customizeMode-gridTexture.png"),
                     url("chrome://browser/skin/customizableui/background-noise-toolbar.png");
   background-color: rgb(178,178,178);
   background-repeat: repeat;
   background-attachment: fixed;
--- a/browser/themes/osx/downloads/indicator.css
+++ b/browser/themes/osx/downloads/indicator.css
@@ -1,12 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+%include ../../shared/downloads/indicator.inc.css
+
 /*** Status and progress indicator ***/
 
 #downloads-indicator-anchor {
   min-width: 18px;
   min-height: 18px;
 }
 
 #downloads-animation-container {
@@ -19,23 +21,39 @@
      This is required by the animated event notification. */
   position: relative;
   /* The selected tab may overlap #downloads-indicator-notification */
   z-index: 5;
 }
 
 /*** Main indicator icon ***/
 
+#downloads-button {
+  --downloads-indicator-image: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 0, 198, 18, 180);
+  --downloads-indicator-image-attention: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 36, 198, 54, 180);
+  --downloads-indicator-image-inverted: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 0, 198, 18, 180);
+  --downloads-indicator-image-attention-inverted: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 36, 198, 54, 180);
+}
+
+@media (min-resolution: 2dppx) {
+  #downloads-button {
+    --downloads-indicator-image: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 0, 396, 36, 360);
+    --downloads-indicator-image-attention: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 72, 396, 108, 360);
+    --downloads-indicator-image-inverted: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"), 0, 396, 36, 360);
+    --downloads-indicator-image-attention-inverted: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"), 72, 396, 108, 360);
+  }
+}
+
 #downloads-indicator-icon {
-  background: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"),
-                              0, 198, 18, 180) center no-repeat;
+  background: var(--downloads-indicator-image) center no-repeat;
+  background-size: 18px;
 }
 
 toolbar[brighttext] #downloads-indicator-icon {
-  background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 0, 198, 18, 180);
+  background-image: var(--downloads-indicator-image-inverted);
 }
 
 #downloads-button[attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
 #downloads-button[attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
   display: -moz-box;
   height: 8px;
   width: 8px;
   min-width: 0;
@@ -59,100 +77,67 @@ toolbar[brighttext] #downloads-indicator
 }
 
 #downloads-button[attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive,
 #downloads-button[attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive {
   filter: none;
 }
 
 #downloads-button[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
-  background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 36, 198, 54, 180);
+  background-image: var(--downloads-indicator-image-attention);
 }
 
 toolbar[brighttext] #downloads-button[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
-  background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 36, 198, 54, 180);
+  background-image: var(--downloads-indicator-image-attention-inverted);
 }
 
 #downloads-button[cui-areatype="menu-panel"][attention="success"] {
   list-style-image: url("chrome://browser/skin/downloads/download-glow-menuPanel.png");
   -moz-image-region: auto;
 }
 
 /* In the next few rules, we use :not([counter]) as a shortcut that is
    equivalent to -moz-any([progress], [paused]). */
 
 #downloads-button:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
-  background: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"),
-                              0, 198, 18, 180) center no-repeat;
+  background: var(--downloads-indicator-image) center no-repeat;
   background-size: 12px;
 }
 
 toolbar[brighttext] #downloads-button:not([counter]):not([attention="success"]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
-  background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 0, 198, 18, 180);
+  background-image: var(--downloads-indicator-image-inverted);
 }
 
 #downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
-  background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 36, 198, 54, 180);
+  background-image: var(--downloads-indicator-image-attention);
 }
 
 toolbar[brighttext] #downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
-  background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 36, 198, 54, 180);
+  background-image: var(--downloads-indicator-image-attention-inverted);
 }
 
 @media (min-resolution: 2dppx) {
-  #downloads-indicator-icon {
-    background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 0, 396, 36, 360);
-    background-size: 18px;
-  }
-
-  toolbar[brighttext] #downloads-indicator-icon {
-    background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"), 0, 396, 36, 360);
-  }
-
-  #downloads-button:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
-    background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 0, 396, 36, 360);
-  }
-
-  toolbar[brighttext] #downloads-button:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
-    background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"),
-                                      0, 396, 36, 360);
-  }
-
-  #downloads-button[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
-    background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 72, 396, 108, 360);
-  }
-
-  toolbar[brighttext] #downloads-button[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
-    background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"), 72, 396, 108, 360);
-  }
-
   #downloads-button[cui-areatype="menu-panel"][attention="success"] {
     list-style-image: url("chrome://browser/skin/downloads/download-glow-menuPanel@2x.png");
   }
-
-  #downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
-    background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 72, 396, 108, 360);
-  }
-
-  toolbar[brighttext] #downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
-    background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"), 72, 396, 108, 360);
-  }
 }
 
 /*** Download notifications ***/
 
 #downloads-indicator-notification {
   opacity: 0;
   background-size: 16px;
   background-position: center;
   background-repeat: no-repeat;
   width: 16px;
   height: 16px;
 }
 
+/*** Progress bar and text ***/
+
 @keyframes downloadsIndicatorNotificationStartRight {
   from { opacity: 0; transform: translate(-128px, 128px) scale(8); }
   20%  { opacity: .85; animation-timing-function: ease-out; }
   to   { opacity: 0; transform: translate(0) scale(1); }
 }
 
 @keyframes downloadsIndicatorNotificationStartLeft {
   from { opacity: 0; transform: translate(128px, 128px) scale(8); }
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -6,17 +6,16 @@ browser.jar:
 % skin browser classic/1.0 %skin/classic/browser/
 #include ../shared/jar.inc.mn
   skin/classic/browser/sanitizeDialog.css
   skin/classic/browser/aboutSessionRestore-window-icon.png
   skin/classic/browser/aboutSyncTabs.css
 * skin/classic/browser/syncedtabs/sidebar.css          (syncedtabs/sidebar.css)
 * skin/classic/browser/browser.css
 * skin/classic/browser/compacttheme.css
-* skin/classic/browser/browser-lightweightTheme.css
   skin/classic/browser/click-to-play-warning-stripes.png
   skin/classic/browser/Info.png
   skin/classic/browser/keyhole-circle.png
   skin/classic/browser/keyhole-circle@2x.png
   skin/classic/browser/subtle-pattern.png
   skin/classic/browser/menu-back.png
   skin/classic/browser/menu-forward.png
   skin/classic/browser/menuPanel-customize.png
--- a/browser/themes/shared/customizableui/customizeMode.inc.css
+++ b/browser/themes/shared/customizableui/customizeMode.inc.css
@@ -3,16 +3,32 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* Customization mode */
 
 :root {
   --drag-drop-transition-duration: .3s;
 }
 
+#main-window[customization-lwtheme] #tab-view-deck:-moz-lwtheme {
+  background-repeat: no-repeat;
+  background-position: right top;
+  background-attachment: fixed;
+  background-color: transparent;
+  background-image: -moz-image-rect(var(--lwt-header-image), 0, 100%,
+                      var(--toolbox-rect-height), 0),
+                    linear-gradient(to bottom,
+                      var(--lwt-accent-color) calc(var(--toolbox-rect-height-with-unit) - 1px),
+                      rgba(0,0,0,0.25) calc(var(--toolbox-rect-height-with-unit) - 1px),
+                      rgba(0,0,0,0.25) calc(var(--toolbox-rect-height-with-unit) + 1px),
+                      rgba(255,255,255,0.5) calc(var(--toolbox-rect-height-with-unit) + 1px),
+                      rgba(255,255,255,0.5) calc(var(--toolbox-rect-height-with-unit) + 2px),
+                      transparent calc(var(--toolbox-rect-height-with-unit) + 2px));
+}
+
 #main-window:-moz-any([customize-entering],[customize-entered]) #browser-bottombox {
   margin-bottom: 2em;
 }
 
 #main-window:-moz-any([customize-entering],[customize-entered]) #content-deck,
 #main-window:-moz-any([customize-entering],[customize-entered]) #browser-bottombox,
 #main-window:-moz-any([customize-entering],[customize-entered]) #navigator-toolbox {
   margin-left: 2em;
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/downloads/indicator.inc.css
@@ -0,0 +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/. */
+
+#downloads-indicator-icon {
+  position: relative;
+}
+
+#downloads-indicator-progress-icon {
+  background: var(--downloads-indicator-image-attention) bottom no-repeat;
+  background-size: 18px;
+  position: absolute;
+  bottom: 0;
+  width: 100%;
+  height: 0;
+  transition: height 0.5s;
+}
+
+toolbar[brighttext] #downloads-indicator-progress-icon {
+  background-image: var(--downloads-indicator-image-attention-inverted);
+}
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -378,38 +378,64 @@
   background-repeat: repeat-x;
   background-size: var(--tab-stroke-background-size), auto 100%;
   /* The padding-top combined with background-clip: content-box (the bottom-most) ensure the
      background-color doesn't extend above the top border. */
   padding-top: 2px;
 }
 
 /* Selected tab lightweight theme styles.
-   See browser-lightweightTheme.css for information about run-time changes to LWT styles. */
+   See the "Lightweight theme on tabs" section of this file
+   for information about run-time changes to LWT styles. */
 .tab-background-middle[selected=true]:-moz-lwtheme {
   background-color: transparent;
   background-image: url(chrome://browser/skin/tabbrowser/tab-active-middle.png),
-                    @fgTabTextureLWT@;/*,
-                    lwtHeader;*/
+                    @fgTabTextureLWT@;
   /* Don't stretch the LWT header images */
   background-size: var(--tab-stroke-background-size), auto 100%, auto auto;
 }
 
-/* These LWT styles are normally overridden by browser-lightweightTheme.css */
+/* These LWT styles are normally overridden by the "Lightweight theme on tabs"
+   section of this file. */
 .tab-background-start[selected=true]:-moz-lwtheme::before,
 .tab-background-end[selected=true]:-moz-lwtheme::before {
   background-image: @fgTabTextureLWT@;
 }
 
 .tab-background-start[selected=true]:-moz-lwtheme::before,
 .tab-background-end[selected=true]:-moz-lwtheme::before,
 .tab-background-middle[selected=true]:-moz-lwtheme {
   background-color: transparent;
 }
 
+/*
+ * LightweightThemeListener will set the current lightweight theme's header
+ * image to the lwt-header-image variable, used in each of the following rulesets.
+ */
+
+/* Lightweight theme on tabs */
+#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-start[selected=true]:-moz-lwtheme::before,
+#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-end[selected=true]:-moz-lwtheme::before {
+  background-attachment: scroll, fixed;
+  background-color: transparent;
+  background-image: @fgTabTextureLWT@, var(--lwt-header-image);
+  background-position: 0 0, right top;
+  background-repeat: repeat-x, no-repeat;
+}
+
+#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-middle[selected=true]:-moz-lwtheme {
+  background-attachment: scroll, scroll, fixed;
+  background-color: transparent;
+  background-image: url(chrome://browser/skin/tabbrowser/tab-active-middle.png),
+                    @fgTabTextureLWT@,
+                    var(--lwt-header-image);
+  background-position: 0 0, 0 0, right top;
+  background-repeat: repeat-x, repeat-x, no-repeat;
+}
+
 /* End selected tab */
 
 /* new tab button border and gradient on hover */
 .tabbrowser-tab:hover > .tab-stack > .tab-background:not([selected=true]),
 .tabs-newtab-button:hover {
   background-image: url(chrome://browser/skin/tabbrowser/tab-background-start.png),
                     url(chrome://browser/skin/tabbrowser/tab-background-middle.png),
                     url(chrome://browser/skin/tabbrowser/tab-background-end.png);
@@ -525,16 +551,22 @@
     background-image: url(chrome://browser/skin/tabbrowser/tab-stroke-start@2x.png);
   }
 
   .tab-background-end[selected=true]:-moz-locale-dir(ltr)::after,
   .tab-background-start[selected=true]:-moz-locale-dir(rtl)::after {
     background-image: url(chrome://browser/skin/tabbrowser/tab-stroke-end@2x.png);
   }
 
+  #tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-middle[selected=true]:-moz-lwtheme {
+    background-image: url(chrome://browser/skin/tabbrowser/tab-active-middle@2x.png),
+                      @fgTabTextureLWT@,
+                      var(--lwt-header-image);
+  }
+
   .tab-throbber[busy] {
     list-style-image: url("chrome://browser/skin/tabbrowser/connecting@2x.png");
   }
 
   .tab-icon-image {
     list-style-image: url("chrome://mozapps/skin/places/defaultFavicon@2x.png");
   }
 
deleted file mode 100644
--- a/browser/themes/windows/browser-lightweightTheme.css
+++ /dev/null
@@ -1,39 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-%include windowsShared.inc
-%filter substitution
-
-/*
- * LightweightThemeListener will append the current lightweight theme's header
- * image to the background-image for each of the following rulesets.
- */
-
-/* Lightweight theme on tabs */
-#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-start[selected=true]:-moz-lwtheme::before,
-#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-end[selected=true]:-moz-lwtheme::before {
-  background-attachment: scroll, fixed;
-  background-color: transparent;
-  background-image: @fgTabTextureLWT@;/*, lwtHeader;*/
-  background-position: 0 0, right top;
-  background-repeat: repeat-x, no-repeat;
-}
-
-#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-middle[selected=true]:-moz-lwtheme {
-  background-attachment: scroll, scroll, fixed;
-  background-color: transparent;
-  background-image: url(chrome://browser/skin/tabbrowser/tab-active-middle.png),
-                    @fgTabTextureLWT@;/*,
-                    lwtHeader;*/
-  background-position: 0 0, 0 0, right top;
-  background-repeat: repeat-x, repeat-x, no-repeat;
-}
-
-@media (min-resolution: 1.25dppx) {
-  #tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-middle[selected=true]:-moz-lwtheme {
-    background-image: url(chrome://browser/skin/tabbrowser/tab-active-middle@2x.png),
-                      @fgTabTextureLWT@;/*,
-                      lwtHeader;*/
-  }
-}
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -21,17 +21,16 @@
   --space-above-tabbar: 15px;
 
   --backbutton-urlbar-overlap: 6px;
 
   /* icon width + border + horizontal padding (without the overlap from backbutton-urlbar-overlap) */
   --forwardbutton-width: 25px;
 
   --toolbarbutton-vertical-inner-padding: 2px;
-  --toolbarbutton-vertical-outer-padding: 8px;
 
   --toolbarbutton-hover-background: rgba(0,0,0,.1);
   --toolbarbutton-hover-bordercolor: rgba(0,0,0,.2);
   --toolbarbutton-hover-boxshadow: none;
 
   --toolbarbutton-active-background: rgba(0,0,0,.15);
   --toolbarbutton-active-bordercolor: rgba(0,0,0,.3);
   --toolbarbutton-active-boxshadow: 0 0 0 1px rgba(0,0,0,.15) inset;
@@ -635,17 +634,17 @@ toolbar[brighttext] .toolbarbutton-1 > .
 #nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button {
   -moz-appearance: none;
   padding: 0;
 }
 
 #nav-bar .toolbarbutton-1:not([type=menu-button]),
 #nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button,
 #nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
-  padding: var(--toolbarbutton-vertical-outer-padding) 2px;
+  padding: 0 2px;
   -moz-box-pack: center;
 }
 
 #nav-bar #PanelUI-menu-button {
   padding-inline-start: 5px;
   padding-inline-end: 5px;
 }
 
@@ -1184,17 +1183,16 @@ toolbar[brighttext] #close-button {
   .searchbar-textbox {
     font-size: 1.15em;
     min-height: 28px;
   }
 
   :root {
     /* let toolbar buttons match the location and search bar's minimum height */
     --toolbarbutton-vertical-inner-padding: 4px;
-    --toolbarbutton-vertical-outer-padding: 5px;
   }
 }
 
 #urlbar:-moz-lwtheme,
 .searchbar-textbox:-moz-lwtheme {
   background-color: rgba(255,255,255,.8);
   color: black;
 }
@@ -2322,25 +2320,16 @@ notification.pluginVulnerable > .notific
   transform: perspective(0.01px);
 }
 
 #main-window[customize-entered] > #tab-view-deck {
   background-image: url("chrome://browser/skin/customizableui/customizeMode-gridTexture.png");
   background-attachment: fixed;
 }
 
-#main-window[customization-lwtheme] > #tab-view-deck:-moz-lwtheme {
-  background-repeat: no-repeat;
-  background-position: right top;
-  background-attachment: fixed;
-  /* The image will get set from CustomizeMode.jsm */
-  background-image: none;
-  background-color: transparent;
-}
-
 #main-window[customization-lwtheme]:-moz-lwtheme {
   background-image: url("chrome://browser/skin/customizableui/customizeMode-gridTexture.png");
   background-repeat: repeat;
   background-attachment: fixed;
   background-position: left top;
 }
 
 #main-window[customize-entered] #browser-bottombox,
--- a/browser/themes/windows/downloads/indicator.css
+++ b/browser/themes/windows/downloads/indicator.css
@@ -1,12 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+%include ../../shared/downloads/indicator.inc.css
+
 /*** Status and progress indicator ***/
 
 #downloads-animation-container {
   min-height: 1px;
   min-width: 1px;
   height: 1px;
   margin-bottom: -1px;
   /* Makes the outermost animation container element positioned, so that its
@@ -16,41 +18,41 @@
   /* The selected tab may overlap #downloads-indicator-notification */
   z-index: 5;
 }
 
 /*** Main indicator icon ***/
 
 @media not all and (min-resolution: 1.1dppx) {
   #downloads-button {
-    --downloads-indicator-icon: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 0, 198, 18, 180);
-    --downloads-indicator-icon-attention: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 18, 198, 36, 180);
-    --downloads-indicator-icon-inverted: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 0, 198, 18, 180);
-    --downloads-indicator-icon-attention-inverted: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 18, 198, 36, 180);
+    --downloads-indicator-image: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 0, 198, 18, 180);
+    --downloads-indicator-image-attention: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 18, 198, 36, 180);
+    --downloads-indicator-image-inverted: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 0, 198, 18, 180);
+    --downloads-indicator-image-attention-inverted: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 18, 198, 36, 180);
   }
 }
 
 @media (min-resolution: 1.1dppx) {
   #downloads-button {
-    --downloads-indicator-icon: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 0, 396, 36, 360);
-    --downloads-indicator-icon-attention: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 36, 396, 72, 360);
-    --downloads-indicator-icon-inverted: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"), 0, 396, 36, 360);
-    --downloads-indicator-icon-attention-inverted: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"), 36, 396, 72, 360);
+    --downloads-indicator-image: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 0, 396, 36, 360);
+    --downloads-indicator-image-attention: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 36, 396, 72, 360);
+    --downloads-indicator-image-inverted: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"), 0, 396, 36, 360);
+    --downloads-indicator-image-attention-inverted: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"), 36, 396, 72, 360);
   }
 }
 
 #downloads-indicator-icon {
-  background: var(--downloads-indicator-icon) center no-repeat;
+  background: var(--downloads-indicator-image) center no-repeat;
   width: 18px;
   height: 18px;
   background-size: 18px;
 }
 
 toolbar[brighttext] #downloads-button:not([attention="success"]) > #downloads-indicator-anchor > #downloads-indicator-icon {
-  background-image: var(--downloads-indicator-icon-inverted);
+  background-image: var(--downloads-indicator-image-inverted);
 }
 
 #downloads-button[attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
 #downloads-button[attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
   display: -moz-box;
   height: 8px;
   width: 8px;
   min-width: 0;
@@ -74,46 +76,46 @@ toolbar[brighttext] #downloads-button:no
 }
 
 #downloads-button[attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive,
 #downloads-button[attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive {
   filter: none;
 }
 
 #downloads-button[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
-  background-image: var(--downloads-indicator-icon-attention);
+  background-image: var(--downloads-indicator-image-attention);
 }
 
 toolbar[brighttext] #downloads-button[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
-  background-image: var(--downloads-indicator-icon-attention-inverted);
+  background-image: var(--downloads-indicator-image-attention-inverted);
 }
 
 #downloads-button[cui-areatype="menu-panel"][attention="success"] {
   list-style-image: url("chrome://browser/skin/downloads/download-glow-menuPanel.png");
   -moz-image-region: auto;
 }
 
 /* In the next few rules, we use :not([counter]) as a shortcut that is
    equivalent to -moz-any([progress], [paused]). */
 
 #downloads-button:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
-  background: var(--downloads-indicator-icon) center no-repeat;
+  background: var(--downloads-indicator-image) center no-repeat;
   background-size: 12px;
 }
 
 toolbar[brighttext] #downloads-button:not([counter]):not([attention="success"]) > #downloads-indicator-anchor > #downloads-button-progress-area > #downloads-indicator-counter {
-  background-image: var(--downloads-indicator-icon-inverted);
+  background-image: var(--downloads-indicator-image-inverted);
 }
 
 #downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
-  background-image: var(--downloads-indicator-icon-attention);
+  background-image: var(--downloads-indicator-image-attention);
 }
 
 toolbar[brighttext] #downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
-  background-image: var(--downloads-indicator-icon-attention-inverted);
+  background-image: var(--downloads-indicator-image-attention-inverted);
 }
 
 /*** Download notifications ***/
 
 #downloads-indicator-notification {
   opacity: 0;
   background-size: 16px;
   background-position: center;
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -6,17 +6,16 @@ browser.jar:
 % skin browser classic/1.0 %skin/classic/browser/
 #include ../shared/jar.inc.mn
   skin/classic/browser/sanitizeDialog.css
   skin/classic/browser/aboutSessionRestore-window-icon.png
   skin/classic/browser/aboutSyncTabs.css
 * skin/classic/browser/syncedtabs/sidebar.css     (syncedtabs/sidebar.css)
 * skin/classic/browser/browser.css
 * skin/classic/browser/compacttheme.css
-* skin/classic/browser/browser-lightweightTheme.css
   skin/classic/browser/caption-buttons.svg
   skin/classic/browser/click-to-play-warning-stripes.png
   skin/classic/browser/Info.png
   skin/classic/browser/livemark-folder.png
   skin/classic/browser/menu-back.png
   skin/classic/browser/menu-forward.png
   skin/classic/browser/menuPanel-customize.png
   skin/classic/browser/menuPanel-customize@2x.png
--- a/build/buildconfig.py
+++ b/build/buildconfig.py
@@ -11,10 +11,10 @@ config = MozbuildObject.from_environment
 for var in ('topsrcdir', 'topobjdir', 'defines', 'non_global_defines',
             'substs'):
     value = getattr(config, var)
     setattr(sys.modules[__name__], var, value)
 
 substs = dict(substs)
 
 for var in os.environ:
-    if var != 'SHELL' and var in substs:
+    if var not in ('CPP', 'CXXCPP', 'SHELL') and var in substs:
         substs[var] = os.environ[var]
--- a/build/mach_bootstrap.py
+++ b/build/mach_bootstrap.py
@@ -403,17 +403,18 @@ class ImportHook(object):
         # loading, remove the .pyc/.pyo file, and reload the module.
         # Since we already loaded the .pyc/.pyo module, if it had side
         # effects, they will have happened already, and loading the module
         # with the same name, from another directory may have the same side
         # effects (or different ones). We assume it's not a problem for the
         # python modules under our source directory (either because it
         # doesn't happen or because it doesn't matter).
         if not os.path.exists(module.__file__[:-1]):
-            os.remove(module.__file__)
+            if os.path.exists(module.__file__):
+                os.remove(module.__file__)
             del sys.modules[module.__name__]
             module = self(name, globals, locals, fromlist, level)
 
         return module
 
 
 # Install our hook
 __builtin__.__import__ = ImportHook(__builtin__.__import__)
--- a/devtools/client/framework/components/moz.build
+++ b/devtools/client/framework/components/moz.build
@@ -3,10 +3,11 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 
 DevToolsModules(
   'toolbox-controller.js',
   'toolbox-tab.js',
+  'toolbox-tabs.js',
   'toolbox-toolbar.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/components/toolbox-tabs.js
@@ -0,0 +1,154 @@
+/* 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 {DOM, createClass, createFactory, PropTypes} = require("devtools/client/shared/vendor/react");
+
+const {findDOMNode} = require("devtools/client/shared/vendor/react-dom");
+const {button, div} = DOM;
+
+const Menu = require("devtools/client/framework/menu");
+const MenuItem = require("devtools/client/framework/menu-item");
+const ToolboxTab = createFactory(require("devtools/client/framework/components/toolbox-tab"));
+
+module.exports = createClass({
+  displayName: "ToolboxTabs",
+
+  // See toolbox-toolbar propTypes for details on the props used here.
+  propTypes: {
+    currentToolId: PropTypes.string,
+    focusButton: PropTypes.func,
+    focusedButton: PropTypes.string,
+    highlightedTool: PropTypes.string,
+    panelDefinitions: PropTypes.array,
+    selectTool: PropTypes.func,
+    toolbox: PropTypes.object,
+    L10N: PropTypes.object,
+  },
+
+  getInitialState() {
+    return {
+      overflow: false,
+    };
+  },
+
+  componentDidUpdate() {
+    this.addFlowEvents();
+  },
+
+  componentWillUnmount() {
+    this.removeFlowEvents();
+  },
+
+  addFlowEvents() {
+    this.removeFlowEvents();
+    let node = findDOMNode(this);
+    if (node) {
+      node.addEventListener("overflow", this.onOverflow);
+      node.addEventListener("underflow", this.onUnderflow);
+    }
+  },
+
+  removeFlowEvents() {
+    let node = findDOMNode(this);
+    if (node) {
+      node.removeEventListener("overflow", this.onOverflow);
+      node.removeEventListener("underflow", this.onUnderflow);
+    }
+  },
+
+  onOverflow() {
+    this.setState({
+      overflow: true
+    });
+  },
+
+  onUnderflow() {
+    this.setState({
+      overflow: false
+    });
+  },
+
+  /**
+   * Render all of the tabs, based on the panel definitions and builds out
+   * a toolbox tab for each of them. Will render an all-tabs button if the
+   * container has an overflow.
+   */
+  render() {
+    let {
+      currentToolId,
+      focusButton,
+      focusedButton,
+      highlightedTool,
+      panelDefinitions,
+      selectTool,
+    } = this.props;
+
+    let tabs = panelDefinitions.map(panelDefinition => ToolboxTab({
+      currentToolId,
+      focusButton,
+      focusedButton,
+      highlightedTool,
+      panelDefinition,
+      selectTool,
+    }));
+
+    // A wrapper is needed to get flex sizing correct in XUL.
+    return div(
+      {
+        className: "toolbox-tabs-wrapper"
+      },
+      div(
+        {
+          className: "toolbox-tabs"
+        },
+        tabs
+      ),
+      this.state.overflow ? renderAllToolsButton(this.props) : null
+    );
+  },
+});
+
+/**
+ * Render a button to access all tools, displayed only when the toolbar presents an
+ * overflow.
+ */
+function renderAllToolsButton(props) {
+  let {
+    currentToolId,
+    panelDefinitions,
+    selectTool,
+    toolbox,
+    L10N,
+  } = props;
+
+  return button({
+    className: "all-tools-menu all-tabs-menu",
+    tabIndex: -1,
+    title: L10N.getStr("toolbox.allToolsButton.tooltip"),
+    onClick: ({ target }) => {
+      let menu = new Menu({
+        id: "all-tools-menupopup"
+      });
+      panelDefinitions.forEach(({id, label}) => {
+        menu.append(new MenuItem({
+          checked: currentToolId === id,
+          click: () => {
+            selectTool(id);
+          },
+          id: "all-tools-menupopup-" + id,
+          label,
+        }));
+      });
+
+      let rect = target.getBoundingClientRect();
+      let screenX = target.ownerDocument.defaultView.mozInnerScreenX;
+      let screenY = target.ownerDocument.defaultView.mozInnerScreenY;
+
+      // Display the popup below the button.
+      menu.popup(rect.left + screenX, rect.bottom + screenY, toolbox);
+      return menu;
+    },
+  });
+}
--- a/devtools/client/framework/components/toolbox-toolbar.js
+++ b/devtools/client/framework/components/toolbox-toolbar.js
@@ -1,16 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const {DOM, createClass, createFactory, PropTypes} = require("devtools/client/shared/vendor/react");
 const {div, button} = DOM;
+
 const ToolboxTab = createFactory(require("devtools/client/framework/components/toolbox-tab"));
+const ToolboxTabs = createFactory(require("devtools/client/framework/components/toolbox-tabs"));
 
 /**
  * This is the overall component for the toolbox toolbar. It is designed to not know how
  * the state is being managed, and attempts to be as pure as possible. The
  * ToolboxController component controls the changing state, and passes in everything as
  * props.
  */
 module.exports = createClass({
@@ -34,69 +36,43 @@ module.exports = createClass({
     focusButton: PropTypes.func,
     // The options button definition.
     optionsPanel: PropTypes.object,
     // Hold off displaying the toolbar until enough information is ready for it to render
     // nicely.
     canRender: PropTypes.bool,
     // Localization interface.
     L10N: PropTypes.object,
+    // The devtools toolbox
+    toolbox: PropTypes.object,
   },
 
   /**
    * The render function is kept fairly short for maintainability. See the individual
    * render functions for how each of the sections is rendered.
    */
   render() {
     const containerProps = {className: "devtools-tabbar"};
     return this.props.canRender
       ? (
         div(
           containerProps,
           renderToolboxButtonsStart(this.props),
-          renderTabs(this.props),
+          ToolboxTabs(this.props),
           renderToolboxButtonsEnd(this.props),
           renderOptions(this.props),
           renderSeparator(),
           renderDockButtons(this.props)
         )
       )
       : div(containerProps);
   }
 });
 
 /**
- * Render all of the tabs, this takes in the panel definitions and builds out
- * the buttons for each of them.
- *
- * @param {Array}    panelDefinitions - Array of objects that define panels.
- * @param {String}   currentToolId - The currently selected tool's id; e.g. "inspector".
- * @param {String}   highlightedTool - If a tool is highlighted, this is it's id.
- * @param {Function} selectTool - Function to select a tool in the toolbox.
- * @param {String}   focusedButton - The id of the focused button.
- * @param {Function} focusButton - Keep a record of the currently focused button.
- */
-function renderTabs({panelDefinitions, currentToolId, highlightedTool, selectTool,
-                     focusedButton, focusButton}) {
-  // A wrapper is needed to get flex sizing correct in XUL.
-  return div({className: "toolbox-tabs-wrapper"},
-    div({className: "toolbox-tabs"},
-      ...panelDefinitions.map(panelDefinition => ToolboxTab({
-        panelDefinition,
-        currentToolId,
-        highlightedTool,
-        selectTool,
-        focusedButton,
-        focusButton,
-      }))
-    )
-  );
-}
-
-/**
  * A little helper function to call renderToolboxButtons for buttons at the start
  * of the toolbox.
  */
 function renderToolboxButtonsStart(props) {
   return renderToolboxButtons(props, true);
 }
 
 /**
@@ -145,17 +121,17 @@ function renderToolboxButtons({toolboxBu
         onFocus: () => focusButton(id),
         tabIndex: id === focusedButton ? "0" : "-1"
       });
     })
   );
 }
 
 /**
- * The options button is a ToolboxTab just like in the renderTabs() function. However
+ * The options button is a ToolboxTab just like in the ToolboxTabs component. However
  * it is separate from the normal tabs, so deal with it separately here.
  *
  * @param {Object}   optionsPanel - A single panel definition for the options panel.
  * @param {String}   currentToolId - The currently selected tool's id; e.g. "inspector".
  * @param {Function} selectTool - Function to select a tool in the toolbox.
  * @param {String}   focusedButton - The id of the focused button.
  * @param {Function} focusButton - Keep a record of the currently focused button.
  */
--- a/devtools/client/framework/test/browser.ini
+++ b/devtools/client/framework/test/browser.ini
@@ -75,16 +75,17 @@ skip-if = e10s # Bug 1069044 - destroyIn
 [browser_toolbox_target.js]
 [browser_toolbox_tabsswitch_shortcuts.js]
 [browser_toolbox_textbox_context_menu.js]
 [browser_toolbox_theme.js]
 [browser_toolbox_theme_registration.js]
 [browser_toolbox_toggle.js]
 [browser_toolbox_tool_ready.js]
 [browser_toolbox_tool_remote_reopen.js]
+[browser_toolbox_toolbar_overflow.js]
 [browser_toolbox_tools_per_toolbox_registration.js]
 [browser_toolbox_transport_events.js]
 [browser_toolbox_view_source_01.js]
 [browser_toolbox_view_source_02.js]
 [browser_toolbox_view_source_03.js]
 [browser_toolbox_view_source_04.js]
 [browser_toolbox_window_reload_target.js]
 [browser_toolbox_window_shortcuts.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/browser_toolbox_toolbar_overflow.js
@@ -0,0 +1,87 @@
+/* -*- 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/ */
+
+/* import-globals-from shared-head.js */
+"use strict";
+
+// Test that a button to access tools hidden by toolbar overflow is displayed when the
+// toolbar starts to present an overflow.
+let { Toolbox } = require("devtools/client/framework/toolbox");
+
+add_task(function* () {
+  let tab = yield addTab("about:blank");
+
+  info("Open devtools on the Inspector in a separate window");
+  let toolbox = yield openToolboxForTab(tab, "inspector", Toolbox.HostType.WINDOW);
+
+  let hostWindow = toolbox.win.parent;
+  let originalWidth = hostWindow.outerWidth;
+  let originalHeight = hostWindow.outerHeight;
+
+  info("Resize devtools window to a width that should not trigger any overflow");
+  let onResize = once(hostWindow, "resize");
+  hostWindow.resizeTo(640, 300);
+  yield onResize;
+
+  let allToolsButton = toolbox.doc.querySelector(".all-tools-menu");
+  ok(!allToolsButton, "The all tools button is not displayed");
+
+  info("Resize devtools window to a width that should trigger an overflow");
+  onResize = once(hostWindow, "resize");
+  hostWindow.resizeTo(300, 300);
+  yield onResize;
+
+  info("Wait until the all tools button is available");
+  yield waitUntil(() => toolbox.doc.querySelector(".all-tools-menu"));
+
+  allToolsButton = toolbox.doc.querySelector(".all-tools-menu");
+  ok(allToolsButton, "The all tools button is displayed");
+
+  info("Open the all-tools-menupopup and verify that the inspector button is checked");
+  let menuPopup = yield openAllToolsMenu(toolbox);
+
+  let inspectorButton = toolbox.doc.querySelector("#all-tools-menupopup-inspector");
+  ok(inspectorButton, "The inspector button is available");
+  ok(inspectorButton.getAttribute("checked"), "The inspector button is checked");
+
+  let consoleButton = toolbox.doc.querySelector("#all-tools-menupopup-webconsole");
+  ok(consoleButton, "The console button is available");
+  ok(!consoleButton.getAttribute("checked"), "The console button is not checked");
+
+  info("Switch to the webconsole using the all-tools-menupopup popup");
+  let onSelected = toolbox.once("webconsole-selected");
+  consoleButton.click();
+  yield onSelected;
+
+  info("Closing the all-tools-menupopup popup");
+  let onPopupHidden = once(menuPopup, "popuphidden");
+  menuPopup.hidePopup();
+  yield onPopupHidden;
+
+  info("Re-open the all-tools-menupopup and verify that the console button is checked");
+  menuPopup = yield openAllToolsMenu(toolbox);
+
+  inspectorButton = toolbox.doc.querySelector("#all-tools-menupopup-inspector");
+  ok(!inspectorButton.getAttribute("checked"), "The inspector button is not checked");
+
+  consoleButton = toolbox.doc.querySelector("#all-tools-menupopup-webconsole");
+  ok(consoleButton.getAttribute("checked"), "The console button is checked");
+
+  info("Restore the original window size");
+  hostWindow.resizeTo(originalWidth, originalHeight);
+});
+
+function* openAllToolsMenu(toolbox) {
+  let allToolsButton = toolbox.doc.querySelector(".all-tools-menu");
+  EventUtils.synthesizeMouseAtCenter(allToolsButton, {}, toolbox.win);
+
+  let menuPopup = toolbox.doc.querySelector("#all-tools-menupopup");
+  ok(menuPopup, "all-tools-menupopup is available");
+
+  info("Waiting for the menu popup to be displayed");
+  yield waitUntil(() => menuPopup && menuPopup.state === "open");
+
+  return menuPopup;
+}
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -999,16 +999,17 @@ Toolbox.prototype = {
     // Ensure the toolbar doesn't try to render until the tool is ready.
     const element = this.React.createElement(this.ToolboxController, {
       L10N,
       currentToolId: this.currentToolId,
       selectTool: this.selectTool,
       closeToolbox: this.destroy,
       focusButton: this._onToolbarFocus,
       toggleMinimizeMode: this._toggleMinimizeMode,
+      toolbox: this
     });
 
     this.component = this.ReactDOM.render(element, this._componentMount);
   },
 
   /**
    * Reset tabindex attributes across all focusable elements inside the toolbar.
    * Only have one element with tabindex=0 at a time to make sure that tabbing
--- a/devtools/client/jsonview/converter-sniffer.js
+++ b/devtools/client/jsonview/converter-sniffer.js
@@ -9,17 +9,17 @@
 const {Cc, Ci, Cu, Cm, Cr, components} = require("chrome");
 const registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
 const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
 
 const categoryManager = Cc["@mozilla.org/categorymanager;1"]
   .getService(Ci.nsICategoryManager);
 
 // Constants
-const JSON_TYPE = "application/json";
+const JSON_TYPES = ["application/json", "application/manifest+json"];
 const CONTRACT_ID = "@mozilla.org/devtools/jsonview-sniffer;1";
 const CLASS_ID = components.ID("{4148c488-dca1-49fc-a621-2a0097a62422}");
 const CLASS_DESCRIPTION = "JSONView content sniffer";
 const JSON_VIEW_MIME_TYPE = "application/vnd.mozilla.json.view";
 const JSON_VIEW_TYPE = "JSON View";
 const CONTENT_SNIFFER_CATEGORY = "net-content-sniffers";
 
 function isTopLevelLoad(request) {
@@ -57,19 +57,20 @@ Sniffer.prototype = {
         if (request.contentDisposition ==
           Ci.nsIChannel.DISPOSITION_ATTACHMENT) {
           return "";
         }
       } catch (e) {
         // Channel doesn't support content dispositions
       }
 
-      // Check the response content type and if it's application/json
+      // Check the response content type and if it's a valid type
+      // such as application/json or application/manifest+json
       // change it to new internal type consumed by JSON View.
-      if (request.contentType == JSON_TYPE) {
+      if (JSON_TYPES.includes(request.contentType)) {
         return JSON_VIEW_MIME_TYPE;
       }
     }
 
     return "";
   }
 };
 
--- a/devtools/client/jsonview/test/browser.ini
+++ b/devtools/client/jsonview/test/browser.ini
@@ -3,16 +3,18 @@ tags = devtools
 subsuite = devtools
 support-files =
   array_json.json
   array_json.json^headers^
   doc_frame_script.js
   head.js
   invalid_json.json
   invalid_json.json^headers^
+  manifest_json.json
+  manifest_json.json^headers^
   simple_json.json
   simple_json.json^headers^
   valid_json.json
   valid_json.json^headers^
   !/devtools/client/commandline/test/head.js
   !/devtools/client/framework/test/head.js
   !/devtools/client/framework/test/shared-head.js
 
@@ -24,8 +26,9 @@ subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_jsonview_copy_rawdata.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_jsonview_filter.js]
 [browser_jsonview_invalid_json.js]
 [browser_jsonview_valid_json.js]
 [browser_jsonview_save_json.js]
+[browser_jsonview_manifest.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/jsonview/test/browser_jsonview_manifest.js
@@ -0,0 +1,17 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_JSON_URL = URL_ROOT + "manifest_json.json";
+
+add_task(function* () {
+  info("Test manifest JSON file started");
+
+  yield addJsonViewTab(TEST_JSON_URL);
+
+  let count = yield getElementCount(".jsonPanelBox .treeTable .treeRow");
+  is(count, 37, "There must be expected number of rows");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/jsonview/test/manifest_json.json
@@ -0,0 +1,39 @@
+{
+  "name": "HackerWeb",
+  "short_name": "HackerWeb",
+  "start_url": ".",
+  "display": "standalone",
+  "background_color": "#fff",
+  "description": "A simply readable Hacker News app.",
+  "icons": [{
+    "src": "images/touch/homescreen48.png",
+    "sizes": "48x48",
+    "type": "image/png"
+  }, {
+    "src": "images/touch/homescreen72.png",
+    "sizes": "72x72",
+    "type": "image/png"
+  }, {
+    "src": "images/touch/homescreen96.png",
+    "sizes": "96x96",
+    "type": "image/png"
+  }, {
+    "src": "images/touch/homescreen144.png",
+    "sizes": "144x144",
+    "type": "image/png"
+  }, {
+    "src": "images/touch/homescreen168.png",
+    "sizes": "168x168",
+    "type": "image/png"
+  }, {
+    "src": "images/touch/homescreen192.png",
+    "sizes": "192x192",
+    "type": "image/png"
+  }],
+  "related_applications": [{
+    "platform": "web"
+  }, {
+    "platform": "play",
+    "url": "https://play.google.com/store/apps/details?id=cheeaun.hackerweb"
+  }]
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/jsonview/test/manifest_json.json^headers^
@@ -0,0 +1,1 @@
+Content-Type: application/manifest+json; charset=utf-8
--- a/devtools/client/locales/en-US/toolbox.properties
+++ b/devtools/client/locales/en-US/toolbox.properties
@@ -168,8 +168,12 @@ toolbox.frames.tooltip=Select an iframe 
 # the button to force the popups/panels to stay visible on blur.
 # This is only visible in the browser toolbox as it is meant for
 # addon developers and Firefox contributors.
 toolbox.noautohide.tooltip=Disable popup auto hide
 
 # LOCALIZATION NOTE (toolbox.closebutton.tooltip): This is the tooltip for
 # the close button the developer tools toolbox.
 toolbox.closebutton.tooltip=Close Developer Tools
+
+# LOCALIZATION NOTE (toolbox.allToolsButton.tooltip): This is the tooltip for the
+# "all tools" button displayed when some tools are hidden by overflow of the toolbar.
+toolbox.allToolsButton.tooltip=Select another tool
--- a/devtools/client/netmonitor/components/network-monitor.js
+++ b/devtools/client/netmonitor/components/network-monitor.js
@@ -7,28 +7,28 @@
 const {
   createFactory,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 
 // Components
-const MonitoPanel = createFactory(require("./monitor-panel"));
+const MonitorPanel = createFactory(require("./monitor-panel"));
 const StatisticsPanel = createFactory(require("./statistics-panel"));
 
 const { div } = DOM;
 
 /*
  * Network monitor component
  */
 function NetworkMonitor({ statisticsOpen }) {
   return (
     div({ className: "network-monitor" },
-      !statisticsOpen ? MonitoPanel() : StatisticsPanel()
+      !statisticsOpen ? MonitorPanel() : StatisticsPanel()
     )
   );
 }
 
 NetworkMonitor.displayName = "NetworkMonitor";
 
 NetworkMonitor.propTypes = {
   statisticsOpen: PropTypes.bool.isRequired,
--- a/devtools/client/netmonitor/netmonitor-controller.js
+++ b/devtools/client/netmonitor/netmonitor-controller.js
@@ -1,25 +1,28 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const Services = require("Services");
 const { TimelineFront } = require("devtools/shared/fronts/timeline");
 const { CurlUtils } = require("devtools/client/shared/curl");
 const { ACTIVITY_TYPE, EVENTS } = require("./constants");
 const { configureStore } = require("./store");
 const Actions = require("./actions/index");
 const {
   fetchHeaders,
   formDataURI,
 } = require("./utils/request-utils");
 const {
+  onFirefoxConnect,
+  onFirefoxDisconnect,
+} = require("./utils/client");
+const {
   getRequestById,
   getDisplayedRequestById,
 } = require("./selectors/index");
 
 const gStore = window.gStore = configureStore();
 
 /**
  * Object defining the network monitor controller components.
@@ -49,17 +52,18 @@ var NetMonitorController = {
    *         A promise that is resolved when the monitor finishes shutdown.
    */
   shutdownNetMonitor() {
     if (this._shutdown) {
       return this._shutdown;
     }
     this._shutdown = new Promise(async (resolve) => {
       gStore.dispatch(Actions.batchReset());
-      this.TargetEventsHandler.disconnect();
+      onFirefoxDisconnect(this._target);
+      this._target.off("close", this._onTabDetached);
       this.NetworkEventsHandler.disconnect();
       await this.disconnect();
       resolve();
     });
 
     return this._shutdown;
   },
 
@@ -71,38 +75,40 @@ var NetMonitorController = {
    *
    * @return object
    *         A promise that is resolved when the monitor finishes connecting.
    */
   connect() {
     if (this._connection) {
       return this._connection;
     }
+    this._onTabDetached = this.shutdownNetMonitor.bind(this);
+
     this._connection = new Promise(async (resolve) => {
       // Some actors like AddonActor or RootActor for chrome debugging
       // aren't actual tabs.
       if (this._target.isTabActor) {
         this.tabClient = this._target.activeTab;
       }
+      this.webConsoleClient = this._target.activeConsole;
 
       let connectTimeline = () => {
         // Don't start up waiting for timeline markers if the server isn't
         // recent enough to emit the markers we're interested in.
         if (this._target.getTrait("documentLoadingMarkers")) {
           this.timelineFront = new TimelineFront(this._target.client,
             this._target.form);
           return this.timelineFront.start({ withDocLoadingEvents: true });
         }
         return undefined;
       };
-
-      this.webConsoleClient = this._target.activeConsole;
       await connectTimeline();
 
-      this.TargetEventsHandler.connect();
+      onFirefoxConnect(this._target);
+      this._target.on("close", this._onTabDetached);
       this.NetworkEventsHandler.connect();
 
       window.emit(EVENTS.CONNECTED);
 
       resolve();
       this._connected = true;
     });
     return this._connection;
@@ -354,88 +360,16 @@ var NetMonitorController = {
 
       window.on(EVENTS.NETWORK_EVENT, onRequest);
       window.on(EVENTS.RECEIVED_EVENT_TIMINGS, onTimings);
     });
   },
 };
 
 /**
- * Functions handling target-related lifetime events.
- */
-function TargetEventsHandler() {
-  this._onTabNavigated = this._onTabNavigated.bind(this);
-  this._onTabDetached = this._onTabDetached.bind(this);
-}
-
-TargetEventsHandler.prototype = {
-  get target() {
-    return NetMonitorController._target;
-  },
-
-  /**
-   * Listen for events emitted by the current tab target.
-   */
-  connect: function () {
-    this.target.on("close", this._onTabDetached);
-    this.target.on("navigate", this._onTabNavigated);
-    this.target.on("will-navigate", this._onTabNavigated);
-  },
-
-  /**
-   * Remove events emitted by the current tab target.
-   */
-  disconnect: function () {
-    if (!this.target) {
-      return;
-    }
-    this.target.off("close", this._onTabDetached);
-    this.target.off("navigate", this._onTabNavigated);
-    this.target.off("will-navigate", this._onTabNavigated);
-  },
-
-  /**
-   * Called for each location change in the monitored tab.
-   *
-   * @param string type
-   *        Packet type.
-   * @param object packet
-   *        Packet received from the server.
-   */
-  _onTabNavigated: function (type, packet) {
-    switch (type) {
-      case "will-navigate": {
-        // Reset UI.
-        if (!Services.prefs.getBoolPref("devtools.webconsole.persistlog")) {
-          gStore.dispatch(Actions.batchReset());
-          gStore.dispatch(Actions.clearRequests());
-        } else {
-          // If the log is persistent, just clear all accumulated timing markers.
-          gStore.dispatch(Actions.clearTimingMarkers());
-        }
-
-        window.emit(EVENTS.TARGET_WILL_NAVIGATE);
-        break;
-      }
-      case "navigate": {
-        window.emit(EVENTS.TARGET_DID_NAVIGATE);
-        break;
-      }
-    }
-  },
-
-  /**
-   * Called when the monitored tab is closed.
-   */
-  _onTabDetached: function () {
-    NetMonitorController.shutdownNetMonitor();
-  }
-};
-
-/**
  * Functions handling target network events.
  */
 function NetworkEventsHandler() {
   this.addRequest = this.addRequest.bind(this);
   this.updateRequest = this.updateRequest.bind(this);
   this.getString = this.getString.bind(this);
   this._onNetworkEvent = this._onNetworkEvent.bind(this);
   this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
@@ -886,13 +820,15 @@ NetworkEventsHandler.prototype = {
     if (typeof stringGrip === "string") {
       return Promise.resolve(stringGrip);
     }
 
     return this.webConsoleClient.getString(stringGrip);
   }
 };
 
-NetMonitorController.TargetEventsHandler = new TargetEventsHandler();
+/**
+ * Preliminary setup for the NetMonitorController object.
+ */
 NetMonitorController.NetworkEventsHandler = new NetworkEventsHandler();
 window.gNetwork = NetMonitorController.NetworkEventsHandler;
 
 exports.NetMonitorController = NetMonitorController;
--- a/devtools/client/netmonitor/netmonitor.js
+++ b/devtools/client/netmonitor/netmonitor.js
@@ -1,53 +1,52 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+/* exported Netmonitor */
+
 "use strict";
 
 const { BrowserLoader } = Components.utils.import("resource://devtools/client/shared/browser-loader.js", {});
 
-function Netmonitor(toolbox) {
-  const require = window.windowRequire = BrowserLoader({
-    baseURI: "resource://devtools/client/netmonitor/",
-    window,
-    commonLibRequire: toolbox.browserRequire,
-  }).require;
+var Netmonitor = {
+  bootstrap: ({ tabTarget, toolbox }) => {
+    const require = window.windowRequire = BrowserLoader({
+      baseURI: "resource://devtools/client/netmonitor/",
+      window,
+      commonLibRequire: toolbox.browserRequire,
+    }).require;
 
-  // Inject EventEmitter into netmonitor window.
-  const EventEmitter = require("devtools/shared/event-emitter");
-  EventEmitter.decorate(window);
-
-  window.NetMonitorController = require("./netmonitor-controller").NetMonitorController;
-  window.NetMonitorController._toolbox = toolbox;
-  window.NetMonitorController._target = toolbox.target;
-}
-
-Netmonitor.prototype = {
-  init() {
-    const require = window.windowRequire;
+    const EventEmitter = require("devtools/shared/event-emitter");
     const { createFactory } = require("devtools/client/shared/vendor/react");
     const { render } = require("devtools/client/shared/vendor/react-dom");
     const Provider = createFactory(require("devtools/client/shared/vendor/react-redux").Provider);
 
     // Components
     const NetworkMonitor = createFactory(require("./components/network-monitor"));
 
-    this.networkMonitor = document.querySelector(".root");
+    // Inject EventEmitter into netmonitor window.
+    EventEmitter.decorate(window);
+
+    window.NetMonitorController = require("./netmonitor-controller").NetMonitorController;
+    window.NetMonitorController._toolbox = toolbox;
+    window.NetMonitorController._target = tabTarget;
+
+    this.root = document.querySelector(".root");
 
     render(Provider(
       { store: window.gStore },
-      NetworkMonitor({ toolbox: window.NetMonitorController._toolbox }),
-    ), this.networkMonitor);
+      NetworkMonitor(),
+    ), this.root);
 
     return window.NetMonitorController.startupNetMonitor();
   },
 
-  destroy() {
+  destroy: () => {
     const require = window.windowRequire;
     const { unmountComponentAtNode } = require("devtools/client/shared/vendor/react-dom");
 
-    unmountComponentAtNode(this.networkMonitor);
+    unmountComponentAtNode(this.root);
 
     return window.NetMonitorController.shutdownNetMonitor();
   }
 };
--- a/devtools/client/netmonitor/panel.js
+++ b/devtools/client/netmonitor/panel.js
@@ -1,32 +1,33 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 function NetMonitorPanel(iframeWindow, toolbox) {
   this.panelWin = iframeWindow;
-  this.panelDoc = iframeWindow.document;
   this.toolbox = toolbox;
-  this.netmonitor = new iframeWindow.Netmonitor(toolbox);
 }
 
 NetMonitorPanel.prototype = {
   async open() {
     if (!this.toolbox.target.isRemote) {
       await this.toolbox.target.makeRemote();
     }
-    await this.netmonitor.init();
+    await this.panelWin.Netmonitor.bootstrap({
+      tabTarget: this.toolbox.target,
+      toolbox: this.toolbox,
+    });
     this.emit("ready");
     this.isReady = true;
     return this;
   },
 
   async destroy() {
-    await this.netmonitor.destroy();
+    await this.panelWin.Netmonitor.destroy();
     this.emit("destroyed");
     return this;
   },
 };
 
 exports.NetMonitorPanel = NetMonitorPanel;
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/utils/client.js
@@ -0,0 +1,65 @@
+/* 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/. */
+
+/* global gStore */
+
+"use strict";
+
+const Services = require("Services");
+const Actions = require("../actions/index");
+const { EVENTS } = require("../constants");
+
+/**
+ * Called for each location change in the monitored tab.
+ *
+ * @param {String} type Packet type.
+ * @param {Object} packet Packet received from the server.
+ */
+function navigated(type) {
+  window.emit(EVENTS.TARGET_DID_NAVIGATE);
+}
+
+/**
+ * Called for each location change in the monitored tab.
+ *
+ * @param {String} type Packet type.
+ * @param {Object} packet Packet received from the server.
+ */
+function willNavigate(type) {
+  // Reset UI.
+  if (!Services.prefs.getBoolPref("devtools.webconsole.persistlog")) {
+    gStore.dispatch(Actions.batchReset());
+    gStore.dispatch(Actions.clearRequests());
+  } else {
+    // If the log is persistent, just clear all accumulated timing markers.
+    gStore.dispatch(Actions.clearTimingMarkers());
+  }
+
+  window.emit(EVENTS.TARGET_WILL_NAVIGATE);
+}
+
+/**
+ * Process connection events.
+ *
+ * @param {Object} tabTarget
+ */
+function onFirefoxConnect(tabTarget) {
+  tabTarget.on("navigate", navigated);
+  tabTarget.on("will-navigate", willNavigate);
+}
+
+/**
+ * Process disconnect events.
+ *
+ * @param {Object} tabTarget
+ */
+function onFirefoxDisconnect(tabTarget) {
+  tabTarget.off("navigate", navigated);
+  tabTarget.off("will-navigate", willNavigate);
+}
+
+module.exports = {
+  onFirefoxConnect,
+  onFirefoxDisconnect,
+};
--- a/devtools/client/netmonitor/utils/moz.build
+++ b/devtools/client/netmonitor/utils/moz.build
@@ -1,14 +1,15 @@
 # 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(
+    'client.js',
     'filter-predicates.js',
     'format-utils.js',
     'l10n.js',
     'mdn-utils.js',
     'prefs.js',
     'request-utils.js',
     'sort-predicates.js',
 )
--- a/devtools/client/responsive.html/components/device-modal.js
+++ b/devtools/client/responsive.html/components/device-modal.js
@@ -33,32 +33,46 @@ module.exports = createClass({
   },
 
   componentDidMount() {
     window.addEventListener("keydown", this.onKeyDown, true);
   },
 
   componentWillReceiveProps(nextProps) {
     let {
+      devices: oldDevices,
+    } = this.props;
+    let {
       devices,
     } = nextProps;
 
-    for (let type of devices.types) {
-      for (let device of devices[type]) {
-        this.setState({
-          [device.name]: device.displayed,
-        });
+    // Refresh component state only when model transitions from closed to open
+    if (!oldDevices.isModalOpen && devices.isModalOpen) {
+      for (let type of devices.types) {
+        for (let device of devices[type]) {
+          this.setState({
+            [device.name]: device.displayed,
+          });
+        }
       }
     }
   },
 
   componentWillUnmount() {
     window.removeEventListener("keydown", this.onKeyDown, true);
   },
 
+  onAddCustomDevice(device) {
+    this.props.onAddCustomDevice(device);
+    // Default custom devices to enabled
+    this.setState({
+      [device.name]: true,
+    });
+  },
+
   onDeviceCheckboxChange({ nativeEvent: { button }, target }) {
     if (button !== 0) {
       return;
     }
     this.setState({
       [target.value]: !this.state[target.value]
     });
   },
@@ -108,21 +122,24 @@ module.exports = createClass({
       onUpdateDeviceModal(false);
     }
   },
 
   render() {
     let {
       deviceAdderViewportTemplate,
       devices,
-      onAddCustomDevice,
       onRemoveCustomDevice,
       onUpdateDeviceModal,
     } = this.props;
 
+    let {
+      onAddCustomDevice,
+    } = this;
+
     const sortedDevices = {};
     for (let type of devices.types) {
       sortedDevices[type] = Object.assign([], devices[type])
         .sort((a, b) => a.name.localeCompare(b.name));
     }
 
     return dom.div(
       {
--- a/devtools/client/responsive.html/test/browser/browser_device_custom.js
+++ b/devtools/client/responsive.html/test/browser/browser_device_custom.js
@@ -48,22 +48,22 @@ addRDMTask(TEST_URL, function* ({ ui }) 
 
   info("Fill out device adder form and save");
   setDeviceAdder(ui, device);
   let adderSave = document.querySelector("#device-adder-save");
   let saved = waitUntilState(store, state => state.devices.custom.length == 1);
   Simulate.click(adderSave);
   yield saved;
 
-  info("Enable device in modal");
+  info("Verify device defaults to enabled in modal");
   let deviceCb = [...document.querySelectorAll(".device-input-checkbox")].find(cb => {
     return cb.value == device.name;
   });
   ok(deviceCb, "Custom device checkbox added to modal");
-  deviceCb.click();
+  ok(deviceCb.checked, "Custom device enabled");
   Simulate.click(submitButton);
 
   info("Look for custom device in device selector");
   let selectorOption = [...deviceSelector.options].find(opt => opt.value == device.name);
   ok(selectorOption, "Custom device option added to device selector");
 });
 
 addRDMTask(TEST_URL, function* ({ ui }) {
--- a/devtools/client/shared/components/tabs/tabs.css
+++ b/devtools/client/shared/components/tabs/tabs.css
@@ -46,29 +46,16 @@
 .tabs .panels {
   height: calc(100% - 24px);
 }
 
 .tabs .tab-panel {
   height: 100%;
 }
 
-.tabs .all-tabs-menu  {
-  position: absolute;
-  top: 0;
-  offset-inline-end: 0;
-  width: 15px;
-  height: 100%;
-  border-inline-start: 1px solid var(--theme-splitter-color);
-  background: var(--theme-tab-toolbar-background);
-  background-image: url("chrome://devtools/skin/images/dropmarker.svg");
-  background-repeat: no-repeat;
-  background-position: center;
-}
-
 .tabs .tabs-navigation,
 .tabs .tabs-navigation {
   position: relative;
   border-bottom: 1px solid var(--theme-splitter-color);
   background: var(--theme-tab-toolbar-background);
 }
 
 .theme-dark .tabs .tabs-menu-item,
--- a/devtools/client/themes/common.css
+++ b/devtools/client/themes/common.css
@@ -667,8 +667,26 @@ checkbox:-moz-focusring {
 @keyframes throbber-spin {
   from {
     transform: none;
   }
   to {
     transform: rotate(360deg);
   }
 }
+
+/* Common tabs styles */
+
+.all-tabs-menu {
+  position: absolute;
+
+  top: 0;
+  offset-inline-end: 0;
+  width: 15px;
+  height: 100%;
+
+  border-inline-start: 1px solid var(--theme-splitter-color);
+
+  background: var(--theme-tab-toolbar-background);
+  background-image: url("chrome://devtools/skin/images/dropmarker.svg");
+  background-repeat: no-repeat;
+  background-position: center;
+}
--- a/devtools/client/themes/images/grid.svg
+++ b/devtools/client/themes/images/grid.svg
@@ -1,6 +1,6 @@
 <!-- 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/. -->
-<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" stroke="#696969">
-  <path fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" d="M2 4h12M2 8h12M2 12h12M4 14V2M8 14V2M12 14V2"/>
+<svg width="12" height="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg" stroke="#696969">
+  <path fill="none" d="M1 2.5h9m-9 3h9m-9 3h9M2.5 1v9m3-9v9m3-9v9"/>
 </svg>
--- a/devtools/client/themes/rules.css
+++ b/devtools/client/themes/rules.css
@@ -411,17 +411,16 @@
   margin-top: -1px;
   margin-inline-end: 5px;
   display: inline-block;
   position: relative;
 }
 
 .ruleview-grid {
   background: url("chrome://devtools/skin/images/grid.svg");
-  background-size: 1em;
   border-radius: 0;
 }
 
 .ruleview-colorswatch::before {
   content: '';
   background-color: #eee;
   background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),
                     linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc);
--- a/devtools/client/themes/toolbox.css
+++ b/devtools/client/themes/toolbox.css
@@ -50,24 +50,31 @@
   background: var(--theme-tab-toolbar-background);
   border-bottom-color: var(--theme-splitter-color);
   display: flex;
 }
 
 .toolbox-tabs {
   margin: 0;
   flex: 1;
+  overflow: hidden;
 }
 
 .toolbox-tabs-wrapper {
   position: relative;
   display: flex;
   flex: 1;
 }
 
+.toolbox-tabs-wrapper .all-tools-menu {
+  border-inline-end: 1px solid var(--theme-splitter-color);
+  border-top-width: 0;
+  border-bottom-width: 0;
+}
+
 .toolbox-tabs {
   position: absolute;
   top: 0;
   left: 0;
   right: 0;
   bottom: 0;
   display: flex;
 }
--- a/devtools/client/themes/variables.css
+++ b/devtools/client/themes/variables.css
@@ -16,23 +16,25 @@
 
 :root.theme-light {
   --theme-body-background: white;
   --theme-sidebar-background: white;
   --theme-contrast-background: #e6b064;
 
   --theme-tab-toolbar-background: #fcfcfc;
   --theme-toolbar-background: #fcfcfc;
+  --theme-toolbar-background-alt: #f5f5f5;
   --theme-toolbar-hover: rgba(170, 170, 170, .2);
   --theme-toolbar-hover-active: rgba(170, 170, 170, .4);
   --theme-selection-background: #4c9ed9;
   --theme-selection-background-semitransparent: rgba(76, 158, 217, 0.15);
   --theme-selection-color: #f5f7fa;
   --theme-splitter-color: #dde1e4;
   --theme-comment: #696969;
+  --theme-comment-alt: #ccd1d5;
 
   --theme-body-color: #393f4c;
   --theme-body-color-alt: #585959;
   --theme-body-color-inactive: #999797;
   --theme-content-color1: #292e33;
   --theme-content-color2: #8fa1b2;
   --theme-content-color3: #667380;
 
@@ -78,23 +80,25 @@
 
 :root.theme-dark {
   --theme-body-background: #393f4c;
   --theme-sidebar-background: #393f4c;
   --theme-contrast-background: #ffb35b;
 
   --theme-tab-toolbar-background: #272b35;
   --theme-toolbar-background: #272b35;
+  --theme-toolbar-background-alt: #2F343E;
   --theme-toolbar-hover: rgba(110, 120, 130, 0.1);
   --theme-toolbar-hover-active: rgba(110, 120, 130, 0.2);
   --theme-selection-background: #5675B9;
   --theme-selection-background-semitransparent: rgba(86, 117, 185, 0.5);
   --theme-selection-color: #f5f7fa;
   --theme-splitter-color: #454d5d;
   --theme-comment: #757873;
+  --theme-comment-alt: #5a6375;
 
   --theme-body-color: #8fa1b2;
   --theme-body-color-alt: #b6babf;
   --theme-body-color-inactive: #8fa1b2;
   --theme-content-color1: #a9bacb;
   --theme-content-color2: #8fa1b2;
   --theme-content-color3: #5f7387;
 
--- a/devtools/server/actors/root.js
+++ b/devtools/server/actors/root.js
@@ -385,30 +385,31 @@ RootActor.prototype = {
 
   onListAddons: function () {
     let addonList = this._parameters.addonList;
     if (!addonList) {
       return { from: this.actorID, error: "noAddons",
                message: "This root actor has no browser addons." };
     }
 
+    // Reattach the onListChanged listener now that a client requested the list.
+    addonList.onListChanged = this._onAddonListChanged;
+
     return addonList.getList().then((addonActors) => {
       let addonActorPool = new ActorPool(this.conn);
       for (let addonActor of addonActors) {
         addonActorPool.addActor(addonActor);
       }
 
       if (this._addonActorPool) {
         this.conn.removeActorPool(this._addonActorPool);
       }
       this._addonActorPool = addonActorPool;
       this.conn.addActorPool(this._addonActorPool);
 
-      addonList.onListChanged = this._onAddonListChanged;
-
       return {
         "from": this.actorID,
         "addons": addonActors.map(addonActor => addonActor.form())
       };
     });
   },
 
   onAddonListChanged: function () {
@@ -418,28 +419,29 @@ RootActor.prototype = {
 
   onListWorkers: function () {
     let workerList = this._parameters.workerList;
     if (!workerList) {
       return { from: this.actorID, error: "noWorkers",
                message: "This root actor has no workers." };
     }
 
+    // Reattach the onListChanged listener now that a client requested the list.
+    workerList.onListChanged = this._onWorkerListChanged;
+
     return workerList.getList().then(actors => {
       let pool = new ActorPool(this.conn);
       for (let actor of actors) {
         pool.addActor(actor);
       }
 
       this.conn.removeActorPool(this._workerActorPool);
       this._workerActorPool = pool;
       this.conn.addActorPool(this._workerActorPool);
 
-      workerList.onListChanged = this._onWorkerListChanged;
-
       return {
         "from": this.actorID,
         "workers": actors.map(actor => actor.form())
       };
     });
   },
 
   onWorkerListChanged: function () {
@@ -449,28 +451,29 @@ RootActor.prototype = {
 
   onListServiceWorkerRegistrations: function () {
     let registrationList = this._parameters.serviceWorkerRegistrationList;
     if (!registrationList) {
       return { from: this.actorID, error: "noServiceWorkerRegistrations",
                message: "This root actor has no service worker registrations." };
     }
 
+    // Reattach the onListChanged listener now that a client requested the list.
+    registrationList.onListChanged = this._onServiceWorkerRegistrationListChanged;
+
     return registrationList.getList().then(actors => {
       let pool = new ActorPool(this.conn);
       for (let actor of actors) {
         pool.addActor(actor);
       }
 
       this.conn.removeActorPool(this._serviceWorkerRegistrationActorPool);
       this._serviceWorkerRegistrationActorPool = pool;
       this.conn.addActorPool(this._serviceWorkerRegistrationActorPool);
 
-      registrationList.onListChanged = this._onServiceWorkerRegistrationListChanged;
-
       return {
         "from": this.actorID,
         "registrations": actors.map(actor => actor.form())
       };
     });
   },
 
   onServiceWorkerRegistrationListChanged: function () {
--- a/devtools/shared/css/generated/properties-db.js
+++ b/devtools/shared/css/generated/properties-db.js
@@ -3040,16 +3040,17 @@ exports.CSS_PROPERTIES = {
       "text-decoration-color",
       "text-decoration-line",
       "text-decoration-style",
       "text-emphasis-color",
       "text-emphasis-position",
       "text-emphasis-style",
       "-webkit-text-fill-color",
       "text-indent",
+      "text-justify",
       "text-orientation",
       "text-overflow",
       "text-rendering",
       "text-shadow",
       "-moz-text-size-adjust",
       "-webkit-text-stroke-color",
       "-webkit-text-stroke-width",
       "text-transform",
@@ -9482,16 +9483,20 @@ exports.PREFERENCES = [
     "text-combine-upright",
     "layout.css.text-combine-upright.enabled"
   ],
   [
     "-webkit-text-fill-color",
     "layout.css.prefixes.webkit"
   ],
   [
+    "text-justify",
+    "layout.css.text-justify.enabled"
+  ],
+  [
     "-webkit-text-stroke",
     "layout.css.prefixes.webkit"
   ],
   [
     "-webkit-text-stroke-color",
     "layout.css.prefixes.webkit"
   ],
   [
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -115,16 +115,17 @@ static mozilla::LazyLogModule gMediaElem
 
 #include "nsIPermissionManager.h"
 #include "nsDocShell.h"
 
 #include "mozilla/EventStateManager.h"
 
 #include "mozilla/dom/HTMLVideoElement.h"
 #include "mozilla/dom/VideoPlaybackQuality.h"
+#include "HTMLMediaElement.h"
 
 using namespace mozilla::layers;
 using mozilla::net::nsMediaFragmentURIParser;
 
 namespace mozilla {
 namespace dom {
 
 // Number of milliseconds between progress events as defined by spec
@@ -1477,16 +1478,24 @@ HTMLMediaElement::MozRequestDebugInfo(Er
   RefPtr<Promise> promise = CreateDOMPromise(aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   nsAutoString result;
   GetMozDebugReaderData(result);
 
+  if (mMediaKeys) {
+    nsString EMEInfo;
+    GetEMEInfo(EMEInfo);
+    result.AppendLiteral("EME Info: ");
+    result.Append(EMEInfo);
+    result.AppendLiteral("\n");
+  }
+
   if (mDecoder) {
     mDecoder->RequestDebugInfo()->Then(
       AbstractThread::MainThread(), __func__,
       [promise, result] (const nsACString& aString) {
         promise->MaybeResolve(result + NS_ConvertUTF8toUTF16(aString));
       },
       [promise, result] () {
         promise->MaybeResolve(result);
@@ -7340,16 +7349,35 @@ HTMLMediaElement::AsyncRejectPendingPlay
                                                      TakePendingPlayPromises(),
                                                      aError);
 
   OwnerDoc()->Dispatch("nsResolveOrRejectPendingPlayPromisesRunner",
                        TaskCategory::Other,
                        event.forget());
 }
 
+void
+HTMLMediaElement::GetEMEInfo(nsString& aEMEInfo)
+{
+  if (!mMediaKeys) {
+    return;
+  }
+
+  nsString keySystem;
+  mMediaKeys->GetKeySystem(keySystem);
+
+  nsString sessionsInfo;
+  mMediaKeys->GetSessionsInfo(sessionsInfo);
+
+  aEMEInfo.AppendLiteral("Key System=");
+  aEMEInfo.Append(keySystem);
+  aEMEInfo.AppendLiteral(" SessionsInfo=");
+  aEMEInfo.Append(sessionsInfo);
+}
+
 bool HasDebuggerPrivilege(JSContext* aCx, JSObject* aObj)
 {
   return nsContentUtils::CallerHasPermission(aCx,
                                              NS_LITERAL_STRING("debugger"));
  }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -282,16 +282,18 @@ public:
   // principal. If there are no live video tracks but content has been rendered
   // to the image container, we return the last video principal we had. Should
   // the image container be empty with no live video tracks, we return nullptr.
   already_AddRefed<nsIPrincipal> GetCurrentVideoPrincipal();
 
   // called to notify that the principal of the decoder's media resource has changed.
   void NotifyDecoderPrincipalChanged() final override;
 
+  void GetEMEInfo(nsString& aEMEInfo);
+
   // An interface for observing principal changes on the media elements
   // MediaDecoder. This will also be notified if the active CORSMode changes.
   class DecoderPrincipalChangeObserver
   {
   public:
     virtual void NotifyDecoderPrincipalChanged() = 0;
   };
 
--- a/dom/ipc/ContentPrefs.cpp
+++ b/dom/ipc/ContentPrefs.cpp
@@ -136,16 +136,17 @@ const char* mozilla::dom::ContentPrefs::
   "layout.css.scope-pseudo.enabled",
   "layout.css.scroll-behavior.property-enabled",
   "layout.css.scroll-snap.enabled",
   "layout.css.servo.enabled",
   "layout.css.shape-outside.enabled",
   "layout.css.text-align-unsafe-value.enabled",
   "layout.css.text-combine-upright-digits.enabled",
   "layout.css.text-combine-upright.enabled",
+  "layout.css.text-justify.enabled",
   "layout.css.touch_action.enabled",
   "layout.css.unprefixing-service.enabled",
   "layout.css.unprefixing-service.globally-whitelisted",
   "layout.css.unprefixing-service.include-test-domains",
   "layout.css.variables.enabled",
   "layout.css.visited_links_enabled",
   "layout.idle_period.required_quiescent_frames",
   "layout.idle_period.time_limit",
--- a/dom/media/AbstractMediaDecoder.h
+++ b/dom/media/AbstractMediaDecoder.h
@@ -96,22 +96,16 @@ public:
   }
 
   // Return an abstract thread on which to run main thread runnables.
   virtual AbstractThread* AbstractMainThread() const = 0;
 
 protected:
   virtual void UpdateEstimatedMediaDuration(int64_t aDuration) { };
 public:
-  void DispatchUpdateEstimatedMediaDuration(int64_t aDuration)
-  {
-    NS_DispatchToMainThread(NewRunnableMethod<int64_t>(
-      this, &AbstractMediaDecoder::UpdateEstimatedMediaDuration, aDuration));
-  }
-
   virtual VideoFrameContainer* GetVideoFrameContainer() = 0;
   virtual mozilla::layers::ImageContainer* GetImageContainer() = 0;
 
   // Returns the owner of this decoder or null when the decoder is shutting
   // down. The owner should only be used on the main thread.
   virtual MediaDecoderOwner* GetOwner() const = 0;
 
   // Set by Reader if the current audio track can be offloaded
--- a/dom/media/MediaPrefs.h
+++ b/dom/media/MediaPrefs.h
@@ -160,17 +160,17 @@ private:
   DECL_MEDIA_PREF("media.decoder.limit",                      MediaDecoderLimit, int32_t, MediaDecoderLimitDefault());
 
   // Ogg
   DECL_MEDIA_PREF("media.ogg.enabled",                        OggEnabled, bool, true);
   // Flac
   DECL_MEDIA_PREF("media.ogg.flac.enabled",                   FlacInOgg, bool, false);
   DECL_MEDIA_PREF("media.flac.enabled",                       FlacEnabled, bool, true);
 
-#if defined(MOZ_RUST_MP4PARSE) && !defined(RELEASE_OR_BETA)
+#if !defined(RELEASE_OR_BETA)
   DECL_MEDIA_PREF("media.rust.test_mode",                     RustTestMode, bool, false);
 #endif
 
 public:
   // Manage the singleton:
   static MediaPrefs& GetSingleton();
   static bool SingletonExists();
 
--- a/dom/media/eme/MediaKeyStatusMap.cpp
+++ b/dom/media/eme/MediaKeyStatusMap.cpp
@@ -90,16 +90,23 @@ MediaKeyStatusMap::GetIterableLength() c
 
 TypedArrayCreator<ArrayBuffer>
 MediaKeyStatusMap::GetKeyAtIndex(uint32_t aIndex) const
 {
   MOZ_ASSERT(aIndex < GetIterableLength());
   return TypedArrayCreator<ArrayBuffer>(mStatuses[aIndex].mKeyId);
 }
 
+nsString
+MediaKeyStatusMap::GetKeyIDAsHexString(uint32_t aIndex) const
+{
+  MOZ_ASSERT(aIndex < GetIterableLength());
+  return NS_ConvertUTF8toUTF16(ToHexString(mStatuses[aIndex].mKeyId));
+}
+
 MediaKeyStatus
 MediaKeyStatusMap::GetValueAtIndex(uint32_t aIndex) const
 {
   MOZ_ASSERT(aIndex < GetIterableLength());
   return mStatuses[aIndex].mStatus;
 }
 
 uint32_t
--- a/dom/media/eme/MediaKeyStatusMap.h
+++ b/dom/media/eme/MediaKeyStatusMap.h
@@ -48,16 +48,17 @@ public:
            const ArrayBufferViewOrArrayBuffer& aKey,
            JS::MutableHandle<JS::Value> aOutValue,
            ErrorResult& aOutRv) const;
   bool Has(const ArrayBufferViewOrArrayBuffer& aKey) const;
   uint32_t Size() const;
 
   uint32_t GetIterableLength() const;
   TypedArrayCreator<ArrayBuffer> GetKeyAtIndex(uint32_t aIndex) const;
+  nsString GetKeyIDAsHexString(uint32_t aIndex) const;
   MediaKeyStatus GetValueAtIndex(uint32_t aIndex) const;
 
   void Update(const nsTArray<CDMCaps::KeyStatus>& keys);
 
 private:
 
   nsCOMPtr<nsPIDOMWindowInner> mParent;
 
--- a/dom/media/eme/MediaKeys.cpp
+++ b/dom/media/eme/MediaKeys.cpp
@@ -572,10 +572,38 @@ MediaKeys::Bind(HTMLMediaElement* aEleme
 
 void
 MediaKeys::Unbind()
 {
   MOZ_ASSERT(NS_IsMainThread());
   mElement = nullptr;
 }
 
+void
+MediaKeys::GetSessionsInfo(nsString& sessionsInfo)
+{
+  for (KeySessionHashMap::Iterator it = mKeySessions.Iter();
+       !it.Done();
+       it.Next()) {
+    MediaKeySession* keySession = it.Data();
+    nsString sessionID;
+    keySession->GetSessionId(sessionID);
+    sessionsInfo.AppendLiteral("(sid:");
+    sessionsInfo.Append(sessionID);
+    MediaKeyStatusMap* keyStatusMap = keySession->KeyStatuses();
+    for (uint32_t i = 0; i < keyStatusMap->GetIterableLength(); i++) {
+      nsString keyID = keyStatusMap->GetKeyIDAsHexString(i);
+      sessionsInfo.AppendLiteral("(kid:");
+      sessionsInfo.Append(keyID);
+      using IntegerType = typename std::underlying_type<MediaKeyStatus>::type;
+      auto idx = static_cast<IntegerType>(keyStatusMap->GetValueAtIndex(i));
+      const char* keyStatus = MediaKeyStatusValues::strings[idx].value;
+      sessionsInfo.AppendLiteral(" status:");
+      sessionsInfo.Append(
+        NS_ConvertUTF8toUTF16((nsDependentCString(keyStatus))));
+      sessionsInfo.AppendLiteral(")");
+    }
+    sessionsInfo.AppendLiteral(")");
+  }
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/eme/MediaKeys.h
+++ b/dom/media/eme/MediaKeys.h
@@ -123,16 +123,18 @@ public:
 
   // Called by CDMProxy when CDM crashes or shuts down. It is different from
   // Shutdown which is called from the script/dom side.
   void Terminated();
 
   // Returns true if this MediaKeys has been bound to a media element.
   bool IsBoundToMediaElement() const;
 
+  void GetSessionsInfo(nsString& sessionsInfo);
+
 private:
 
   // Instantiate CDMProxy instance.
   // It could be MediaDrmCDMProxy (Widevine on Fennec) or GMPCDMProxy (the rest).
   already_AddRefed<CDMProxy> CreateCDMProxy();
 
   // Removes promise from mPromises, and returns it.
   already_AddRefed<DetailedPromise> RetrievePromise(PromiseId aId);
--- a/dom/media/gmp/GMPContentChild.cpp
+++ b/dom/media/gmp/GMPContentChild.cpp
@@ -94,17 +94,17 @@ GMPContentChild::DeallocPGMPVideoEncoder
 }
 
 mozilla::ipc::IPCResult
 GMPContentChild::RecvPGMPDecryptorConstructor(PGMPDecryptorChild* aActor)
 {
   GMPDecryptorChild* child = static_cast<GMPDecryptorChild*>(aActor);
 
   void* ptr = nullptr;
-  GMPErr err = mGMPChild->GetAPI(GMP_API_DECRYPTOR, nullptr, &ptr, aActor->Id());
+  GMPErr err = mGMPChild->GetAPI(GMP_API_DECRYPTOR, nullptr, &ptr, child->DecryptorId());
   if (err != GMPNoErr || !ptr) {
     NS_WARNING("GMPGetAPI call failed trying to construct decryptor.");
     return IPC_FAIL_NO_REASON(this);
   }
   child->Init(static_cast<GMPDecryptor*>(ptr));
 
   return IPC_OK();
 }
--- a/dom/media/gmp/GMPDecryptorChild.cpp
+++ b/dom/media/gmp/GMPDecryptorChild.cpp
@@ -15,19 +15,22 @@
 #define ON_GMP_THREAD() (mPlugin->GMPMessageLoop() == MessageLoop::current())
 
 #define CALL_ON_GMP_THREAD(_func, ...) \
   CallOnGMPThread(&GMPDecryptorChild::_func, __VA_ARGS__)
 
 namespace mozilla {
 namespace gmp {
 
+static uint32_t sDecryptorCount = 1;
+
 GMPDecryptorChild::GMPDecryptorChild(GMPContentChild* aPlugin)
   : mSession(nullptr)
   , mPlugin(aPlugin)
+  , mDecryptorId(sDecryptorCount++)
 {
   MOZ_ASSERT(mPlugin);
 }
 
 GMPDecryptorChild::~GMPDecryptorChild()
 {
 }
 
@@ -68,17 +71,17 @@ void
 GMPDecryptorChild::Init(GMPDecryptor* aSession)
 {
   MOZ_ASSERT(aSession);
   mSession = aSession;
   // The ID of this decryptor is the IPDL actor ID. Note it's unique inside
   // the child process, but not necessarily across all gecko processes. However,
   // since GMPDecryptors are segregated by node ID/origin, we shouldn't end up
   // with clashes in the content process.
-  SendSetDecryptorId(Id());
+  SendSetDecryptorId(DecryptorId());
 }
 
 void
 GMPDecryptorChild::SetSessionId(uint32_t aCreateSessionToken,
                                 const char* aSessionId,
                                 uint32_t aSessionIdLength)
 {
   CALL_ON_GMP_THREAD(SendSetSessionId,
--- a/dom/media/gmp/GMPDecryptorChild.h
+++ b/dom/media/gmp/GMPDecryptorChild.h
@@ -70,16 +70,17 @@ public:
 
   void Decrypted(GMPBuffer* aBuffer, GMPErr aResult) override;
 
   void BatchedKeyStatusChanged(const char* aSessionId,
                                uint32_t aSessionIdLength,
                                const GMPMediaKeyInfo* aKeyInfos,
                                uint32_t aKeyInfosLength) override;
 
+  uint32_t DecryptorId() const { return mDecryptorId; }
 private:
   ~GMPDecryptorChild();
 
   // GMPDecryptorChild
   mozilla::ipc::IPCResult RecvInit(const bool& aDistinctiveIdentifierRequired,
                                    const bool& aPersistentStateRequired) override;
 
   mozilla::ipc::IPCResult RecvCreateSession(const uint32_t& aCreateSessionToken,
@@ -116,14 +117,16 @@ private:
 
   template<typename MethodType, typename... ParamType>
   void CallOnGMPThread(MethodType, ParamType&&...);
 
   // GMP's GMPDecryptor implementation.
   // Only call into this on the (GMP process) main thread.
   GMPDecryptor* mSession;
   GMPContentChild* mPlugin;
+
+  const uint32_t mDecryptorId;
 };
 
 } // namespace gmp
 } // namespace mozilla
 
 #endif // GMPDecryptorChild_h_
--- a/dom/smil/test/mochitest.ini
+++ b/dom/smil/test/mochitest.ini
@@ -52,9 +52,10 @@ skip-if = toolkit == 'android' #TIMED_OU
 [test_smilSyncTransform.xhtml]
 [test_smilSyncbaseTarget.xhtml]
 [test_smilTextZoom.xhtml]
 [test_smilTimeEvents.xhtml]
 [test_smilTiming.xhtml]
 [test_smilTimingZeroIntervals.xhtml]
 [test_smilUpdatedInterval.xhtml]
 [test_smilValues.xhtml]
+[test_smilWithXlink.xhtml]
 [test_smilXHR.xhtml]
new file mode 100644
--- /dev/null
+++ b/dom/smil/test/test_smilWithXlink.xhtml
@@ -0,0 +1,48 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <title>Test for animate with xlink:href attribute.</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <style>
+    div#target {
+      width: 300px;
+      height: 100px;
+      background-color: red;
+    }
+  </style>
+</head>
+<body>
+<p id="display"></p>
+<div id="target"></div>
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     id="svg">
+  <animate xlink:href="#target"
+           attributeName="width" from="0" to="200" dur="10s" fill="freeze"/>
+</svg>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+function runTest() {
+  var svg = document.getElementById("svg");
+  var target = document.getElementById("target");
+
+  svg.pauseAnimations();
+  svg.setCurrentTime(5);
+
+  var cs = getComputedStyle(target);
+  is(cs.width, "100px", "SMIL should affect outer element.");
+
+  SimpleTest.finish();
+  return;
+}
+
+window.addEventListener("load", runTest);
+
+]]>
+</script>
+</pre>
+</body>
+</html>
--- a/dom/svg/SVGFETurbulenceElement.cpp
+++ b/dom/svg/SVGFETurbulenceElement.cpp
@@ -128,17 +128,24 @@ SVGFETurbulenceElement::GetPrimitiveDesc
   float fX = mNumberPairAttributes[BASE_FREQ].GetAnimValue(nsSVGNumberPair::eFirst);
   float fY = mNumberPairAttributes[BASE_FREQ].GetAnimValue(nsSVGNumberPair::eSecond);
   float seed = mNumberAttributes[OCTAVES].GetAnimValue();
   uint32_t octaves = clamped(mIntegerAttributes[OCTAVES].GetAnimValue(), 0, MAX_OCTAVES);
   uint32_t type = mEnumAttributes[TYPE].GetAnimValue();
   uint16_t stitch = mEnumAttributes[STITCHTILES].GetAnimValue();
 
   if (fX == 0 || fY == 0) {
-    return FilterPrimitiveDescription(PrimitiveType::Empty);
+    // A base frequency of zero results in transparent black for
+    // type="turbulence" and in 50% alpha 50% gray for type="fractalNoise".
+    if (type == SVG_TURBULENCE_TYPE_TURBULENCE) {
+      return FilterPrimitiveDescription(PrimitiveType::Empty);
+    }
+    FilterPrimitiveDescription descr(PrimitiveType::Flood);
+    descr.Attributes().Set(eFloodColor, Color(0.5, 0.5, 0.5, 0.5));
+    return descr;
   }
 
   // We interpret the base frequency as relative to user space units. In other
   // words, we consider one turbulence base period to be 1 / fX user space
   // units wide and 1 / fY user space units high. We do not scale the frequency
   // depending on the filter primitive region.
   gfxRect firstPeriodInUserSpace(0, 0, 1 / fX, 1 / fY);
   gfxRect firstPeriodInFilterSpace = aInstance->UserSpaceToFilterSpace(firstPeriodInUserSpace);
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -320,16 +320,17 @@ private:
 
   DECL_GFX_PREF(Live, "dom.ipc.plugins.asyncdrawing.enabled",  PluginAsyncDrawingEnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.meta-viewport.enabled",             MetaViewportEnabled, bool, false);
   DECL_GFX_PREF(Once, "dom.vr.enabled",                        VREnabled, bool, false);
   DECL_GFX_PREF(Once, "dom.vr.oculus.enabled",                 VROculusEnabled, bool, true);
   DECL_GFX_PREF(Once, "dom.vr.openvr.enabled",                 VROpenVREnabled, bool, false);
   DECL_GFX_PREF(Once, "dom.vr.osvr.enabled",                   VROSVREnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.vr.poseprediction.enabled",         VRPosePredictionEnabled, bool, false);
+  DECL_GFX_PREF(Once, "dom.vr.puppet.enabled",                 VRPuppetEnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.w3c_pointer_events.enabled",        PointerEventsEnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.w3c_touch_events.enabled",          TouchEventsEnabled, int32_t, 0);
 
   DECL_GFX_PREF(Live, "general.smoothScroll",                  SmoothScrollEnabled, bool, true);
   DECL_GFX_PREF(Live, "general.smoothScroll.currentVelocityWeighting",
                 SmoothScrollCurrentVelocityWeighting, float, 0.25);
   DECL_GFX_PREF(Live, "general.smoothScroll.durationToIntervalRatio",
                 SmoothScrollDurationToIntervalRatio, int32_t, 200);
--- a/gfx/vr/VRManager.cpp
+++ b/gfx/vr/VRManager.cpp
@@ -17,16 +17,17 @@
 #include "gfxPrefs.h"
 #include "gfxVR.h"
 #if defined(XP_WIN)
 #include "gfxVROculus.h"
 #endif
 #if defined(XP_WIN) || defined(XP_MACOSX) || defined(XP_LINUX)
 #include "gfxVROSVR.h"
 #endif
+#include "gfxVRPuppet.h"
 #include "ipc/VRLayerParent.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
 using namespace mozilla::gl;
 
 namespace mozilla {
@@ -83,16 +84,20 @@ VRManager::VRManager()
   }
 
   // OSVR is cross platform compatible
   mgr = VRSystemManagerOSVR::Create();
   if (mgr) {
       mManagers.AppendElement(mgr);
   }
 #endif
+  mgr = VRSystemManagerPuppet::Create();
+  if (mgr) {
+    mManagers.AppendElement(mgr);
+  }
   // Enable gamepad extensions while VR is enabled.
   // Preference only can be set at the Parent process.
   if (XRE_IsParentProcess() && gfxPrefs::VREnabled()) {
     Preferences::SetBool("dom.gamepad.extensions.enabled", true);
   }
 }
 
 VRManager::~VRManager()
--- a/gfx/vr/gfxVR.h
+++ b/gfx/vr/gfxVR.h
@@ -29,16 +29,17 @@ namespace gfx {
 class VRLayerParent;
 class VRDisplayHost;
 class VRControllerHost;
 
 enum class VRDeviceType : uint16_t {
   Oculus,
   OpenVR,
   OSVR,
+  Puppet,
   NumVRDeviceTypes
 };
 
 enum class VRDisplayCapabilityFlags : uint16_t {
   Cap_None = 0,
   /**
    * Cap_Position is set if the VRDisplay is capable of tracking its position.
    */
new file mode 100644
--- /dev/null
+++ b/gfx/vr/gfxVRPuppet.cpp
@@ -0,0 +1,459 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if defined(XP_WIN)
+#include "CompositorD3D11.h"
+#include "TextureD3D11.h"
+#endif // XP_WIN
+
+#include "gfxVRPuppet.h"
+
+#include "mozilla/dom/GamepadEventTypes.h"
+#include "mozilla/dom/GamepadBinding.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::gfx::impl;
+using namespace mozilla::layers;
+
+
+// Reminder: changing the order of these buttons may break web content
+static const uint64_t kPuppetButtonMask[] = {
+  1,
+  2,
+  4,
+  8
+};
+
+static const uint32_t kNumPuppetButtonMask = sizeof(kPuppetButtonMask) /
+                                             sizeof(uint64_t);
+
+static const uint32_t kPuppetAxes[] = {
+  0,
+  1,
+  2
+};
+
+static const uint32_t kNumPuppetAxis = sizeof(kPuppetAxes) /
+                                       sizeof(uint32_t);
+
+VRDisplayPuppet::VRDisplayPuppet()
+ : VRDisplayHost(VRDeviceType::Puppet)
+ , mIsPresenting(false)
+{
+  MOZ_COUNT_CTOR_INHERITED(VRDisplayPuppet, VRDisplayHost);
+
+  mDisplayInfo.mDisplayName.AssignLiteral("Puppet HMD");
+  mDisplayInfo.mIsConnected = true;
+  mDisplayInfo.mIsMounted = false;
+  mDisplayInfo.mCapabilityFlags = VRDisplayCapabilityFlags::Cap_None |
+                                  VRDisplayCapabilityFlags::Cap_Orientation |
+                                  VRDisplayCapabilityFlags::Cap_Position |
+                                  VRDisplayCapabilityFlags::Cap_External |
+                                  VRDisplayCapabilityFlags::Cap_Present |
+                                  VRDisplayCapabilityFlags::Cap_StageParameters;
+  mDisplayInfo.mEyeResolution.width = 1836; // 1080 * 1.7
+  mDisplayInfo.mEyeResolution.height = 2040; // 1200 * 1.7
+
+  // SteamVR gives the application a single FOV to use; it's not configurable as with Oculus
+  for (uint32_t eye = 0; eye < 2; ++eye) {
+    mDisplayInfo.mEyeTranslation[eye].x = 0.0f;
+    mDisplayInfo.mEyeTranslation[eye].y = 0.0f;
+    mDisplayInfo.mEyeTranslation[eye].z = 0.0f;
+    mDisplayInfo.mEyeFOV[eye] = VRFieldOfView(45.0, 45.0, 45.0, 45.0);
+  }
+
+  // default: 1m x 1m space, 0.75m high in seated position
+  mDisplayInfo.mStageSize.width = 1.0f;
+  mDisplayInfo.mStageSize.height = 1.0f;
+
+  mDisplayInfo.mSittingToStandingTransform._11 = 1.0f;
+  mDisplayInfo.mSittingToStandingTransform._12 = 0.0f;
+  mDisplayInfo.mSittingToStandingTransform._13 = 0.0f;
+  mDisplayInfo.mSittingToStandingTransform._14 = 0.0f;
+
+  mDisplayInfo.mSittingToStandingTransform._21 = 0.0f;
+  mDisplayInfo.mSittingToStandingTransform._22 = 1.0f;
+  mDisplayInfo.mSittingToStandingTransform._23 = 0.0f;
+  mDisplayInfo.mSittingToStandingTransform._24 = 0.0f;
+
+  mDisplayInfo.mSittingToStandingTransform._31 = 0.0f;
+  mDisplayInfo.mSittingToStandingTransform._32 = 0.0f;
+  mDisplayInfo.mSittingToStandingTransform._33 = 1.0f;
+  mDisplayInfo.mSittingToStandingTransform._34 = 0.0f;
+
+  mDisplayInfo.mSittingToStandingTransform._41 = 0.0f;
+  mDisplayInfo.mSittingToStandingTransform._42 = 0.75f;
+  mDisplayInfo.mSittingToStandingTransform._43 = 0.0f;
+
+  mSensorState.Clear();
+  mSensorState.timestamp = PR_Now();
+
+  gfx::Quaternion rot;
+
+  mSensorState.flags |= VRDisplayCapabilityFlags::Cap_Orientation;
+  mSensorState.orientation[0] = rot.x;
+  mSensorState.orientation[1] = rot.y;
+  mSensorState.orientation[2] = rot.z;
+  mSensorState.orientation[3] = rot.w;
+  mSensorState.angularVelocity[0] = 0.0f;
+  mSensorState.angularVelocity[1] = 0.0f;
+  mSensorState.angularVelocity[2] = 0.0f;
+
+  mSensorState.flags |= VRDisplayCapabilityFlags::Cap_Position;
+  mSensorState.position[0] = 0.0f;
+  mSensorState.position[1] = 0.0f;
+  mSensorState.position[2] = 0.0f;
+  mSensorState.linearVelocity[0] = 0.0f;
+  mSensorState.linearVelocity[1] = 0.0f;
+  mSensorState.linearVelocity[2] = 0.0f;
+}
+
+VRDisplayPuppet::~VRDisplayPuppet()
+{
+  MOZ_COUNT_DTOR_INHERITED(VRDisplayPuppet, VRDisplayHost);
+}
+
+void
+VRDisplayPuppet::SetDisplayInfo(const VRDisplayInfo& aDisplayInfo)
+{
+  mDisplayInfo = aDisplayInfo;
+}
+
+void
+VRDisplayPuppet::Destroy()
+{
+  StopPresentation();
+}
+
+void
+VRDisplayPuppet::ZeroSensor()
+{
+}
+
+VRHMDSensorState
+VRDisplayPuppet::GetSensorState()
+{
+  return GetSensorState(0.0f);
+}
+
+VRHMDSensorState
+VRDisplayPuppet::GetImmediateSensorState()
+{
+  return GetSensorState(0.0f);
+}
+
+VRHMDSensorState
+VRDisplayPuppet::GetSensorState(double timeOffset)
+{
+  return mSensorState;
+}
+
+void
+VRDisplayPuppet::SetSensorState(const VRHMDSensorState& aSensorState)
+{
+  mSensorState = aSensorState;
+}
+
+void
+VRDisplayPuppet::StartPresentation()
+{
+  if (mIsPresenting) {
+    return;
+  }
+  mIsPresenting = true;
+}
+
+void
+VRDisplayPuppet::StopPresentation()
+{
+  if (!mIsPresenting) {
+    return;
+  }
+
+  mIsPresenting = false;
+}
+
+#if defined(XP_WIN)
+void
+VRDisplayPuppet::SubmitFrame(TextureSourceD3D11* aSource,
+                             const IntSize& aSize,
+                             const VRHMDSensorState& aSensorState,
+                             const gfx::Rect& aLeftEyeRect,
+                             const gfx::Rect& aRightEyeRect)
+{
+  if (!mIsPresenting) {
+    return;
+  }
+
+  ID3D11Texture2D* tex = aSource->GetD3D11Texture();
+  MOZ_ASSERT(tex);
+
+  // TODO: Bug 1343730, Need to block until the next simulated
+  // vblank interval and capture frames for use in reftests.
+
+  // Trigger the next VSync immediately
+  VRManager *vm = VRManager::Get();
+  MOZ_ASSERT(vm);
+  vm->NotifyVRVsync(mDisplayInfo.mDisplayID);
+}
+#else
+void
+VRDisplayPuppet::SubmitFrame(TextureSourceOGL* aSource,
+                             const IntSize& aSize,
+                             const VRHMDSensorState& aSensorState,
+                             const gfx::Rect& aLeftEyeRect,
+                             const gfx::Rect& aRightEyeRect)
+{
+  if (!mIsPresenting) {
+    return;
+  }
+
+  // TODO: Bug 1343730, Need to block until the next simulated
+  // vblank interval and capture frames for use in reftests.
+
+  // Trigger the next VSync immediately
+  VRManager *vm = VRManager::Get();
+  MOZ_ASSERT(vm);
+  vm->NotifyVRVsync(mDisplayInfo.mDisplayID);
+}
+#endif
+
+void
+VRDisplayPuppet::NotifyVSync()
+{
+  // We update mIsConneced once per frame.
+  mDisplayInfo.mIsConnected = true;
+}
+
+VRControllerPuppet::VRControllerPuppet(dom::GamepadHand aHand)
+  : VRControllerHost(VRDeviceType::Puppet)
+  , mButtonPressState(0)
+{
+  MOZ_COUNT_CTOR_INHERITED(VRControllerPuppet, VRControllerHost);
+  mControllerInfo.mControllerName.AssignLiteral("Puppet Gamepad");
+  mControllerInfo.mMappingType = GamepadMappingType::_empty;
+  mControllerInfo.mHand = aHand;
+  mControllerInfo.mNumButtons = kNumPuppetButtonMask;
+  mControllerInfo.mNumAxes = kNumPuppetAxis;
+}
+
+VRControllerPuppet::~VRControllerPuppet()
+{
+  MOZ_COUNT_DTOR_INHERITED(VRControllerPuppet, VRControllerHost);
+}
+
+void
+VRControllerPuppet::SetButtonPressState(uint32_t aButton, bool aPressed)
+{
+  const uint64_t buttonMask = kPuppetButtonMask[aButton];
+  uint64_t pressedBit = GetButtonPressed();
+
+  if (aPressed) {
+    pressedBit |= kPuppetButtonMask[aButton];
+  } else if (pressedBit & buttonMask) {
+    // this button was pressed but is released now.
+    uint64_t mask = 0xff ^ buttonMask;
+    pressedBit &= mask;
+  }
+
+  mButtonPressState = pressedBit;
+}
+
+uint64_t
+VRControllerPuppet::GetButtonPressState()
+{
+  return mButtonPressState;
+}
+
+void
+VRControllerPuppet::SetAxisMoveState(uint32_t aAxis, double aValue)
+{
+  MOZ_ASSERT((sizeof(mAxisMoveState) / sizeof(float)) == kNumPuppetAxis);
+  MOZ_ASSERT(aAxis <= kNumPuppetAxis);
+
+  mAxisMoveState[aAxis] = aValue;
+}
+
+double
+VRControllerPuppet::GetAxisMoveState(uint32_t aAxis)
+{
+  return mAxisMoveState[aAxis];
+}
+
+void
+VRControllerPuppet::SetPoseMoveState(const dom::GamepadPoseState& aPose)
+{
+  mPoseState = aPose;
+}
+
+const dom::GamepadPoseState&
+VRControllerPuppet::GetPoseMoveState()
+{
+  return mPoseState;
+}
+
+float
+VRControllerPuppet::GetAxisMove(uint32_t aAxis)
+{
+  return mAxisMove[aAxis];
+}
+
+void
+VRControllerPuppet::SetAxisMove(uint32_t aAxis, float aValue)
+{
+  mAxisMove[aAxis] = aValue;
+}
+
+VRSystemManagerPuppet::VRSystemManagerPuppet()
+{
+}
+
+/*static*/ already_AddRefed<VRSystemManagerPuppet>
+VRSystemManagerPuppet::Create()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!gfxPrefs::VREnabled() || !gfxPrefs::VRPuppetEnabled()) {
+    return nullptr;
+  }
+
+  RefPtr<VRSystemManagerPuppet> manager = new VRSystemManagerPuppet();
+  return manager.forget();
+}
+
+bool
+VRSystemManagerPuppet::Init()
+{
+  return true;
+}
+
+void
+VRSystemManagerPuppet::Destroy()
+{
+  mPuppetHMD = nullptr;
+}
+
+void
+VRSystemManagerPuppet::GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult)
+{
+  if (mPuppetHMD == nullptr) {
+    mPuppetHMD = new VRDisplayPuppet();
+  }
+  aHMDResult.AppendElement(mPuppetHMD);
+}
+
+void
+VRSystemManagerPuppet::HandleInput()
+{
+  RefPtr<impl::VRControllerPuppet> controller;
+  for (uint32_t i = 0; i < mPuppetController.Length(); ++i) {
+    controller = mPuppetController[i];
+    HandleButtonPress(i, controller->GetButtonPressState());
+
+    for (uint32_t j = 0; j < kNumPuppetAxis; ++j) {
+      HandleAxisMove(i, j, controller->GetAxisMoveState(j));
+    }
+    HandlePoseTracking(i, controller->GetPoseMoveState(), controller);
+  }
+}
+
+void
+VRSystemManagerPuppet::HandleButtonPress(uint32_t aControllerIdx,
+                                         uint64_t aButtonPressed)
+{
+  uint64_t buttonMask = 0;
+  RefPtr<impl::VRControllerPuppet> controller(mPuppetController[aControllerIdx]);
+  MOZ_ASSERT(controller);
+  uint64_t diff = (controller->GetButtonPressed() ^ aButtonPressed);
+
+  if (!diff) {
+    return;
+  }
+
+  for (uint32_t i = 0; i < kNumPuppetButtonMask; ++i) {
+    buttonMask = kPuppetButtonMask[i];
+
+    if (diff & buttonMask) {
+      // diff & aButtonPressed would be true while a new button press
+      // event, otherwise it is an old press event and needs to notify
+      // the button has been released.
+      NewButtonEvent(aControllerIdx, i, diff & aButtonPressed);
+    }
+  }
+
+  controller->SetButtonPressed(aButtonPressed);
+}
+
+void
+VRSystemManagerPuppet::HandleAxisMove(uint32_t aControllerIdx, uint32_t aAxis,
+                                      float aValue)
+{
+  RefPtr<impl::VRControllerPuppet> controller(mPuppetController[aControllerIdx]);
+  MOZ_ASSERT(controller);
+
+  if (controller->GetAxisMove(aAxis) != aValue) {
+    NewAxisMove(aControllerIdx, aAxis, aValue);
+    controller->SetAxisMove(aAxis, aValue);
+  }
+}
+
+void
+VRSystemManagerPuppet::HandlePoseTracking(uint32_t aControllerIdx,
+                                          const GamepadPoseState& aPose,
+                                          VRControllerHost* aController)
+{
+  MOZ_ASSERT(aController);
+  if (aPose != aController->GetPose()) {
+    aController->SetPose(aPose);
+    NewPoseState(aControllerIdx, aPose);
+  }
+}
+
+void
+VRSystemManagerPuppet::GetControllers(nsTArray<RefPtr<VRControllerHost>>& aControllerResult)
+{
+  aControllerResult.Clear();
+  for (uint32_t i = 0; i < mPuppetController.Length(); ++i) {
+    aControllerResult.AppendElement(mPuppetController[i]);
+  }
+}
+
+void
+VRSystemManagerPuppet::ScanForControllers()
+{
+  // We make VRSystemManagerPuppet has two controllers always.
+  const uint32_t newControllerCount = 2;
+
+  if (newControllerCount != mControllerCount) {
+    // controller count is changed, removing the existing gamepads first.
+    for (uint32_t i = 0; i < mPuppetController.Length(); ++i) {
+      RemoveGamepad(i);
+    }
+
+    mControllerCount = 0;
+    mPuppetController.Clear();
+
+    // Re-adding controllers to VRControllerManager.
+    for (uint32_t i = 0; i < newControllerCount; ++i) {
+      dom::GamepadHand hand = (i % 2) ? dom::GamepadHand::Right :
+                                        dom::GamepadHand::Left;
+      RefPtr<VRControllerPuppet> puppetController = new VRControllerPuppet(hand);
+      puppetController->SetIndex(mControllerCount);
+      mPuppetController.AppendElement(puppetController);
+
+      // Not already present, add it.
+      AddGamepad(puppetController->GetControllerInfo());
+      ++mControllerCount;
+    }
+  }
+}
+
+void
+VRSystemManagerPuppet::RemoveControllers()
+{
+  mPuppetController.Clear();
+  mControllerCount = 0;
+}
new file mode 100644
--- /dev/null
+++ b/gfx/vr/gfxVRPuppet.h
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_VR_PUPPET_H
+#define GFX_VR_PUPPET_H
+
+#include "nsTArray.h"
+#include "mozilla/RefPtr.h"
+
+#include "gfxVR.h"
+
+namespace mozilla {
+namespace gfx {
+namespace impl {
+
+class VRDisplayPuppet : public VRDisplayHost
+{
+public:
+  void SetDisplayInfo(const VRDisplayInfo& aDisplayInfo);
+  virtual void NotifyVSync() override;
+  virtual VRHMDSensorState GetSensorState() override;
+  virtual VRHMDSensorState GetImmediateSensorState() override;
+  void SetSensorState(const VRHMDSensorState& aSensorState);
+  void ZeroSensor() override;
+
+protected:
+  virtual void StartPresentation() override;
+  virtual void StopPresentation() override;
+#if defined(XP_WIN)
+  virtual void SubmitFrame(mozilla::layers::TextureSourceD3D11* aSource,
+                           const IntSize& aSize,
+                           const VRHMDSensorState& aSensorState,
+                           const gfx::Rect& aLeftEyeRect,
+                           const gfx::Rect& aRightEyeRect) override;
+#else
+  virtual void SubmitFrame(mozilla::layers::TextureSourceOGL* aSource,
+                           const IntSize& aSize,
+                           const VRHMDSensorState& aSensorState,
+                           const gfx::Rect& aLeftEyeRect,
+                           const gfx::Rect& aRightEyeRect);
+#endif // XP_WIN
+
+public:
+  explicit VRDisplayPuppet();
+
+protected:
+  virtual ~VRDisplayPuppet();
+  void Destroy();
+
+  VRHMDSensorState GetSensorState(double timeOffset);
+
+  bool mIsPresenting;
+
+private:
+  VRHMDSensorState mSensorState;
+};
+
+class VRControllerPuppet : public VRControllerHost
+{
+public:
+  explicit VRControllerPuppet(dom::GamepadHand aHand);
+  void SetButtonPressState(uint32_t aButton, bool aPressed);
+  uint64_t GetButtonPressState();
+  void SetAxisMoveState(uint32_t aAxis, double aValue);
+  double GetAxisMoveState(uint32_t aAxis);
+  void SetPoseMoveState(const dom::GamepadPoseState& aPose);
+  const dom::GamepadPoseState& GetPoseMoveState();
+  float GetAxisMove(uint32_t aAxis);
+  void SetAxisMove(uint32_t aAxis, float aValue);
+
+protected:
+  virtual ~VRControllerPuppet();
+
+private:
+  uint64_t mButtonPressState;
+  float mAxisMoveState[3];
+  float mAxisMove[3];
+  dom::GamepadPoseState mPoseState;
+};
+
+} // namespace impl
+
+class VRSystemManagerPuppet : public VRSystemManager
+{
+public:
+  static already_AddRefed<VRSystemManagerPuppet> Create();
+
+  virtual bool Init() override;
+  virtual void Destroy() override;
+  virtual void GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult) override;
+  virtual void HandleInput() override;
+  virtual void GetControllers(nsTArray<RefPtr<VRControllerHost>>&
+                              aControllerResult) override;
+  virtual void ScanForControllers() override;
+  virtual void RemoveControllers() override;
+
+protected:
+  VRSystemManagerPuppet();
+
+private:
+  virtual void HandleButtonPress(uint32_t aControllerIndex,
+                                 uint64_t aButtonPressed) override;
+  virtual void HandleAxisMove(uint32_t aControllerIndex, uint32_t aAxis,
+                              float aValue) override;
+  virtual void HandlePoseTracking(uint32_t aControllerIndex,
+                                  const dom::GamepadPoseState& aPose,
+                                  VRControllerHost* aController) override;
+
+  // there can only be one
+  RefPtr<impl::VRDisplayPuppet> mPuppetHMD;
+  nsTArray<RefPtr<impl::VRControllerPuppet>> mPuppetController;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif  /* GFX_VR_PUPPET_H*/
\ No newline at end of file
--- a/gfx/vr/moz.build
+++ b/gfx/vr/moz.build
@@ -20,16 +20,17 @@ LOCAL_INCLUDES += [
     '/gfx/layers/d3d11',
     '/gfx/thebes',
 ]
 
 UNIFIED_SOURCES += [
     'gfxVR.cpp',
     'gfxVROpenVR.cpp',
     'gfxVROSVR.cpp',
+    'gfxVRPuppet.cpp',
     'ipc/VRLayerChild.cpp',
     'ipc/VRLayerParent.cpp',
     'ipc/VRManagerChild.cpp',
     'ipc/VRManagerParent.cpp',
     'VRDisplayClient.cpp',
     'VRDisplayHost.cpp',
     'VRDisplayPresentation.cpp',
     'VRManager.cpp',
--- a/intl/build/nsI18nModule.cpp
+++ b/intl/build/nsI18nModule.cpp
@@ -40,16 +40,17 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsSt
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsCaseConversionImp2)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsCategoryImp,
                                          nsCategoryImp::GetInstance)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsEntityConverter)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsSaveAsCharset)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeNormalizer)
 
 NS_DEFINE_NAMED_CID(MOZ_LOCALESERVICE_CID);
+NS_DEFINE_NAMED_CID(MOZ_OSPREFERENCES_CID);
 NS_DEFINE_NAMED_CID(NS_LBRK_CID);
 NS_DEFINE_NAMED_CID(NS_WBRK_CID);
 NS_DEFINE_NAMED_CID(NS_SEMANTICUNITSCANNER_CID);
 NS_DEFINE_NAMED_CID(NS_UNICHARUTIL_CID);
 NS_DEFINE_NAMED_CID(NS_UNICHARCATEGORY_CID);
 NS_DEFINE_NAMED_CID(NS_ENTITYCONVERTER_CID);
 NS_DEFINE_NAMED_CID(NS_SAVEASCHARSET_CID);
 NS_DEFINE_NAMED_CID(NS_UNICODE_NORMALIZER_CID);
@@ -67,16 +68,17 @@ NS_DEFINE_NAMED_CID(NS_COLLATION_CID);
 NS_DEFINE_NAMED_CID(NS_COLLATION_CID);
 #endif
 #ifdef USE_MAC_LOCALE
 NS_DEFINE_NAMED_CID(NS_COLLATION_CID);
 #endif
 
 static const mozilla::Module::CIDEntry kIntlCIDs[] = {
     { &kMOZ_LOCALESERVICE_CID, false, nullptr, mozilla::intl::LocaleServiceConstructor },
+    { &kMOZ_OSPREFERENCES_CID, false, nullptr, mozilla::intl::OSPreferencesConstructor },
     { &kNS_LBRK_CID, false, nullptr, nsJISx4051LineBreakerConstructor },
     { &kNS_WBRK_CID, false, nullptr, nsSampleWordBreakerConstructor },
     { &kNS_SEMANTICUNITSCANNER_CID, false, nullptr, nsSemanticUnitScannerConstructor },
     { &kNS_UNICHARUTIL_CID, false, nullptr, nsCaseConversionImp2Constructor },
     { &kNS_UNICHARCATEGORY_CID, false, nullptr, nsCategoryImpConstructor },
     { &kNS_ENTITYCONVERTER_CID, false, nullptr, nsEntityConverterConstructor },
     { &kNS_SAVEASCHARSET_CID, false, nullptr, nsSaveAsCharsetConstructor },
     { &kNS_UNICODE_NORMALIZER_CID, false, nullptr, nsUnicodeNormalizerConstructor },
@@ -96,16 +98,17 @@ static const mozilla::Module::CIDEntry k
 #ifdef USE_MAC_LOCALE
     { &kNS_COLLATION_CID, false, nullptr, nsCollationMacUCConstructor },
 #endif
     { nullptr }
 };
 
 static const mozilla::Module::ContractIDEntry kIntlContracts[] = {
     { MOZ_LOCALESERVICE_CONTRACTID, &kMOZ_LOCALESERVICE_CID },
+    { MOZ_OSPREFERENCES_CONTRACTID, &kMOZ_OSPREFERENCES_CID },
     { NS_LBRK_CONTRACTID, &kNS_LBRK_CID },
     { NS_WBRK_CONTRACTID, &kNS_WBRK_CID },
     { NS_SEMANTICUNITSCANNER_CONTRACTID, &kNS_SEMANTICUNITSCANNER_CID },
     { NS_UNICHARUTIL_CONTRACTID, &kNS_UNICHARUTIL_CID },
     { NS_UNICHARCATEGORY_CONTRACTID, &kNS_UNICHARCATEGORY_CID },
     { NS_ENTITYCONVERTER_CONTRACTID, &kNS_ENTITYCONVERTER_CID },
     { NS_SAVEASCHARSET_CONTRACTID, &kNS_SAVEASCHARSET_CID },
     { NS_UNICODE_NORMALIZER_CONTRACTID, &kNS_UNICODE_NORMALIZER_CID },
--- a/intl/locale/OSPreferences.cpp
+++ b/intl/locale/OSPreferences.cpp
@@ -5,21 +5,28 @@
 
 /**
  * This is a shared part of the OSPreferences API implementation.
  * It defines helper methods and public methods that are calling
  * platform-specific private methods.
  */
 
 #include "OSPreferences.h"
+
 #include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Services.h"
+#include "nsIObserverService.h"
+#include "unicode/udat.h"
+#include "unicode/udatpg.h"
 
 using namespace mozilla::intl;
 
-mozilla::StaticAutoPtr<OSPreferences> OSPreferences::sInstance;
+NS_IMPL_ISUPPORTS(OSPreferences, mozIOSPreferences)
+
+mozilla::StaticRefPtr<OSPreferences> OSPreferences::sInstance;
 
 OSPreferences*
 OSPreferences::GetInstance()
 {
   if (!sInstance) {
     sInstance = new OSPreferences();
     ClearOnShutdown(&sInstance);
   }
@@ -32,16 +39,31 @@ OSPreferences::GetSystemLocales(nsTArray
   bool status = true;
   if (mSystemLocales.IsEmpty()) {
     status = ReadSystemLocales(mSystemLocales);
   }
   aRetVal = mSystemLocales;
   return status;
 }
 
+void
+OSPreferences::Refresh()
+{
+  nsTArray<nsCString> newLocales;
+  ReadSystemLocales(newLocales);
+
+  if (mSystemLocales != newLocales) {
+    mSystemLocales = Move(newLocales);
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    if (obs) {
+      obs->NotifyObservers(nullptr, "intl:system-locales-changed", nullptr);
+    }
+  }
+}
+
 /**
  * This method should be called by every method of OSPreferences that
  * retrieves a locale id from external source.
  *
  * It attempts to retrieve as much of the locale ID as possible, cutting
  * out bits that are not understood (non-strict behavior of ICU).
  *
  * It returns true if the canonicalization was successful.
@@ -58,8 +80,264 @@ OSPreferences::CanonicalizeLanguageTag(n
 
   if (U_FAILURE(status)) {
     return false;
   }
 
   aLoc.Assign(langTag, langTagLen);
   return true;
 }
+
+/**
+ * This method retrieves from ICU the best pattern for a given date/time style.
+ */
+bool
+OSPreferences::GetDateTimePatternForStyle(DateTimeFormatStyle aDateStyle,
+                                          DateTimeFormatStyle aTimeStyle,
+                                          const nsACString& aLocale,
+                                          nsAString& aRetVal)
+{
+  UDateFormatStyle timeStyle = UDAT_NONE;
+  UDateFormatStyle dateStyle = UDAT_NONE;
+
+  switch (aTimeStyle) {
+    case DateTimeFormatStyle::None:
+      timeStyle = UDAT_NONE;
+      break;
+    case DateTimeFormatStyle::Short:
+      timeStyle = UDAT_SHORT;
+      break;
+    case DateTimeFormatStyle::Medium:
+      timeStyle = UDAT_MEDIUM;
+      break;
+    case DateTimeFormatStyle::Long:
+      timeStyle = UDAT_LONG;
+      break;
+    case DateTimeFormatStyle::Full:
+      timeStyle = UDAT_FULL;
+      break;
+    case DateTimeFormatStyle::Invalid:
+      timeStyle = UDAT_NONE;
+      break;
+  }
+
+  switch (aDateStyle) {
+    case DateTimeFormatStyle::None:
+      dateStyle = UDAT_NONE;
+      break;
+    case DateTimeFormatStyle::Short:
+      dateStyle = UDAT_SHORT;
+      break;
+    case DateTimeFormatStyle::Medium:
+      dateStyle = UDAT_MEDIUM;
+      break;
+    case DateTimeFormatStyle::Long:
+      dateStyle = UDAT_LONG;
+      break;
+    case DateTimeFormatStyle::Full:
+      dateStyle = UDAT_FULL;
+      break;
+    case DateTimeFormatStyle::Invalid:
+      dateStyle = UDAT_NONE;
+      break;
+  }
+
+  const int32_t kPatternMax = 160;
+  UChar pattern[kPatternMax];
+
+  UErrorCode status = U_ZERO_ERROR;
+  UDateFormat* df = udat_open(timeStyle, dateStyle,
+                              PromiseFlatCString(aLocale).get(),
+                              nullptr, -1, nullptr, -1, &status);
+  if (U_FAILURE(status)) {
+    return false;
+  }
+
+  int32_t patsize = udat_toPattern(df, false, pattern, kPatternMax, &status);
+  udat_close(df);
+  if (U_FAILURE(status)) {
+    return false;
+  }
+  aRetVal.Assign((const char16_t*)pattern, patsize);
+  return true;
+}
+
+
+/**
+ * This method retrieves from ICU the best skeleton for a given date/time style.
+ *
+ * This is useful for cases where an OS does not provide its own patterns,
+ * but provide ability to customize the skeleton, like alter hourCycle setting.
+ *
+ * The returned value is a skeleton that matches the styles.
+ */
+bool
+OSPreferences::GetDateTimeSkeletonForStyle(DateTimeFormatStyle aDateStyle,
+                                           DateTimeFormatStyle aTimeStyle,
+                                           const nsACString& aLocale,
+                                           nsAString& aRetVal)
+{
+  nsAutoString pattern;
+  if (!GetDateTimePatternForStyle(aDateStyle, aTimeStyle, aLocale, pattern)) {
+    return false;
+  }
+
+  const int32_t kSkeletonMax = 160;
+  UChar skeleton[kSkeletonMax];
+
+  UErrorCode status = U_ZERO_ERROR;
+  int32_t skelsize = udatpg_getSkeleton(
+    nullptr, (const UChar*)pattern.BeginReading(), pattern.Length(),
+    skeleton, kSkeletonMax, &status
+  );
+  if (U_FAILURE(status)) {
+    return false;
+  }
+
+  aRetVal.Assign((const char16_t*)skeleton, skelsize);
+  return true;
+}
+
+/**
+ * This function is a counterpart to GetDateTimeSkeletonForStyle.
+ *
+ * It takes a skeleton and returns the best available pattern for a given locale
+ * that represents the provided skeleton.
+ *
+ * For example:
+ * "Hm" skeleton for "en-US" will return "H:m"
+ */
+bool
+OSPreferences::GetPatternForSkeleton(const nsAString& aSkeleton,
+                                     const nsACString& aLocale,
+                                     nsAString& aRetVal)
+{
+  UErrorCode status = U_ZERO_ERROR;
+  UDateTimePatternGenerator* pg = udatpg_open(PromiseFlatCString(aLocale).get(), &status);
+  if (U_FAILURE(status)) {
+    return false;
+  }
+
+  int32_t len =
+    udatpg_getBestPattern(pg, (const UChar*)aSkeleton.BeginReading(),
+                          aSkeleton.Length(), nullptr, 0, &status);
+  if (status == U_BUFFER_OVERFLOW_ERROR) { // expected
+    aRetVal.SetLength(len);
+    status = U_ZERO_ERROR;
+    udatpg_getBestPattern(pg, (const UChar*)aSkeleton.BeginReading(),
+                          aSkeleton.Length(), (UChar*)aRetVal.BeginWriting(),
+                          len, &status);
+  }
+
+  udatpg_close(pg);
+
+  return U_SUCCESS(status);
+}
+
+/**
+ * This function returns a pattern that should be used to join date and time
+ * patterns into a single date/time pattern string.
+ *
+ * It's useful for OSes that do not provide an API to retrieve such combined
+ * pattern.
+ *
+ * An example output is "{1}, {0}".
+ */
+bool
+OSPreferences::GetDateTimeConnectorPattern(const nsACString& aLocale,
+                                           nsAString& aRetVal)
+{
+  UErrorCode status = U_ZERO_ERROR;
+  UDateTimePatternGenerator* pg = udatpg_open(PromiseFlatCString(aLocale).get(), &status);
+  if (U_FAILURE(status)) {
+    return false;
+  }
+
+  int32_t resultSize;
+  const UChar* value = udatpg_getDateTimeFormat(pg, &resultSize);
+  MOZ_ASSERT(resultSize >= 0);
+
+  aRetVal.Assign((char16_t*)value, resultSize);
+  return true;
+}
+
+/**
+ * mozIOSPreferences methods
+ */
+NS_IMETHODIMP
+OSPreferences::GetSystemLocales(uint32_t* aCount, char*** aOutArray)
+{
+  if (mSystemLocales.IsEmpty()) {
+    ReadSystemLocales(mSystemLocales);
+  }
+
+  *aCount = mSystemLocales.Length();
+  *aOutArray = static_cast<char**>(moz_xmalloc(*aCount * sizeof(char*)));
+
+  for (uint32_t i = 0; i < *aCount; i++) {
+    (*aOutArray)[i] = moz_xstrdup(mSystemLocales[i].get());
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+OSPreferences::GetSystemLocale(nsACString& aRetVal)
+{
+  if (mSystemLocales.IsEmpty()) {
+    ReadSystemLocales(mSystemLocales);
+  }
+
+  if (!mSystemLocales.IsEmpty()) {
+    aRetVal = mSystemLocales[0];
+  }
+  return NS_OK;
+}
+
+static OSPreferences::DateTimeFormatStyle
+ToDateTimeFormatStyle(int32_t aTimeFormat)
+{
+  switch (aTimeFormat) {
+    // See mozIOSPreferences.idl for the integer values here.
+    case 0:
+      return OSPreferences::DateTimeFormatStyle::None;
+    case 1:
+      return OSPreferences::DateTimeFormatStyle::Short;
+    case 2:
+      return OSPreferences::DateTimeFormatStyle::Medium;
+    case 3:
+      return OSPreferences::DateTimeFormatStyle::Long;
+    case 4:
+      return OSPreferences::DateTimeFormatStyle::Full;
+  }
+  return OSPreferences::DateTimeFormatStyle::Invalid;
+}
+
+NS_IMETHODIMP
+OSPreferences::GetDateTimePattern(int32_t aDateFormatStyle,
+                                  int32_t aTimeFormatStyle,
+                                  const nsACString& aLocale,
+                                  nsAString& aRetVal)
+{
+  DateTimeFormatStyle dateStyle = ToDateTimeFormatStyle(aDateFormatStyle);
+  if (dateStyle == DateTimeFormatStyle::Invalid) {
+    return NS_ERROR_INVALID_ARG;
+  }
+  DateTimeFormatStyle timeStyle = ToDateTimeFormatStyle(aTimeFormatStyle);
+  if (timeStyle == DateTimeFormatStyle::Invalid) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  // If the user is asking for None on both, date and time style,
+  // let's exit early.
+  if (timeStyle == DateTimeFormatStyle::None &&
+      dateStyle == DateTimeFormatStyle::None) {
+    return NS_OK;
+  }
+
+  if (!ReadDateTimePattern(dateStyle, timeStyle, aLocale, aRetVal)) {
+    if (!GetDateTimePatternForStyle(dateStyle, timeStyle, aLocale, aRetVal)) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+
+  return NS_OK;
+}
--- a/intl/locale/OSPreferences.h
+++ b/intl/locale/OSPreferences.h
@@ -6,16 +6,18 @@
 #ifndef mozilla_intl_IntlOSPreferences_h__
 #define mozilla_intl_IntlOSPreferences_h__
 
 #include "mozilla/StaticPtr.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "unicode/uloc.h"
 
+#include "mozIOSPreferences.h"
+
 namespace mozilla {
 namespace intl {
 
 /**
  * OSPreferences API provides a set of methods for retrieving information from
  * the host environment on topics such as:
  *   - Internationalization
  *   - Localization
@@ -33,23 +35,52 @@ namespace intl {
  *
  * Second is caching. This API does cache values and where possible will
  * hook into the environment for some event-driven cache invalidation.
  *
  * This means that on platforms that do not support a mechanism to
  * notify apps about changes, new OS-level settings may not be reflected
  * in the app until it is relaunched.
  */
-class OSPreferences
+class OSPreferences: public mozIOSPreferences
 {
 
 public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_MOZIOSPREFERENCES
+
+  enum class DateTimeFormatStyle {
+    Invalid = -1,
+    None,
+    Short,   // e.g. time: HH:mm, date: Y/m/d
+    Medium,  // likely same as Short
+    Long,    // e.g. time: including seconds, date: including weekday
+    Full     // e.g. time: with timezone, date: with long weekday, month
+  };
+
+  /**
+   * Create (if necessary) and return a raw pointer to the singleton instance.
+   * Use this accessor in C++ code that just wants to call a method on the
+   * instance, but does not need to hold a reference, as in
+   *    nsAutoCString str;
+   *    OSPreferences::GetInstance()->GetSystemLocale(str);
+   */
   static OSPreferences* GetInstance();
 
   /**
+   * Return an addRef'd pointer to the singleton instance. This is used by the
+   * XPCOM constructor that exists to support usage from JS.
+   */
+  static already_AddRefed<OSPreferences> GetInstanceAddRefed()
+  {
+    return RefPtr<OSPreferences>(GetInstance()).forget();
+  }
+
+
+  /**
    * Returns a list of locales used by the host environment.
    *
    * The result is a sorted list and we expect that the OS attempts to
    * use the top locale from the list for which it has data.
    *
    * Each element of the list is a valid locale ID that can be passed to ICU
    * and ECMA402 Intl APIs,
    * At the same time each element is a valid BCP47 language tag that can be
@@ -58,35 +89,91 @@ public:
    * Example: ["en-US", "de", "pl", "sr-Cyrl", "zh-Hans-HK"]
    *
    * The return bool value indicates whether the function successfully
    * resolved at least one locale.
    *
    * Usage:
    *   nsTArray<nsCString> systemLocales;
    *   OSPreferences::GetInstance()->GetSystemLocales(systemLocales);
+   *
+   * (See mozIOSPreferences.idl for a JS-callable version of this.)
    */
   bool GetSystemLocales(nsTArray<nsCString>& aRetVal);
 
 protected:
   nsTArray<nsCString> mSystemLocales;
 
 private:
-  static StaticAutoPtr<OSPreferences> sInstance;
+  virtual ~OSPreferences() {};
+
+  static StaticRefPtr<OSPreferences> sInstance;
 
   static bool CanonicalizeLanguageTag(nsCString& aLoc);
 
   /**
+   * Helper methods to get formats from ICU; these will return false
+   * in case of error, in which case the caller cannot rely on aRetVal.
+   */
+  bool GetDateTimePatternForStyle(DateTimeFormatStyle aDateStyle,
+                                  DateTimeFormatStyle aTimeStyle,
+                                  const nsACString& aLocale,
+                                  nsAString& aRetVal);
+
+  bool GetDateTimeSkeletonForStyle(DateTimeFormatStyle aDateStyle,
+                                   DateTimeFormatStyle aTimeStyle,
+                                   const nsACString& aLocale,
+                                   nsAString& aRetVal);
+
+  bool GetPatternForSkeleton(const nsAString& aSkeleton,
+                             const nsACString& aLocale,
+                             nsAString& aRetVal);
+
+  bool GetDateTimeConnectorPattern(const nsACString& aLocale,
+                                   nsAString& aRetVal);
+
+  /**
    * This is a host environment specific method that will be implemented
    * separately for each platform.
    *
    * It is only called when the cache is empty or invalidated.
    *
    * The return value indicates whether the function successfully
    * resolved at least one locale.
    */
   bool ReadSystemLocales(nsTArray<nsCString>& aRetVal);
+
+  /**
+   * This is a host environment specific method that will be implemented
+   * separately for each platform.
+   *
+   * It is `best-effort` kind of API that attempts to construct the best
+   * possible date/time pattern for the given styles and locales.
+   *
+   * In case we fail to, or don't know how to retrieve the pattern in a
+   * given environment this function will return false.
+   * Callers should always be prepared to handle that scenario.
+   *
+   * The heuristic may depend on the OS API and HIG guidelines.
+   */
+  bool ReadDateTimePattern(DateTimeFormatStyle aDateFormatStyle,
+                           DateTimeFormatStyle aTimeFormatStyle,
+                           const nsACString& aLocale,
+                           nsAString& aRetVal);
+
+  /**
+   * Triggers a refresh of retrieving data from host environment.
+   *
+   * If the result differs from the previous list, it will additionally
+   * trigger global events for changed values:
+   *
+   *  * SystemLocales: "intl:system-locales-changed"
+   *
+   * This method should not be called from anywhere except of per-platform
+   * hooks into OS events.
+   */
+  void Refresh();
 };
 
 } // intl
 } // namespace mozilla
 
 #endif /* mozilla_intl_IntlOSPreferences_h__ */
new file mode 100644
--- /dev/null
+++ b/intl/locale/gtk/OSPreferences_gtk.cpp
@@ -0,0 +1,174 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "OSPreferences.h"
+#include "dlfcn.h"
+#include "glib.h"
+#include "gio/gio.h"
+
+using namespace mozilla::intl;
+
+bool
+OSPreferences::ReadSystemLocales(nsTArray<nsCString>& aLocaleList)
+{
+  MOZ_ASSERT(aLocaleList.IsEmpty());
+
+  nsAutoCString defaultLang(uloc_getDefault());
+
+  if (CanonicalizeLanguageTag(defaultLang)) {
+    aLocaleList.AppendElement(defaultLang);
+    return true;
+  }
+  return false;
+}
+
+/*
+ * This looks up into gtk settings for hourCycle format.
+ *
+ * This works for all GUIs that use gtk settings like Gnome, Elementary etc.
+ * Ubuntu does not use those settings so we'll want to support them separately.
+ *
+ * We're taking the current 12/24h settings irrelevant of the locale, because
+ * in the UI user selects this setting for all locales.
+ */
+typedef GVariant* (*get_value_fn_t)(GSettings*, const gchar*);
+
+static get_value_fn_t
+FindGetValueFunction()
+{
+  get_value_fn_t fn = reinterpret_cast<get_value_fn_t>(
+    dlsym(RTLD_DEFAULT, "g_settings_get_user_value")
+  );
+  return fn ? fn : &g_settings_get_value;
+}
+
+static int
+HourCycle()
+{
+  int rval = 0;
+
+  const char* schema;
+  const char* key;
+  const char* env = getenv("XDG_CURRENT_DESKTOP");
+  if (env && strcmp(env, "Unity") == 0) {
+    schema = "com.canonical.indicator.datetime";
+    key = "time-format";
+  } else {
+    schema = "org.gnome.desktop.interface";
+    key = "clock-format";
+  }
+
+  GSettings* settings = g_settings_new(schema);
+  if (settings) {
+    // We really want to use g_settings_get_user_value which will
+    // only want to take it if user manually changed the value.
+    // But this requires glib 2.40, and we still support older glib versions,
+    // so we have to check whether it's available and fall back to the older
+    // g_settings_get_value if not.
+    static get_value_fn_t sGetValueFunction = FindGetValueFunction();
+    GVariant* value = sGetValueFunction(settings, key);
+    if (value) {
+      if (g_variant_is_of_type(value, G_VARIANT_TYPE_STRING)) {
+        const char* strVal = g_variant_get_string(value, nullptr);
+        if (strncmp("12", strVal, 2) == 0) {
+          rval = 12;
+        } else if (strncmp("24", strVal, 2) == 0) {
+          rval = 24;
+        }
+      }
+      g_variant_unref(value);
+    }
+    g_object_unref(settings);
+  }
+  return rval;
+}
+
+/**
+ * Since Gtk does not provide a way to customize or format date/time patterns,
+ * we're reusing ICU data here, but we do modify it according to the only
+ * setting Gtk gives us - hourCycle.
+ *
+ * This means that for gtk we will return a pattern from ICU altered to
+ * represent h12/h24 hour cycle if the user modified the default value.
+ *
+ * In short, this should work like this:
+ *
+ *  * gtk defaults, pl: 24h
+ *  * gtk defaults, en: 12h
+ *
+ *  * gtk 12h, pl: 12h
+ *  * gtk 12h, en: 12h
+ *
+ *  * gtk 24h, pl: 24h
+ *  * gtk 12h, en: 12h
+ */
+bool
+OSPreferences::ReadDateTimePattern(DateTimeFormatStyle aDateStyle,
+                                   DateTimeFormatStyle aTimeStyle,
+                                   const nsACString& aLocale, nsAString& aRetVal)
+{
+  nsAutoString skeleton;
+  if (!GetDateTimeSkeletonForStyle(aDateStyle, aTimeStyle, aLocale, skeleton)) {
+    return false;
+  }
+
+  // Customize the skeleton if necessary to reflect user's 12/24hr pref
+  switch (HourCycle()) {
+    case 12: {
+      // If skeleton contains 'H' or 'k', replace with 'h' or 'K' respectively,
+      // and add 'a' unless already present.
+      if (skeleton.FindChar('H') == -1 && skeleton.FindChar('k') == -1) {
+        break; // nothing to do
+      }
+      bool foundA = false;
+      for (size_t i = 0; i < skeleton.Length(); ++i) {
+        switch (skeleton[i]) {
+          case 'a':
+            foundA = true;
+            break;
+          case 'H':
+            skeleton.SetCharAt('h', i);
+            break;
+          case 'k':
+            skeleton.SetCharAt('K', i);
+            break;
+        }
+      }
+      if (!foundA) {
+        skeleton.Append(char16_t('a'));
+      }
+      break;
+    }
+    case 24:
+      // If skeleton contains 'h' or 'K', replace with 'H' or 'k' respectively,
+      // and delete 'a' if present.
+      if (skeleton.FindChar('h') == -1 && skeleton.FindChar('K') == -1) {
+        break; // nothing to do
+      }
+      for (int32_t i = 0; i < int32_t(skeleton.Length()); ++i) {
+        switch (skeleton[i]) {
+          case 'a':
+            skeleton.Cut(i, 1);
+            --i;
+            break;
+          case 'h':
+            skeleton.SetCharAt('H', i);
+            break;
+          case 'K':
+            skeleton.SetCharAt('k', i);
+            break;
+        }
+      }
+      break;
+  }
+
+  if (!GetPatternForSkeleton(skeleton, aLocale, aRetVal)) {
+    return false;
+  }
+
+  return true;
+}
+
new file mode 100644
--- /dev/null
+++ b/intl/locale/gtk/moz.build
@@ -0,0 +1,15 @@
+# -*- 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/.
+
+SOURCES += ['OSPreferences_gtk.cpp']
+
+CXXFLAGS += CONFIG['GLIB_CFLAGS']
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+    '..',
+]
--- a/intl/locale/mac/OSPreferences_mac.cpp
+++ b/intl/locale/mac/OSPreferences_mac.cpp
@@ -33,8 +33,86 @@ OSPreferences::ReadSystemLocales(nsTArra
 
   if (CanonicalizeLanguageTag(locale)) {
     aLocaleList.AppendElement(locale);
     return true;
   }
 
   return false;
 }
+
+static CFDateFormatterStyle
+ToCFDateFormatterStyle(OSPreferences::DateTimeFormatStyle aFormatStyle)
+{
+  switch (aFormatStyle) {
+    case OSPreferences::DateTimeFormatStyle::None:
+      return kCFDateFormatterNoStyle;
+    case OSPreferences::DateTimeFormatStyle::Short:
+      return kCFDateFormatterShortStyle;
+    case OSPreferences::DateTimeFormatStyle::Medium:
+      return kCFDateFormatterMediumStyle;
+    case OSPreferences::DateTimeFormatStyle::Long:
+      return kCFDateFormatterLongStyle;
+    case OSPreferences::DateTimeFormatStyle::Full:
+      return kCFDateFormatterFullStyle;
+    case OSPreferences::DateTimeFormatStyle::Invalid:
+      MOZ_ASSERT_UNREACHABLE("invalid time format");
+      return kCFDateFormatterNoStyle;
+  }
+}
+
+// Given an 8-bit Gecko string, create a corresponding CFLocale;
+// if aLocale is empty, returns a copy of the system's current locale.
+// May return null on failure.
+// Follows Core Foundation's Create rule, so the caller is responsible to
+// release the returned reference.
+static CFLocaleRef
+CreateCFLocaleFor(const nsACString& aLocale)
+{
+  if (aLocale.IsEmpty()) {
+    return CFLocaleCopyCurrent();
+  }
+  CFStringRef identifier =
+    CFStringCreateWithBytesNoCopy(kCFAllocatorDefault,
+                                  (const uint8_t*)aLocale.BeginReading(),
+                                  aLocale.Length(), kCFStringEncodingASCII,
+                                  false, kCFAllocatorNull);
+  if (!identifier) {
+    return nullptr;
+  }
+  CFLocaleRef locale = CFLocaleCreate(kCFAllocatorDefault, identifier);
+  CFRelease(identifier);
+  return locale;
+}
+
+/**
+ * Cocoa API maps nicely to our four styles of date/time.
+ *
+ * The only caveat is that Cocoa takes regional preferences modifications
+ * into account only when we pass an empty string as a locale.
+ *
+ * In all other cases it will return the default pattern for a given locale.
+ */
+bool
+OSPreferences::ReadDateTimePattern(DateTimeFormatStyle aDateStyle,
+                                   DateTimeFormatStyle aTimeStyle,
+                                   const nsACString& aLocale, nsAString& aRetVal)
+{
+  CFLocaleRef locale = CreateCFLocaleFor(aLocale);
+  if (!locale) {
+    return false;
+  }
+
+  CFDateFormatterRef formatter =
+    CFDateFormatterCreate(kCFAllocatorDefault, locale,
+                          ToCFDateFormatterStyle(aDateStyle),
+                          ToCFDateFormatterStyle(aTimeStyle));
+  CFStringRef format = CFDateFormatterGetFormat(formatter);
+  CFRelease(locale);
+
+  CFRange range = CFRangeMake(0, CFStringGetLength(format));
+  aRetVal.SetLength(range.length);
+  CFStringGetCharacters(format, range,
+                        reinterpret_cast<UniChar*>(aRetVal.BeginWriting()));
+  CFRelease(formatter);
+
+  return true;
+}
--- a/intl/locale/moz.build
+++ b/intl/locale/moz.build
@@ -15,19 +15,22 @@ if CONFIG['ENABLE_INTL_API']:
 toolkit = CONFIG['MOZ_WIDGET_TOOLKIT']
 
 if toolkit == 'windows':
     DIRS += ['windows']
 elif toolkit == 'cocoa':
     DIRS += ['mac']
 else:
     DIRS += ['unix']
+    if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+        DIRS += ['gtk']
 
 XPIDL_SOURCES += [
     'mozILocaleService.idl',
+    'mozIOSPreferences.idl',
     'nsICollation.idl',
     'nsILocale.idl',
     'nsILocaleService.idl',
     'nsIScriptableDateFormat.idl',
 ]
 
 XPIDL_MODULE = 'locale'
 
new file mode 100644
--- /dev/null
+++ b/intl/locale/mozIOSPreferences.idl
@@ -0,0 +1,86 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{C++
+// Define Contractid and CID
+#define MOZ_OSPREFERENCES_CID \
+  { 0x65944815, 0xe9ae, 0x48bd, { 0xa2, 0xbf, 0xf1, 0x10, 0x87, 0x20, 0x95, 0x0c } }
+
+#define MOZ_OSPREFERENCES_CONTRACTID "@mozilla.org/intl/ospreferences;1"
+%}
+
+[scriptable, uuid(65944815-e9ae-48bd-a2bf-f1108720950c)]
+interface mozIOSPreferences : nsISupports
+{
+  const long dateTimeFormatStyleNone     = 0;
+  const long dateTimeFormatStyleShort    = 1;
+  const long dateTimeFormatStyleMedium   = 2;
+  const long dateTimeFormatStyleLong     = 3;
+  const long dateTimeFormatStyleFull     = 4;
+
+  /**
+   * Returns a list of locales used by the host environment.
+   *
+   * The result is a sorted list and we expect that the OS attempts to
+   * use the top locale from the list for which it has data.
+   *
+   * Each element of the list is a valid locale ID that can be passed to ICU
+   * and ECMA402 Intl APIs,
+   * At the same time each element is a valid BCP47 language tag that can be
+   * used for language negotiation.
+   *
+   * Example: ["en-US", "de", "pl", "sr-Cyrl", "zh-Hans-HK"]
+   *
+   * The API may return an empty list in case no locale could
+   * be retrieved from the system.
+   *
+   * (See OSPreferences.h for a more C++-friendly version of this.)
+   */
+  void getSystemLocales([optional] out unsigned long aCount,
+                        [retval, array, size_is(aCount)] out string aOutArray);
+
+  /**
+   * Returns the best locale that the host environment is localized to.
+   *
+   * The result is a valid locale ID and it should be
+   * used for all APIs that do not handle language negotiation.
+   *
+   * In any scenario involving language negotiation, GetSystemLocales should
+   * be preferred over the single value.
+   *
+   * Example: "zh-Hans-HK"
+   */
+  readonly attribute ACString systemLocale;
+
+  /**
+   * Returns the best possible date/time pattern for the host environment
+   * taking into account date/time regional settings user defined in the OS
+   * preferences.
+   *
+   * Notice, that depending on the OS it may take into account those settings
+   * for all locales, or only if the locale matches the OS locale.
+   *
+   * It takes two integer arguments that must be valid `dateTimeFormatStyle*`
+   * values (see constants defined above), and a string representing a
+   * BCP47 locale.
+   *
+   * It returns a string with a LDML date/time pattern.
+   *
+   * If no pattern can be retrieved from the host environment, it will
+   * lookup the best available pattern from ICU.
+   *
+   * Notice, this is a pretty unique method in this API in that it does
+   * more than look up into host environment.
+   * The reason for that is that constructing the right date/time pattern
+   * requires a lot of OS-specific logic and it ends up being easier to just
+   * handle all scenarios, including with cases where we fail to retrieve
+   * anything from the OS, here.
+   */
+  AString getDateTimePattern(in long timeFormatStyle,
+                             in long dateFormatStyle,
+                             [optional] in ACString locale);
+};
--- a/intl/locale/nsLocaleConstructors.h
+++ b/intl/locale/nsLocaleConstructors.h
@@ -9,16 +9,17 @@
 #include "nsCollationCID.h"
 #include "mozilla/ModuleUtils.h"
 #include "nsILocaleService.h"
 #include "nsIScriptableDateFormat.h"
 #include "nsIServiceManager.h"
 #include "nsLanguageAtomService.h"
 #include "nsPlatformCharset.h"
 #include "LocaleService.h"
+#include "OSPreferences.h"
 
 #if defined(XP_MACOSX)
 #define USE_MAC_LOCALE
 #endif
 
 #if defined(XP_UNIX) && !defined(XP_MACOSX)
 #define USE_UNIX_LOCALE
 #endif
@@ -57,16 +58,18 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsCollati
 //NS_GENERIC_FACTORY_CONSTRUCTOR(nsScriptableDateTimeFormat)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsLanguageAtomService)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPlatformCharset, Init)
 
 namespace mozilla {
 namespace intl {
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(LocaleService,
                                          LocaleService::GetInstanceAddRefed)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(OSPreferences,
+                                         OSPreferences::GetInstanceAddRefed)
 }
 }
 
 #ifdef XP_WIN
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsCollationWin)
 #endif
 
 #ifdef USE_UNIX_LOCALE
--- a/intl/locale/tests/gtest/TestOSPreferences.cpp
+++ b/intl/locale/tests/gtest/TestOSPreferences.cpp
@@ -1,14 +1,15 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gtest/gtest.h"
+#include "mozilla/ArrayUtils.h"
 #include "mozilla/intl/OSPreferences.h"
 
 using namespace mozilla::intl;
 
 /**
  * We test that on all platforms we test against (irrelevant of the tier),
  * we will be able to retrieve at least a single locale out of the system.
  *
@@ -17,8 +18,56 @@ using namespace mozilla::intl;
  * it not happen without us noticing.
  */
 TEST(Intl_Locale_OSPreferences, GetSystemLocales) {
   nsTArray<nsCString> systemLocales;
   ASSERT_TRUE(OSPreferences::GetInstance()->GetSystemLocales(systemLocales));
 
   ASSERT_FALSE(systemLocales.IsEmpty());
 }
+
+/**
+ * We test that on all platforms we test against,
+ * we will be able to retrieve a date and time pattern.
+ *
+ * This may come back empty on platforms where we don't have platforms
+ * bindings for, so effectively, we're testing for crashes. We should
+ * never crash.
+ */
+TEST(Intl_Locale_OSPreferences, GetDateTimePattern) {
+  nsAutoString pattern;
+  OSPreferences* osprefs = OSPreferences::GetInstance();
+
+  struct Test {
+    int dateStyle;
+    int timeStyle;
+    const char* locale;
+  };
+  Test tests[] = {
+    { 0, 0, "" },
+    { 1, 0, "pl" },
+    { 2, 0, "de-DE" },
+    { 3, 0, "fr" },
+    { 4, 0, "ar" },
+
+    { 0, 1, "" },
+    { 0, 2, "it" },
+    { 0, 3, "" },
+    { 0, 4, "ru" },
+
+    { 4, 1, "" },
+    { 3, 2, "cs" },
+    { 2, 3, "" },
+    { 1, 4, "ja" }
+  };
+
+  for (unsigned i = 0; i < mozilla::ArrayLength(tests); i++) {
+    const Test& t = tests[i];
+    nsAutoString pattern;
+    if (NS_SUCCEEDED(osprefs->GetDateTimePattern(t.dateStyle, t.timeStyle,
+                                                 nsDependentCString(t.locale),
+                                                 pattern))) {
+      ASSERT_TRUE((t.dateStyle == 0 && t.timeStyle == 0) || !pattern.IsEmpty());
+    }
+  }
+
+  ASSERT_TRUE(1);
+}
new file mode 100644
--- /dev/null
+++ b/intl/locale/tests/unit/test_osPreferences.js
@@ -0,0 +1,38 @@
+/* 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 run_test()
+{
+  const osprefs =
+    Components.classes["@mozilla.org/intl/ospreferences;1"]
+    .getService(Components.interfaces.mozIOSPreferences);
+
+  const systemLocale = osprefs.systemLocale;
+  do_check_true(systemLocale != "", "systemLocale is non-empty");
+
+  const systemLocales = osprefs.getSystemLocales();
+  do_check_true(Array.isArray(systemLocales), "systemLocales returns an array");
+
+  do_check_true(systemLocale == systemLocales[0],
+    "systemLocale matches first entry in systemLocales");
+
+  const getDateTimePatternTests = [
+    [osprefs.dateTimeFormatStyleNone, osprefs.dateTimeFormatStyleNone, ""],
+    [osprefs.dateTimeFormatStyleShort, osprefs.dateTimeFormatStyleNone, ""],
+    [osprefs.dateTimeFormatStyleNone, osprefs.dateTimeFormatStyleLong, "ar"],
+    [osprefs.dateTimeFormatStyleFull, osprefs.dateTimeFormatStyleMedium, "ru"],
+  ];
+
+  for (let i = 0; i < getDateTimePatternTests.length; i++) {
+    const test = getDateTimePatternTests[i];
+
+    const pattern = osprefs.getDateTimePattern(...test);
+    if (test[0] !== osprefs.dateTimeFormatStyleNone &&
+        test[1] !== osprefs.dateTImeFormatStyleNone) {
+      do_check_true(pattern.length > 0, "pattern is not empty.");
+    }
+  }
+
+  do_check_true(1, "osprefs didn't crash");
+}
--- a/intl/locale/tests/unit/xpcshell.ini
+++ b/intl/locale/tests/unit/xpcshell.ini
@@ -18,8 +18,9 @@ skip-if = toolkit != "cocoa"
 [test_intl_on_workers.js]
 skip-if = toolkit == "android" # bug 1309447
 
 [test_pluralForm.js]
 [test_pluralForm_english.js]
 [test_pluralForm_makeGetter.js]
 
 [test_localeService.js]
+[test_osPreferences.js]
--- a/intl/locale/unix/OSPreferences_unix.cpp
+++ b/intl/locale/unix/OSPreferences_unix.cpp
@@ -16,8 +16,16 @@ OSPreferences::ReadSystemLocales(nsTArra
   nsAutoCString defaultLang(uloc_getDefault());
 
   if (CanonicalizeLanguageTag(defaultLang)) {
     aLocaleList.AppendElement(defaultLang);
     return true;
   }
   return false;
 }
+
+bool
+OSPreferences::ReadDateTimePattern(DateTimeFormatStyle aDateStyle,
+                                   DateTimeFormatStyle aTimeStyle,
+                                   const nsACString& aLocale, nsAString& aRetVal)
+{
+  return false;
+}
--- a/intl/locale/unix/moz.build
+++ b/intl/locale/unix/moz.build
@@ -4,17 +4,17 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 SOURCES += [
     'nsCollationUnix.cpp',
     'nsPosixLocale.cpp',
 ]
 
-if CONFIG['ENABLE_INTL_API']:
+if 'gtk' not in CONFIG['MOZ_WIDGET_TOOLKIT']:
     SOURCES += ['OSPreferences_unix.cpp']
 
 if CONFIG['OS_TARGET'] == 'Android':
     SOURCES += [
         'nsAndroidCharset.cpp',
     ]
 else:
     SOURCES += [
--- a/intl/locale/windows/OSPreferences_win.cpp
+++ b/intl/locale/windows/OSPreferences_win.cpp
@@ -22,8 +22,187 @@ OSPreferences::ReadSystemLocales(nsTArra
   NS_LossyConvertUTF16toASCII loc(locale);
 
   if (CanonicalizeLanguageTag(loc)) {
     aLocaleList.AppendElement(loc);
     return true;
   }
   return false;
 }
+
+static LCTYPE
+ToDateLCType(OSPreferences::DateTimeFormatStyle aFormatStyle)
+{
+  switch (aFormatStyle) {
+    case OSPreferences::DateTimeFormatStyle::None:
+      return LOCALE_SLONGDATE;
+    case OSPreferences::DateTimeFormatStyle::Short:
+      return LOCALE_SSHORTDATE;
+    case OSPreferences::DateTimeFormatStyle::Medium:
+      return LOCALE_SSHORTDATE;
+    case OSPreferences::DateTimeFormatStyle::Long:
+      return LOCALE_SLONGDATE;
+    case OSPreferences::DateTimeFormatStyle::Full:
+      return LOCALE_SLONGDATE;
+    case OSPreferences::DateTimeFormatStyle::Invalid:
+    default:
+      MOZ_ASSERT_UNREACHABLE("invalid date format");
+      return LOCALE_SLONGDATE;
+  }
+}
+
+static LCTYPE
+ToTimeLCType(OSPreferences::DateTimeFormatStyle aFormatStyle)
+{
+  switch (aFormatStyle) {
+    case OSPreferences::DateTimeFormatStyle::None:
+      return LOCALE_STIMEFORMAT;
+    case OSPreferences::DateTimeFormatStyle::Short:
+      return LOCALE_SSHORTTIME;
+    case OSPreferences::DateTimeFormatStyle::Medium:
+      return LOCALE_SSHORTTIME;
+    case OSPreferences::DateTimeFormatStyle::Long:
+      return LOCALE_STIMEFORMAT;
+    case OSPreferences::DateTimeFormatStyle::Full:
+      return LOCALE_STIMEFORMAT;
+    case OSPreferences::DateTimeFormatStyle::Invalid:
+    default:
+      MOZ_ASSERT_UNREACHABLE("invalid time format");
+      return LOCALE_STIMEFORMAT;
+  }
+}
+
+/**
+ * Windows API includes regional preferences from the user only
+ * if we pass empty locale string or if the locale string matches
+ * the current locale.
+ *
+ * Since Windows API only allows us to retrieve two options - short/long
+ * we map it to our four options as:
+ *
+ *   short  -> short
+ *   medium -> short
+ *   long   -> long
+ *   full   -> long
+ *
+ * In order to produce a single date/time format, we use CLDR pattern
+ * for combined date/time string, since Windows API does not provide an
+ * option for this.
+ */
+bool
+OSPreferences::ReadDateTimePattern(DateTimeFormatStyle aDateStyle,
+                                   DateTimeFormatStyle aTimeStyle,
+                                   const nsACString& aLocale, nsAString& aRetVal)
+{
+  LPWSTR localeName = LOCALE_NAME_USER_DEFAULT;
+  nsAutoString localeNameBuffer;
+  if (!aLocale.IsEmpty()) {
+    localeNameBuffer.AppendASCII(aLocale.BeginReading(), aLocale.Length());
+    localeName = (LPWSTR)localeNameBuffer.BeginReading();
+  }
+
+  bool isDate = aDateStyle != DateTimeFormatStyle::None &&
+                aDateStyle != DateTimeFormatStyle::Invalid;
+  bool isTime = aTimeStyle != DateTimeFormatStyle::None &&
+                aTimeStyle != DateTimeFormatStyle::Invalid;
+
+  // If both date and time are wanted, we'll initially read them into a
+  // local string, and then insert them into the overall date+time pattern;
+  // but if only one is needed we'll work directly with the return value.
+  // Set 'str' to point to the string we will use to retrieve patterns
+  // from Windows.
+  nsAutoString tmpStr;
+  nsAString* str;
+  if (isDate && isTime) {
+    if (!GetDateTimeConnectorPattern(aLocale, aRetVal)) {
+      NS_WARNING("failed to get date/time connector");
+      aRetVal.AssignLiteral(u"{1} {0}");
+    }
+    str = &tmpStr;
+  } else if (isDate || isTime) {
+    str = &aRetVal;
+  } else {
+    aRetVal.Truncate(0);
+    return true;
+  }
+
+  if (isDate) {
+    LCTYPE lcType = ToDateLCType(aDateStyle);
+    size_t len = GetLocaleInfoEx(localeName, lcType, nullptr, 0);
+    if (len == 0) {
+      return false;
+    }
+    str->SetLength(len - 1); // -1 because len counts the null terminator
+    GetLocaleInfoEx(localeName, lcType, (WCHAR*)str->BeginWriting(), len);
+
+    // Windows uses "ddd" and "dddd" for abbreviated and full day names respectively,
+    //   https://msdn.microsoft.com/en-us/library/windows/desktop/dd317787(v=vs.85).aspx
+    // but in a CLDR/ICU-style pattern these should be "EEE" and "EEEE".
+    //   http://userguide.icu-project.org/formatparse/datetime
+    // So we fix that up here.
+    nsAString::const_iterator start, pos, end;
+    start = str->BeginReading(pos);
+    str->EndReading(end);
+    if (FindInReadable(NS_LITERAL_STRING("dddd"), pos, end)) {
+      str->Replace(pos - start, 4, NS_LITERAL_STRING("EEEE"));
+    } else if (FindInReadable(NS_LITERAL_STRING("ddd"), pos, end)) {
+      str->Replace(pos - start, 3, NS_LITERAL_STRING("EEE"));
+    }
+
+    // Also, Windows uses lowercase "g" or "gg" for era, but ICU wants uppercase "G"
+    // (it would interpret "g" as "modified Julian day"!). So fix that.
+    int32_t index = str->FindChar('g');
+    if (index >= 0) {
+      str->Replace(index, 1, 'G');
+      // If it was a double "gg", just drop the second one.
+      index++;
+      if (str->CharAt(index) == 'g') {
+        str->Cut(index, 1);
+      }
+    }
+
+    // If time was also requested, we need to substitute the date pattern from Windows
+    // into the date+time format that we have in aRetVal.
+    if (isTime) {
+      nsAString::const_iterator start, pos, end;
+      start = aRetVal.BeginReading(pos);
+      aRetVal.EndReading(end);
+      if (FindInReadable(NS_LITERAL_STRING("{1}"), pos, end)) {
+        aRetVal.Replace(pos - start, 3, tmpStr);
+      }
+    }
+  }
+
+  if (isTime) {
+    LCTYPE lcType = ToTimeLCType(aTimeStyle);
+    size_t len = GetLocaleInfoEx(localeName, lcType, nullptr, 0);
+    if (len == 0) {
+      return false;
+    }
+    str->SetLength(len - 1);
+    GetLocaleInfoEx(localeName, lcType, (WCHAR*)str->BeginWriting(), len);
+
+    // Windows uses "t" or "tt" for a "time marker" (am/pm indicator),
+    //   https://msdn.microsoft.com/en-us/library/windows/desktop/dd318148(v=vs.85).aspx
+    // but in a CLDR/ICU-style pattern that should be "a".
+    //   http://userguide.icu-project.org/formatparse/datetime
+    // So we fix that up here.
+    int32_t index = str->FindChar('t');
+    if (index >= 0) {
+      str->Replace(index, 1, 'a');
+      index++;
+      if (str->CharAt(index) == 't') {
+        str->Cut(index, 1);
+      }
+    }
+
+    if (isDate) {
+      nsAString::const_iterator start, pos, end;
+      start = aRetVal.BeginReading(pos);
+      aRetVal.EndReading(end);
+      if (FindInReadable(NS_LITERAL_STRING("{0}"), pos, end)) {
+        aRetVal.Replace(pos - start, 3, tmpStr);
+      }
+    }
+  }
+
+  return true;
+}
--- a/ipc/mscom/Aggregation.h
+++ b/ipc/mscom/Aggregation.h
@@ -4,16 +4,19 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_mscom_Aggregation_h
 #define mozilla_mscom_Aggregation_h
 
 #include "mozilla/Attributes.h"
 
+#include <stddef.h>
+#include <unknwn.h>
+
 namespace mozilla {
 namespace mscom {
 
 /**
  * This is used for stabilizing a COM object's reference count during
  * construction when that object aggregates other objects. Since the aggregated
  * object(s) may AddRef() or Release(), we need to artifically boost the
  * refcount to prevent premature destruction. Note that we increment/decrement
@@ -39,13 +42,58 @@ public:
   StabilizedRefCount(StabilizedRefCount&&) = delete;
   StabilizedRefCount& operator=(const StabilizedRefCount&) = delete;
   StabilizedRefCount& operator=(StabilizedRefCount&&) = delete;
 
 private:
   RefCntT& mRefCnt;
 };
 
+namespace detail {
+
+template <typename T>
+class InternalUnknown : public IUnknown
+{
+public:
+  STDMETHODIMP QueryInterface(REFIID aIid, void** aOutInterface) override
+  {
+    return This()->InternalQueryInterface(aIid, aOutInterface);
+  }
+
+  STDMETHODIMP_(ULONG) AddRef() override
+  {
+    return This()->InternalAddRef();
+  }
+
+  STDMETHODIMP_(ULONG) Release() override
+  {
+    return This()->InternalRelease();
+  }
+
+private:
+  T* This()
+  {
+    return reinterpret_cast<T*>(reinterpret_cast<char*>(this) -
+                                offsetof(T, mInternalUnknown));
+  }
+};
+
+} // namespace detail
 } // namespace mscom
 } // namespace mozilla
 
+#define DECLARE_AGGREGATABLE(Type) \
+  public: \
+    STDMETHODIMP QueryInterface(REFIID riid, void** ppv) override \
+    { return mOuter->QueryInterface(riid, ppv); } \
+    STDMETHODIMP_(ULONG) AddRef() override \
+    { return mOuter->AddRef(); } \
+    STDMETHODIMP_(ULONG) Release() override \
+    { return mOuter->Release(); } \
+  protected: \
+    STDMETHODIMP InternalQueryInterface(REFIID riid, void** ppv); \
+    STDMETHODIMP_(ULONG) InternalAddRef(); \
+    STDMETHODIMP_(ULONG) InternalRelease(); \
+    friend class mozilla::mscom::detail::InternalUnknown<Type>; \
+    mozilla::mscom::detail::InternalUnknown<Type> mInternalUnknown
+
 #endif // mozilla_mscom_Aggregation_h
 
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/IHandlerPayload.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_mscom_IHandlerPayload_h
+#define mozilla_mscom_IHandlerPayload_h
+
+#include <objidl.h>
+
+namespace mozilla {
+namespace mscom {
+
+struct HandlerPayload
+{
+  virtual STDMETHODIMP GetHandler(CLSID* aHandlerClsid) = 0;
+  virtual STDMETHODIMP GetHandlerPayloadSize(REFIID aIid,
+                                             IUnknown* aTarget,
+                                             DWORD* aOutPayloadSize) = 0;
+  virtual STDMETHODIMP WriteHandlerPayload(IStream* aStream, REFIID aIid,
+                                           IUnknown* aTarget) = 0;
+  virtual REFIID MarshalAs(REFIID aIid) = 0;
+};
+
+struct IHandlerPayload : public IUnknown
+                       , public HandlerPayload
+{
+  virtual STDMETHODIMP Clone(IHandlerPayload** aOutNewPayload) = 0;
+};
+
+} // namespace mscom
+} // namespace mozilla
+
+#endif // mozilla_mscom_IHandlerPayload_h
--- a/ipc/mscom/Interceptor.cpp
+++ b/ipc/mscom/Interceptor.cpp
@@ -41,16 +41,17 @@ Interceptor::Create(STAUniquePtr<IUnknow
   return intcpt->QueryInterface(aIid, aOutput);
 }
 
 Interceptor::Interceptor(STAUniquePtr<IUnknown> aTarget, IInterceptorSink* aSink)
   : WeakReferenceSupport(WeakReferenceSupport::Flags::eDestroyOnMainThread)
   , mTarget(Move(aTarget))
   , mEventSink(aSink)
   , mMutex("mozilla::mscom::Interceptor::mMutex")
+  , mStdMarshal(nullptr)
 {
   MOZ_ASSERT(aSink);
   MOZ_ASSERT(!IsProxy(mTarget.get()));
   RefPtr<IWeakReference> weakRef;
   if (SUCCEEDED(GetWeakReference(getter_AddRefs(weakRef)))) {
     aSink->SetInterceptor(weakRef);
   }
 }
@@ -62,16 +63,87 @@ Interceptor::~Interceptor()
   MOZ_ASSERT(NS_IsMainThread());
   for (uint32_t index = 0, len = mInterceptorMap.Length(); index < len; ++index) {
     MapEntry& entry = mInterceptorMap[index];
     entry.mInterceptor = nullptr;
     entry.mTargetInterface->Release();
   }
 }
 
+HRESULT
+Interceptor::GetClassForHandler(DWORD aDestContext, void* aDestContextPtr,
+                                CLSID* aHandlerClsid)
+{
+  if (aDestContextPtr || !aHandlerClsid ||
+      aDestContext == MSHCTX_DIFFERENTMACHINE) {
+    return E_INVALIDARG;
+  }
+  MOZ_ASSERT(mEventSink);
+  return mEventSink->GetHandler(aHandlerClsid);
+}
+
+HRESULT
+Interceptor::GetUnmarshalClass(REFIID riid, void* pv, DWORD dwDestContext,
+                               void* pvDestContext, DWORD mshlflags,
+                               CLSID* pCid)
+{
+  return mStdMarshal->GetUnmarshalClass(riid, pv, dwDestContext, pvDestContext,
+                                        mshlflags, pCid);
+}
+
+HRESULT
+Interceptor::GetMarshalSizeMax(REFIID riid, void* pv, DWORD dwDestContext,
+                               void* pvDestContext, DWORD mshlflags,
+                               DWORD* pSize)
+{
+  HRESULT hr = mStdMarshal->GetMarshalSizeMax(riid, pv, dwDestContext,
+                                              pvDestContext, mshlflags, pSize);
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  DWORD payloadSize = 0;
+  hr = mEventSink->GetHandlerPayloadSize(riid, mTarget.get(), &payloadSize);
+  *pSize += payloadSize;
+  return hr;
+}
+
+HRESULT
+Interceptor::MarshalInterface(IStream* pStm, REFIID riid, void* pv,
+                              DWORD dwDestContext, void* pvDestContext,
+                              DWORD mshlflags)
+{
+  HRESULT hr = mStdMarshal->MarshalInterface(pStm, riid, pv, dwDestContext,
+                                             pvDestContext, mshlflags);
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  return mEventSink->WriteHandlerPayload(pStm, riid, mTarget.get());
+}
+
+HRESULT
+Interceptor::UnmarshalInterface(IStream* pStm, REFIID riid,
+                                void** ppv)
+{
+  return mStdMarshal->UnmarshalInterface(pStm, riid, ppv);
+}
+
+HRESULT
+Interceptor::ReleaseMarshalData(IStream* pStm)
+{
+  return mStdMarshal->ReleaseMarshalData(pStm);
+}
+
+HRESULT
+Interceptor::DisconnectObject(DWORD dwReserved)
+{
+  return mStdMarshal->DisconnectObject(dwReserved);
+}
+
 Interceptor::MapEntry*
 Interceptor::Lookup(REFIID aIid)
 {
   mMutex.AssertCurrentThreadOwns();
   for (uint32_t index = 0, len = mInterceptorMap.Length(); index < len; ++index) {
     if (mInterceptorMap[index].mIID == aIid) {
       return &mInterceptorMap[index];
     }
@@ -143,57 +215,60 @@ HRESULT
 Interceptor::GetInterceptorForIID(REFIID aIid, void** aOutInterceptor)
 {
   if (!aOutInterceptor) {
     return E_INVALIDARG;
   }
 
   if (aIid == IID_IUnknown) {
     // Special case: When we see IUnknown, we just provide a reference to this
-    *aOutInterceptor = static_cast<IInterceptor*>(this);
-    AddRef();
+    RefPtr<IInterceptor> intcpt(this);
+    intcpt.forget(aOutInterceptor);
     return S_OK;
   }
 
+  REFIID interceptorIid = mEventSink->MarshalAs(aIid);
+
   RefPtr<IUnknown> unkInterceptor;
   IUnknown* interfaceForQILog = nullptr;
 
-  // (1) Check to see if we already have an existing interceptor for aIid.
+  // (1) Check to see if we already have an existing interceptor for
+  // interceptorIid.
 
   { // Scope for lock
     MutexAutoLock lock(mMutex);
-    MapEntry* entry = Lookup(aIid);
+    MapEntry* entry = Lookup(interceptorIid);
     if (entry) {
       unkInterceptor = entry->mInterceptor;
       interfaceForQILog = entry->mTargetInterface;
     }
   }
 
   // (1a) A COM interceptor already exists for this interface, so all we need
   // to do is run a QI on it.
   if (unkInterceptor) {
     // Technically we didn't actually execute a QI on the target interface, but
     // for logging purposes we would like to record the fact that this interface
     // was requested.
     InterceptorLog::QI(S_OK, mTarget.get(), aIid, interfaceForQILog);
 
-    return unkInterceptor->QueryInterface(aIid, aOutInterceptor);
+    return unkInterceptor->QueryInterface(interceptorIid, aOutInterceptor);
   }
 
   // (2) Obtain a new target interface.
 
   // (2a) First, make sure that the target interface is available
   // NB: We *MUST* query the correct interface! ICallEvents::Invoke casts its
   // pvReceiver argument directly to the required interface! DO NOT assume
   // that COM will use QI or upcast/downcast!
   HRESULT hr;
 
   STAUniquePtr<IUnknown> targetInterface;
   IUnknown* rawTargetInterface = nullptr;
-  hr = QueryInterfaceTarget(aIid, (void**)&rawTargetInterface);
+  hr = QueryInterfaceTarget(interceptorIid, (void**)&rawTargetInterface);
   targetInterface.reset(rawTargetInterface);
   InterceptorLog::QI(hr, mTarget.get(), aIid, targetInterface.get());
   MOZ_ASSERT(SUCCEEDED(hr) || hr == E_NOINTERFACE);
   if (FAILED(hr)) {
     return hr;
   }
 
   // We *really* shouldn't be adding interceptors to proxies
@@ -201,17 +276,18 @@ Interceptor::GetInterceptorForIID(REFIID
 
   // (3) Create a new COM interceptor to that interface that delegates its
   // IUnknown to |this|.
 
   // Raise the refcount for stabilization purposes during aggregation
   RefPtr<IUnknown> kungFuDeathGrip(static_cast<IUnknown*>(
         static_cast<WeakReferenceSupport*>(this)));
 
-  hr = CreateInterceptor(aIid, kungFuDeathGrip, getter_AddRefs(unkInterceptor));
+  hr = CreateInterceptor(interceptorIid, kungFuDeathGrip,
+                         getter_AddRefs(unkInterceptor));
   if (FAILED(hr)) {
     return hr;
   }
 
   // (4) Obtain the interceptor's ICallInterceptor interface and register our
   // event sink.
   RefPtr<ICallInterceptor> interceptor;
   hr = unkInterceptor->QueryInterface(IID_ICallInterceptor,
@@ -226,31 +302,31 @@ Interceptor::GetInterceptorForIID(REFIID
   }
 
   // (5) Now that we have this new COM interceptor, insert it into the map.
 
   { // Scope for lock
     MutexAutoLock lock(mMutex);
     // We might have raced with another thread, so first check that we don't
     // already have an entry for this
-    MapEntry* entry = Lookup(aIid);
+    MapEntry* entry = Lookup(interceptorIid);
     if (entry && entry->mInterceptor) {
       unkInterceptor = entry->mInterceptor;
     } else {
       // MapEntry has a RefPtr to unkInterceptor, OTOH we must not touch the
       // refcount for the target interface because we are just moving it into
       // the map and its refcounting might not be thread-safe.
       IUnknown* rawTargetInterface = targetInterface.release();
-      mInterceptorMap.AppendElement(MapEntry(aIid,
+      mInterceptorMap.AppendElement(MapEntry(interceptorIid,
                                              unkInterceptor,
                                              rawTargetInterface));
     }
   }
 
-  return unkInterceptor->QueryInterface(aIid, aOutInterceptor);
+  return unkInterceptor->QueryInterface(interceptorIid, aOutInterceptor);
 }
 
 HRESULT
 Interceptor::QueryInterfaceTarget(REFIID aIid, void** aOutput)
 {
   // NB: This QI needs to run on the main thread because the target object
   // is probably Gecko code that is not thread-safe. Note that this main
   // thread invocation is *synchronous*.
@@ -270,19 +346,73 @@ HRESULT
 Interceptor::QueryInterface(REFIID riid, void** ppv)
 {
   return WeakReferenceSupport::QueryInterface(riid, ppv);
 }
 
 HRESULT
 Interceptor::ThreadSafeQueryInterface(REFIID aIid, IUnknown** aOutInterface)
 {
+  if (aIid == IID_INoMarshal) {
+    // This entire library is designed around marshaling, so there's no point
+    // propagating this QI request all over the place!
+    return E_NOINTERFACE;
+  }
+
+  if (aIid == IID_IStdMarshalInfo) {
+    // Do not indicate that this interface is available unless we actually
+    // support it. We'll check that by looking for a successful call to
+    // IInterceptorSink::GetHandler()
+    CLSID dummy;
+    if (FAILED(mEventSink->GetHandler(&dummy))) {
+      return E_NOINTERFACE;
+    }
+
+    RefPtr<IStdMarshalInfo> std(this);
+    std.forget(aOutInterface);
+    return S_OK;
+  }
+
+  if (aIid == IID_IMarshal) {
+    // Do not indicate that this interface is available unless we actually
+    // support it. We'll check that by looking for a successful call to
+    // IInterceptorSink::GetHandler()
+    CLSID dummy;
+    if (FAILED(mEventSink->GetHandler(&dummy))) {
+      return E_NOINTERFACE;
+    }
+
+    if (!mStdMarshalUnk) {
+      HRESULT hr = ::CoGetStdMarshalEx(static_cast<IWeakReferenceSource*>(this),
+                                       SMEXF_SERVER,
+                                       getter_AddRefs(mStdMarshalUnk));
+      if (FAILED(hr)) {
+        return hr;
+      }
+    }
+
+    if (!mStdMarshal) {
+      HRESULT hr = mStdMarshalUnk->QueryInterface(IID_IMarshal,
+                                                  (void**)&mStdMarshal);
+      if (FAILED(hr)) {
+        return hr;
+      }
+
+      // mStdMarshal is weak, so drop its refcount
+      mStdMarshal->Release();
+    }
+
+    RefPtr<IMarshal> marshal(this);
+    marshal.forget(aOutInterface);
+    return S_OK;
+  }
+
   if (aIid == IID_IInterceptor) {
-    *aOutInterface = static_cast<IInterceptor*>(this);
-    (*aOutInterface)->AddRef();
+    RefPtr<IInterceptor> intcpt(this);
+    intcpt.forget(aOutInterface);
     return S_OK;
   }
 
   if (aIid == IID_IDispatch) {
     STAUniquePtr<IDispatch> disp;
     IDispatch* rawDisp = nullptr;
     HRESULT hr = QueryInterfaceTarget(aIid, (void**)&rawDisp);
     if (FAILED(hr)) {
--- a/ipc/mscom/Interceptor.h
+++ b/ipc/mscom/Interceptor.h
@@ -1,34 +1,37 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#ifndef mozilla_mscom_interceptor_h
-#define mozilla_mscom_interceptor_h
+#ifndef mozilla_mscom_Interceptor_h
+#define mozilla_mscom_Interceptor_h
 
 #include "mozilla/Move.h"
 #include "mozilla/Mutex.h"
 #include "nsTArray.h"
+#include "mozilla/mscom/IHandlerPayload.h"
 #include "mozilla/mscom/Ptr.h"
 #include "mozilla/mscom/WeakRef.h"
 #include "mozilla/RefPtr.h"
 
+#include <objidl.h>
 #include <callobj.h>
 
 namespace mozilla {
 namespace mscom {
 
 // {8831EB53-A937-42BC-9921-B3E1121FDF86}
 DEFINE_GUID(IID_IInterceptorSink,
 0x8831eb53, 0xa937, 0x42bc, 0x99, 0x21, 0xb3, 0xe1, 0x12, 0x1f, 0xdf, 0x86);
 
 struct IInterceptorSink : public ICallFrameEvents
+                        , public HandlerPayload
 {
   virtual STDMETHODIMP SetInterceptor(IWeakReference* aInterceptor) = 0;
 };
 
 // {3710799B-ECA2-4165-B9B0-3FA1E4A9B230}
 DEFINE_GUID(IID_IInterceptor,
 0x3710799b, 0xeca2, 0x4165, 0xb9, 0xb0, 0x3f, 0xa1, 0xe4, 0xa9, 0xb2, 0x30);
 
@@ -52,27 +55,48 @@ struct IInterceptor : public IUnknown
  * interpose QueryInterface calls so that we can instantiate a new
  * ICallInterceptor for each new interface that is requested.
  *
  * We accomplish this by using COM aggregation, which means that the
  * ICallInterceptor delegates its IUnknown implementation to its outer object
  * (the mscom::Interceptor we implement and control).
  */
 class Interceptor final : public WeakReferenceSupport
+                        , public IStdMarshalInfo
+                        , public IMarshal
                         , public IInterceptor
 {
 public:
   static HRESULT Create(STAUniquePtr<IUnknown> aTarget, IInterceptorSink* aSink,
                         REFIID aIid, void** aOutput);
 
   // IUnknown
   STDMETHODIMP QueryInterface(REFIID riid, void** ppv) override;
   STDMETHODIMP_(ULONG) AddRef() override;
   STDMETHODIMP_(ULONG) Release() override;
 
+  // IStdMarshalInfo
+  STDMETHODIMP GetClassForHandler(DWORD aDestContext, void* aDestContextPtr,
+                                  CLSID* aHandlerClsid) override;
+
+  // IMarshal
+  STDMETHODIMP GetUnmarshalClass(REFIID riid, void* pv, DWORD dwDestContext,
+                                 void* pvDestContext, DWORD mshlflags,
+                                 CLSID* pCid) override;
+  STDMETHODIMP GetMarshalSizeMax(REFIID riid, void* pv, DWORD dwDestContext,
+                                 void* pvDestContext, DWORD mshlflags,
+                                 DWORD* pSize) override;
+  STDMETHODIMP MarshalInterface(IStream* pStm, REFIID riid, void* pv,
+                                DWORD dwDestContext, void* pvDestContext,
+                                DWORD mshlflags) override;
+  STDMETHODIMP UnmarshalInterface(IStream* pStm, REFIID riid,
+                                  void** ppv) override;
+  STDMETHODIMP ReleaseMarshalData(IStream* pStm) override;
+  STDMETHODIMP DisconnectObject(DWORD dwReserved) override;
+
   // IInterceptor
   STDMETHODIMP GetTargetForIID(REFIID aIid, InterceptorTargetPtr& aTarget) override;
   STDMETHODIMP GetInterceptorForIID(REFIID aIid, void** aOutInterceptor) override;
 
 private:
   struct MapEntry
   {
     MapEntry(REFIID aIid, IUnknown* aInterceptor, IUnknown* aTargetInterface)
@@ -95,16 +119,18 @@ private:
   HRESULT CreateInterceptor(REFIID aIid, IUnknown* aOuter, IUnknown** aOutput);
 
 private:
   STAUniquePtr<IUnknown>    mTarget;
   RefPtr<IInterceptorSink>  mEventSink;
   mozilla::Mutex            mMutex; // Guards mInterceptorMap
   // Using a nsTArray since the # of interfaces is not going to be very high
   nsTArray<MapEntry>        mInterceptorMap;
+  RefPtr<IUnknown>          mStdMarshalUnk;
+  IMarshal*                 mStdMarshal; // WEAK
 };
 
 template <typename InterfaceT>
 inline HRESULT
 CreateInterceptor(STAUniquePtr<InterfaceT> aTargetInterface,
                   IInterceptorSink* aEventSink,
                   InterfaceT** aOutInterface)
 {
@@ -117,9 +143,9 @@ CreateInterceptor(STAUniquePtr<Interface
   STAUniquePtr<IUnknown> targetUnknown(aTargetInterface.release());
   return Interceptor::Create(Move(targetUnknown), aEventSink, iidTarget,
                              (void**)aOutInterface);
 }
 
 } // namespace mscom
 } // namespace mozilla
 
-#endif // mozilla_mscom_interceptor_h
+#endif // mozilla_mscom_Interceptor_h
--- a/ipc/mscom/MainThreadHandoff.cpp
+++ b/ipc/mscom/MainThreadHandoff.cpp
@@ -1,70 +1,179 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/mscom/MainThreadHandoff.h"
 
+#include "mozilla/Attributes.h"
 #include "mozilla/Move.h"
+#include "mozilla/mscom/AgileReference.h"
 #include "mozilla/mscom/InterceptorLog.h"
 #include "mozilla/mscom/Registration.h"
 #include "mozilla/mscom/Utils.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/DebugOnly.h"
 #include "nsThreadUtils.h"
 #include "nsProxyRelease.h"
 
 using mozilla::DebugOnly;
+using mozilla::mscom::AgileReference;
 
 namespace {
 
+class MOZ_NON_TEMPORARY_CLASS InParamWalker : private ICallFrameWalker
+{
+public:
+  InParamWalker()
+    : mPreHandoff(true)
+  {
+  }
+
+  void SetHandoffDone()
+  {
+    mPreHandoff = false;
+    mAgileRefsItr = mAgileRefs.begin();
+  }
+
+  HRESULT Walk(ICallFrame* aFrame)
+  {
+    MOZ_ASSERT(aFrame);
+    if (!aFrame) {
+      return E_INVALIDARG;
+    }
+
+    return aFrame->WalkFrame(CALLFRAME_WALK_IN, this);
+  }
+
+private:
+  // IUnknown
+  STDMETHODIMP QueryInterface(REFIID aIid, void** aOutInterface) override
+  {
+    if (!aOutInterface) {
+      return E_INVALIDARG;
+    }
+    *aOutInterface = nullptr;
+
+    if (aIid == IID_IUnknown || aIid == IID_ICallFrameWalker) {
+      *aOutInterface = static_cast<ICallFrameWalker*>(this);
+      return S_OK;
+    }
+
+    return E_NOINTERFACE;
+  }
+
+  STDMETHODIMP_(ULONG) AddRef() override
+  {
+    return 2;
+  }
+
+  STDMETHODIMP_(ULONG) Release() override
+  {
+    return 1;
+  }
+
+  // ICallFrameWalker
+  STDMETHODIMP OnWalkInterface(REFIID aIid, PVOID* aInterface, BOOL aIn,
+                               BOOL aOut) override
+  {
+    MOZ_ASSERT(aIn);
+    if (!aIn) {
+      return E_UNEXPECTED;
+    }
+
+    IUnknown* origInterface = static_cast<IUnknown*>(*aInterface);
+    if (!origInterface) {
+      // Nothing to do
+      return S_OK;
+    }
+
+    if (mPreHandoff) {
+      mAgileRefs.AppendElement(AgileReference(aIid, origInterface));
+      return S_OK;
+    }
+
+    MOZ_ASSERT(mAgileRefsItr != mAgileRefs.end());
+    if (mAgileRefsItr == mAgileRefs.end()) {
+      return E_UNEXPECTED;
+    }
+
+    HRESULT hr = mAgileRefsItr->Resolve(aIid, aInterface);
+    MOZ_ASSERT(SUCCEEDED(hr));
+    if (SUCCEEDED(hr)) {
+      ++mAgileRefsItr;
+    }
+
+    return hr;
+  }
+
+  InParamWalker(const InParamWalker&) = delete;
+  InParamWalker(InParamWalker&&) = delete;
+  InParamWalker& operator=(const InParamWalker&) = delete;
+  InParamWalker& operator=(InParamWalker&&) = delete;
+
+private:
+  bool                                mPreHandoff;
+  AutoTArray<AgileReference, 1>       mAgileRefs;
+  nsTArray<AgileReference>::iterator  mAgileRefsItr;
+};
+
 class HandoffRunnable : public mozilla::Runnable
 {
 public:
   explicit HandoffRunnable(ICallFrame* aCallFrame, IUnknown* aTargetInterface)
     : mCallFrame(aCallFrame)
     , mTargetInterface(aTargetInterface)
     , mResult(E_UNEXPECTED)
   {
+    DebugOnly<HRESULT> hr = mInParamWalker.Walk(aCallFrame);
+    MOZ_ASSERT(SUCCEEDED(hr));
   }
 
   NS_IMETHOD Run() override
   {
+    mInParamWalker.SetHandoffDone();
+    // We declare hr a DebugOnly because if mInParamWalker.Walk() fails, then
+    // mCallFrame->Invoke will fail anyway.
+    DebugOnly<HRESULT> hr = mInParamWalker.Walk(mCallFrame);
+    MOZ_ASSERT(SUCCEEDED(hr));
     mResult = mCallFrame->Invoke(mTargetInterface);
     return NS_OK;
   }
 
   HRESULT GetResult() const
   {
     return mResult;
   }
 
 private:
-  ICallFrame* mCallFrame;
-  IUnknown*   mTargetInterface;
-  HRESULT     mResult;
+  ICallFrame*   mCallFrame;
+  InParamWalker mInParamWalker;
+  IUnknown*     mTargetInterface;
+  HRESULT       mResult;
 };
 
 } // anonymous namespace
 
 namespace mozilla {
 namespace mscom {
 
 /* static */ HRESULT
-MainThreadHandoff::Create(IInterceptorSink** aOutput)
+MainThreadHandoff::Create(IHandlerPayload* aHandlerPayload,
+                          IInterceptorSink** aOutput)
 {
-  RefPtr<MainThreadHandoff> handoff(new MainThreadHandoff());
+  RefPtr<MainThreadHandoff> handoff(new MainThreadHandoff(aHandlerPayload));
   return handoff->QueryInterface(IID_IInterceptorSink, (void**) aOutput);
 }
 
-MainThreadHandoff::MainThreadHandoff()
+MainThreadHandoff::MainThreadHandoff(IHandlerPayload* aHandlerPayload)
   : mRefCnt(0)
+  , mHandlerPayload(aHandlerPayload)
 {
 }
 
 MainThreadHandoff::~MainThreadHandoff()
 {
   MOZ_ASSERT(NS_IsMainThread());
 }
 
@@ -138,17 +247,17 @@ MainThreadHandoff::OnCall(ICallFrame* aF
   }
 
   InterceptorTargetPtr targetInterface;
   hr = interceptor->GetTargetForIID(iid, targetInterface);
   if (FAILED(hr)) {
     return hr;
   }
 
-  // (2) Execute the method call syncrhonously on the main thread
+  // (2) Execute the method call synchronously on the main thread
   RefPtr<HandoffRunnable> handoffInfo(new HandoffRunnable(aFrame,
                                                           targetInterface.get()));
   MainThreadInvoker invoker;
   if (!invoker.Invoke(do_AddRef(handoffInfo))) {
     MOZ_ASSERT(false);
     return E_UNEXPECTED;
   }
   hr = handoffInfo->GetResult();
@@ -292,16 +401,54 @@ MainThreadHandoff::FixArrayElements(ICal
 HRESULT
 MainThreadHandoff::SetInterceptor(IWeakReference* aInterceptor)
 {
   mInterceptor = aInterceptor;
   return S_OK;
 }
 
 HRESULT
+MainThreadHandoff::GetHandler(CLSID* aHandlerClsid)
+{
+  if (!mHandlerPayload) {
+    return E_NOTIMPL;
+  }
+  return mHandlerPayload->GetHandler(aHandlerClsid);
+}
+
+HRESULT
+MainThreadHandoff::GetHandlerPayloadSize(REFIID aIid, IUnknown* aTarget,
+                                         DWORD* aOutPayloadSize)
+{
+  if (!mHandlerPayload) {
+    return E_NOTIMPL;
+  }
+  return mHandlerPayload->GetHandlerPayloadSize(aIid, aTarget, aOutPayloadSize);
+}
+
+HRESULT
+MainThreadHandoff::WriteHandlerPayload(IStream* aStream, REFIID aIid,
+                                       IUnknown* aTarget)
+{
+  if (!mHandlerPayload) {
+    return E_NOTIMPL;
+  }
+  return mHandlerPayload->WriteHandlerPayload(aStream, aIid, aTarget);
+}
+
+REFIID
+MainThreadHandoff::MarshalAs(REFIID aIid)
+{
+  if (!mHandlerPayload) {
+    return aIid;
+  }
+  return mHandlerPayload->MarshalAs(aIid);
+}
+
+HRESULT
 MainThreadHandoff::OnWalkInterface(REFIID aIid, PVOID* aInterface,
                                    BOOL aIsInParam, BOOL aIsOutParam)
 {
   MOZ_ASSERT(aInterface && aIsOutParam);
   if (!aInterface || !aIsOutParam) {
     return E_UNEXPECTED;
   }
 
@@ -364,26 +511,37 @@ MainThreadHandoff::OnWalkInterface(REFII
       if (FAILED(hr)) {
         return hr;
       }
       *aInterface = intercepted;
       return S_OK;
     }
   }
 
+  RefPtr<IHandlerPayload> payload;
+  if (mHandlerPayload) {
+    hr = mHandlerPayload->Clone(getter_AddRefs(payload));
+    MOZ_ASSERT(SUCCEEDED(hr));
+    if (FAILED(hr)) {
+      return hr;
+    }
+  }
+
   // Now create a new MainThreadHandoff wrapper...
   RefPtr<IInterceptorSink> handoff;
-  hr = MainThreadHandoff::Create(getter_AddRefs(handoff));
+  hr = MainThreadHandoff::Create(payload, getter_AddRefs(handoff));
   MOZ_ASSERT(SUCCEEDED(hr));
   if (FAILED(hr)) {
     return hr;
   }
 
+  REFIID interceptorIid = payload ? payload->MarshalAs(aIid) : aIid;
+
   RefPtr<IUnknown> wrapped;
-  hr = Interceptor::Create(Move(origInterface), handoff, aIid,
+  hr = Interceptor::Create(Move(origInterface), handoff, interceptorIid,
                            getter_AddRefs(wrapped));
   MOZ_ASSERT(SUCCEEDED(hr));
   if (FAILED(hr)) {
     return hr;
   }
 
   // And replace the original interface pointer with the wrapped one.
   wrapped.forget(reinterpret_cast<IUnknown**>(aInterface));
--- a/ipc/mscom/MainThreadHandoff.h
+++ b/ipc/mscom/MainThreadHandoff.h
@@ -19,53 +19,72 @@ namespace mozilla {
 namespace mscom {
 
 struct ArrayData;
 
 class MainThreadHandoff final : public IInterceptorSink
                               , public ICallFrameWalker
 {
 public:
-  static HRESULT Create(IInterceptorSink** aOutput);
+  static HRESULT Create(IHandlerPayload* aHandlerPayload,
+                        IInterceptorSink** aOutput);
 
   template <typename Interface>
   static HRESULT WrapInterface(STAUniquePtr<Interface> aTargetInterface,
                                Interface** aOutInterface)
   {
+    return WrapInterface<Interface>(Move(aTargetInterface), nullptr,
+                                    aOutInterface);
+  }
+
+  template <typename Interface>
+  static HRESULT WrapInterface(STAUniquePtr<Interface> aTargetInterface,
+                               IHandlerPayload* aHandlerPayload,
+                               Interface** aOutInterface)
+  {
     MOZ_ASSERT(!IsProxy(aTargetInterface.get()));
     RefPtr<IInterceptorSink> handoff;
-    HRESULT hr = MainThreadHandoff::Create(getter_AddRefs(handoff));
+    HRESULT hr = MainThreadHandoff::Create(aHandlerPayload,
+                                           getter_AddRefs(handoff));
     if (FAILED(hr)) {
       return hr;
     }
     return CreateInterceptor(Move(aTargetInterface), handoff, aOutInterface);
   }
 
   // IUnknown
   STDMETHODIMP QueryInterface(REFIID riid, void** ppv) override;
   STDMETHODIMP_(ULONG) AddRef() override;
   STDMETHODIMP_(ULONG) Release() override;
 
   // ICallFrameEvents
   STDMETHODIMP OnCall(ICallFrame* aFrame) override;
 
   // IInterceptorSink
   STDMETHODIMP SetInterceptor(IWeakReference* aInterceptor) override;
+  STDMETHODIMP GetHandler(CLSID* aHandlerClsid) override;
+  STDMETHODIMP GetHandlerPayloadSize(REFIID aIid,
+                                     IUnknown* aTarget,
+                                     DWORD* aOutPayloadSize) override;
+  STDMETHODIMP WriteHandlerPayload(IStream* aStream, REFIID aIid,
+                                   IUnknown* aTarget) override;
+  REFIID MarshalAs(REFIID aIid) override;
 
   // ICallFrameWalker
   STDMETHODIMP OnWalkInterface(REFIID aIid, PVOID* aInterface, BOOL aIsInParam,
                                BOOL aIsOutParam) override;
 
 private:
-  MainThreadHandoff();
+  explicit MainThreadHandoff(IHandlerPayload* aHandlerPayload);
   ~MainThreadHandoff();
   HRESULT FixArrayElements(ICallFrame* aFrame,
                            const ArrayData& aArrayData);
 
 private:
   ULONG                   mRefCnt;
   RefPtr<IWeakReference>  mInterceptor;
+  RefPtr<IHandlerPayload> mHandlerPayload;
 };
 
 } // namespace mscom
 } // namespace mozilla
 
 #endif // mozilla_mscom_MainThreadHandoff_h
--- a/ipc/mscom/MainThreadInvoker.cpp
+++ b/ipc/mscom/MainThreadInvoker.cpp
@@ -4,100 +4,59 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/mscom/MainThreadInvoker.h"
 
 #include "GeckoProfiler.h"
 #include "MainThreadUtils.h"
 #include "mozilla/Assertions.h"
-#include "mozilla/Atomics.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/HangMonitor.h"
+#include "mozilla/mscom/SpinEvent.h"
 #include "mozilla/RefPtr.h"
-#include "nsServiceManagerUtils.h"
-#include "nsSystemInfo.h"
 #include "private/prpriv.h" // For PR_GetThreadID
 #include "WinUtils.h"
 
-// This gives us compiler intrinsics for the x86 PAUSE instruction
-#if defined(_MSC_VER)
-#include <intrin.h>
-#pragma intrinsic(_mm_pause)
-#define CPU_PAUSE() _mm_pause()
-#elif defined(__GNUC__) || defined(__clang__)
-#define CPU_PAUSE() __builtin_ia32_pause()
-#endif
-
-static bool sIsMulticore;
-
 namespace {
 
 /**
  * SyncRunnable implements different code paths depending on whether or not
  * we are running on a multiprocessor system. In the multiprocessor case, we
  * leave the thread in a spin loop while waiting for the main thread to execute
  * our runnable. Since spinning is pointless in the uniprocessor case, we block
  * on an event that is set by the main thread once it has finished the runnable.
  */
 class MOZ_RAII SyncRunnable
 {
 public:
   explicit SyncRunnable(already_AddRefed<nsIRunnable>&& aRunnable)
-    : mDoneEvent(sIsMulticore ? nullptr :
-                 ::CreateEventW(nullptr, FALSE, FALSE, nullptr))
-    , mDone(false)
-    , mRunnable(aRunnable)
+    : mRunnable(aRunnable)
   {
-    MOZ_ASSERT(sIsMulticore || mDoneEvent);
     MOZ_ASSERT(mRunnable);
   }
 
-  ~SyncRunnable()
-  {
-    if (mDoneEvent) {
-      ::CloseHandle(mDoneEvent);
-    }
-  }
+  ~SyncRunnable() = default;
 
   void Run()
   {
     mRunnable->Run();
 
-    if (mDoneEvent) {
-      ::SetEvent(mDoneEvent);
-    } else {
-      mDone = true;
-    }
+    mEvent.Signal();
   }
 
   bool WaitUntilComplete()
   {
-    if (mDoneEvent) {
-      HANDLE handles[] = {mDoneEvent,
-                          mozilla::mscom::MainThreadInvoker::GetTargetThread()};
-      DWORD waitResult = ::WaitForMultipleObjects(mozilla::ArrayLength(handles),
-                                                  handles, FALSE, INFINITE);
-      return waitResult == WAIT_OBJECT_0;
-    }
-
-    while (!mDone) {
-      // The PAUSE instruction is a hint to the CPU that we're doing a spin
-      // loop. It is a no-op on older processors that don't support it, so
-      // it is safe to use here without any CPUID checks.
-      CPU_PAUSE();
-    }
-    return true;
+    return mEvent.Wait(mozilla::mscom::MainThreadInvoker::GetTargetThread());
   }
 
 private:
-  HANDLE                mDoneEvent;
-  mozilla::Atomic<bool> mDone;
-  nsCOMPtr<nsIRunnable> mRunnable;
+  nsCOMPtr<nsIRunnable>     mRunnable;
+  mozilla::mscom::SpinEvent mEvent;
 };
 
 } // anonymous namespace
 
 namespace mozilla {
 namespace mscom {
 
 HANDLE MainThreadInvoker::sMainThread = nullptr;
@@ -115,24 +74,16 @@ MainThreadInvoker::InitStatics()
   rv = mainThread->GetPRThread(&mainPrThread);
   if (NS_FAILED(rv)) {
     return false;
   }
 
   PRUint32 tid = ::PR_GetThreadID(mainPrThread);
   sMainThread = ::OpenThread(SYNCHRONIZE | THREAD_SET_CONTEXT, FALSE, tid);
 
-  nsCOMPtr<nsIPropertyBag2> infoService = do_GetService(NS_SYSTEMINFO_CONTRACTID);
-  if (infoService) {
-    uint32_t cpuCount;
-    nsresult rv = infoService->GetPropertyAsUint32(NS_LITERAL_STRING("cpucount"),
-                                                   &cpuCount);
-    sIsMulticore = NS_SUCCEEDED(rv) && cpuCount > 1;
-  }
-
   return !!sMainThread;
 }
 
 MainThreadInvoker::MainThreadInvoker()
 {
   static const bool gotStatics = InitStatics();
   MOZ_ASSERT(gotStatics);
 }
--- a/ipc/mscom/ProxyStream.cpp
+++ b/ipc/mscom/ProxyStream.cpp
@@ -1,19 +1,19 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "mozilla/Move.h"
 #include "mozilla/mscom/EnsureMTA.h"
 #include "mozilla/mscom/ProxyStream.h"
 #include "mozilla/mscom/Utils.h"
-
-#include "mozilla/Move.h"
+#include "nsExceptionHandler.h"
 
 #include <windows.h>
 #include <objbase.h>
 #include <shlwapi.h>
 
 namespace mozilla {
 namespace mscom {
 
@@ -39,40 +39,48 @@ ProxyStream::ProxyStream(const BYTE* aIn
   // NB: We can't check for a null mStream until after we have checked for
   // the zero aInitBufSize above. This is because InitStream will also fail
   // in that case, even though marshaling a nullptr is allowable.
   MOZ_ASSERT(mStream);
   if (!mStream) {
     return;
   }
 
+  HRESULT unmarshalResult = S_OK;
+
   // We need to convert to an interface here otherwise we mess up const
   // correctness with IPDL. We'll request an IUnknown and then QI the
   // actual interface later.
 
   auto marshalFn = [&]() -> void
   {
     IUnknown* rawUnmarshaledProxy = nullptr;
     // OK to forget mStream when calling into this function because the stream
     // gets released even if the unmarshaling part fails.
-    DebugOnly<HRESULT> hr =
+    unmarshalResult =
       ::CoGetInterfaceAndReleaseStream(mStream.forget().take(), IID_IUnknown,
                                        (void**)&rawUnmarshaledProxy);
-    MOZ_ASSERT(SUCCEEDED(hr));
+    MOZ_ASSERT(SUCCEEDED(unmarshalResult));
     mUnmarshaledProxy.reset(rawUnmarshaledProxy);
   };
 
   if (XRE_IsParentProcess()) {
     // We'll marshal this stuff directly using the current thread, therefore its
     // proxy will reside in the same apartment as the current thread.
     marshalFn();
   } else {
     // When marshaling in child processes, we want to force the MTA.
     EnsureMTA mta(marshalFn);
   }
+
+  if (FAILED(unmarshalResult)) {
+    nsPrintfCString hrAsStr("0x%08X", unmarshalResult);
+    CrashReporter::AnnotateCrashReport(
+        NS_LITERAL_CSTRING("CoGetInterfaceAndReleaseStreamFailure"), hrAsStr);
+  }
 }
 
 already_AddRefed<IStream>
 ProxyStream::InitStream(const BYTE* aInitBuf, const UINT aInitBufSize)
 {
   return already_AddRefed<IStream>(::SHCreateMemStream(aInitBuf, aInitBufSize));
 }
 
@@ -151,42 +159,51 @@ ProxyStream::GetInterface(REFIID aIID, v
 ProxyStream::ProxyStream(REFIID aIID, IUnknown* aObject)
   : mGlobalLockedBuf(nullptr)
   , mHGlobal(nullptr)
   , mBufSize(0)
 {
   RefPtr<IStream> stream;
   HGLOBAL hglobal = NULL;
 
+  HRESULT marshalResult = S_OK;
+
   auto marshalFn = [&]() -> void
   {
     HRESULT hr = ::CreateStreamOnHGlobal(nullptr, TRUE, getter_AddRefs(stream));
     if (FAILED(hr)) {
       return;
     }
 
     hr = ::CoMarshalInterface(stream, aIID, aObject, MSHCTX_LOCAL, nullptr,
                               MSHLFLAGS_NORMAL);
     if (FAILED(hr)) {
+      marshalResult = hr;
       return;
     }
 
     hr = ::GetHGlobalFromStream(stream, &hglobal);
     MOZ_ASSERT(SUCCEEDED(hr));
   };
 
   if (XRE_IsParentProcess()) {
     // We'll marshal this stuff directly using the current thread, therefore its
     // stub will reside in the same apartment as the current thread.
     marshalFn();
   } else {
     // When marshaling in child processes, we want to force the MTA.
     EnsureMTA mta(marshalFn);
   }
 
+  if (FAILED(marshalResult)) {
+    nsPrintfCString hrAsStr("0x%08X", marshalResult);
+    CrashReporter::AnnotateCrashReport(
+        NS_LITERAL_CSTRING("CoMarshalInterfaceFailure"), hrAsStr);
+  }
+
   mStream = mozilla::Move(stream);
   if (hglobal) {
     mGlobalLockedBuf = reinterpret_cast<BYTE*>(::GlobalLock(hglobal));
     mHGlobal = hglobal;
     mBufSize = static_cast<int>(::GlobalSize(hglobal));
   }
 }
 
--- a/ipc/mscom/Registration.cpp
+++ b/ipc/mscom/Registration.cpp
@@ -364,26 +364,26 @@ RegisteredProxy::operator=(RegisteredPro
   mRegCookie = aOther.mRegCookie;
   aOther.mRegCookie = 0;
   mTypeLib = aOther.mTypeLib;
   aOther.mTypeLib = nullptr;
   return *this;
 }
 
 HRESULT
-RegisteredProxy::GetTypeInfoForInterface(REFIID aIid,
-                                         ITypeInfo** aOutTypeInfo) const
+RegisteredProxy::GetTypeInfoForGuid(REFGUID aGuid,
+                                    ITypeInfo** aOutTypeInfo) const
 {
   if (!aOutTypeInfo) {
     return E_INVALIDARG;
   }
   if (!mTypeLib) {
     return E_UNEXPECTED;
   }
-  return mTypeLib->lpVtbl->GetTypeInfoOfGuid(mTypeLib, aIid, aOutTypeInfo);
+  return mTypeLib->lpVtbl->GetTypeInfoOfGuid(mTypeLib, aGuid, aOutTypeInfo);
 }
 
 static StaticAutoPtr<Vector<RegisteredProxy*>> sRegistry;
 
 namespace UseGetMutexForAccess {
 
 // This must not be accessed directly; use GetMutex() instead
 static CRITICAL_SECTION sMutex;
@@ -413,17 +413,17 @@ RegisteredProxy::Find(REFIID aIid, IType
 {
   AutoCriticalSection lock(GetMutex());
 
   if (!sRegistry) {
     return false;
   }
 
   for (auto&& proxy : *sRegistry) {
-    if (SUCCEEDED(proxy->GetTypeInfoForInterface(aIid, aTypeInfo))) {
+    if (SUCCEEDED(proxy->GetTypeInfoForGuid(aIid, aTypeInfo))) {
       return true;
     }
   }
 
   return false;
 }
 
 /* static */ void
--- a/ipc/mscom/Registration.h
+++ b/ipc/mscom/Registration.h
@@ -31,17 +31,17 @@ public:
   RegisteredProxy(IUnknown* aClassObject, uint32_t aRegCookie,
                   ITypeLib* aTypeLib);
   explicit RegisteredProxy(ITypeLib* aTypeLib);
   RegisteredProxy(RegisteredProxy&& aOther);
   RegisteredProxy& operator=(RegisteredProxy&& aOther);
 
   ~RegisteredProxy();
 
-  HRESULT GetTypeInfoForInterface(REFIID aIid, ITypeInfo** aOutTypeInfo) const;
+  HRESULT GetTypeInfoForGuid(REFGUID aGuid, ITypeInfo** aOutTypeInfo) const;
 
   static bool Find(REFIID aIid, ITypeInfo** aOutTypeInfo);
 
 private:
   RegisteredProxy() = delete;
   RegisteredProxy(RegisteredProxy&) = delete;
   RegisteredProxy& operator=(RegisteredProxy&) = delete;
 
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/SpinEvent.cpp
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/mscom/SpinEvent.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsSystemInfo.h"
+
+// This gives us compiler intrinsics for the x86 PAUSE instruction
+#if defined(_MSC_VER)
+#include <intrin.h>
+#pragma intrinsic(_mm_pause)
+#define CPU_PAUSE() _mm_pause()
+#elif defined(__GNUC__) || defined(__clang__)
+#define CPU_PAUSE() __builtin_ia32_pause()
+#endif
+
+namespace mozilla {
+namespace mscom {
+
+SpinEvent::SpinEvent()
+  : mDone(false)
+{
+  static const bool sIsMulticore = []() {
+    nsCOMPtr<nsIPropertyBag2> infoService = do_GetService(NS_SYSTEMINFO_CONTRACTID);
+    if (!infoService) {
+      return false;
+    }
+
+    uint32_t cpuCount;
+    nsresult rv = infoService->GetPropertyAsUint32(NS_LITERAL_STRING("cpucount"),
+                                                   &cpuCount);
+    return NS_SUCCEEDED(rv) && cpuCount > 1;
+  }();
+
+  if (!sIsMulticore) {
+    mDoneEvent.own(::CreateEventW(nullptr, FALSE, FALSE, nullptr));
+    MOZ_ASSERT(mDoneEvent);
+  }
+}
+
+bool
+SpinEvent::Wait(HANDLE aTargetThread)
+{
+  MOZ_ASSERT(aTargetThread);
+  if (!aTargetThread) {
+    return false;
+  }
+
+  if (mDoneEvent) {
+    HANDLE handles[] = {mDoneEvent, aTargetThread};
+    DWORD waitResult = ::WaitForMultipleObjects(mozilla::ArrayLength(handles),
+                                                handles, FALSE, INFINITE);
+    return waitResult == WAIT_OBJECT_0;
+  }
+
+  while (!mDone) {
+    // The PAUSE instruction is a hint to the CPU that we're doing a spin
+    // loop. It is a no-op on older processors that don't support it, so
+    // it is safe to use here without any CPUID checks.
+    CPU_PAUSE();
+  }
+  return true;
+}
+
+void
+SpinEvent::Signal()
+{
+  if (mDoneEvent) {
+    ::SetEvent(mDoneEvent);
+  } else {
+    mDone = true;
+  }
+}
+
+} // namespace mscom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/SpinEvent.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_mscom_SpinEvent_h
+#define mozilla_mscom_SpinEvent_h
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "nsWindowsHelpers.h"
+
+namespace mozilla {
+namespace mscom {
+
+class MOZ_NON_TEMPORARY_CLASS SpinEvent final
+{
+public:
+  SpinEvent();
+  ~SpinEvent() = default;
+
+  bool Wait(HANDLE aTargetThread);
+  void Signal();
+
+  SpinEvent(const SpinEvent&) = delete;
+  SpinEvent(SpinEvent&&) = delete;
+  SpinEvent& operator=(SpinEvent&&) = delete;
+  SpinEvent& operator=(const SpinEvent&) = delete;
+
+private:
+  Atomic<bool, ReleaseAcquire>  mDone;
+  nsAutoHandle                  mDoneEvent;
+};
+
+} // namespace mscom
+} // namespace mozilla
+
+#endif // mozilla_mscom_SpinEvent_h
--- a/ipc/mscom/moz.build
+++ b/ipc/mscom/moz.build
@@ -21,31 +21,38 @@ UNIFIED_SOURCES += [
     'AgileReference.cpp',
     'EnsureMTA.cpp',
     'MainThreadRuntime.cpp',
     'ProxyStream.cpp',
     'Utils.cpp',
 ]
 
 if CONFIG['ACCESSIBILITY']:
+    DIRS += [
+        'oop',
+    ]
+
     EXPORTS.mozilla.mscom += [
         'ActivationContext.h',
         'DispatchForwarder.h',
+        'IHandlerPayload.h',
         'Interceptor.h',
         'InterceptorLog.h',
         'MainThreadHandoff.h',
         'MainThreadInvoker.h',
         'Registration.h',
+        'SpinEvent.h',
         'StructStream.h',
         'WeakRef.h',
     ]
 
     SOURCES += [
         'Interceptor.cpp',
         'Registration.cpp',
+        'SpinEvent.cpp',
         'WeakRef.cpp',
     ]
 
     UNIFIED_SOURCES += [
         'ActivationContext.cpp',
         'DispatchForwarder.cpp',
         'InterceptorLog.cpp',
         'MainThreadHandoff.cpp',
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/oop/Factory.h
@@ -0,0 +1,155 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_mscom_Factory_h
+#define mozilla_mscom_Factory_h
+
+#if defined(MOZILLA_INTERNAL_API)
+#error This code is NOT for internal Gecko use!
+#endif // defined(MOZILLA_INTERNAL_API)
+
+#include "mozilla/Attributes.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Move.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/StaticPtr.h"
+#include "Module.h"
+
+#include <objbase.h>
+#include <unknwn.h>
+
+/* WARNING! The code in this file may be loaded into the address spaces of other
+   processes! It MUST NOT link against xul.dll or other Gecko binaries! Only
+   inline code may be included! */
+
+namespace mozilla {
+namespace mscom {
+
+template <typename T>
+class MOZ_NONHEAP_CLASS Factory : public IClassFactory
+{
+  template <typename... Args>
+  HRESULT DoCreate(Args... args)
+  {
+    MOZ_DIAGNOSTIC_ASSERT(false, "This should not be executed");
+    return E_NOTIMPL;
+  }
+
+  template <typename... Args>
+  HRESULT DoCreate(HRESULT (*aFnPtr)(IUnknown*, REFIID, void**), Args... args)
+  {
+    return aFnPtr(mozilla::Forward<Args>(args)...);
+  }
+
+public:
+  // IUnknown
+  STDMETHODIMP QueryInterface(REFIID aIid, void** aOutInterface) override
+  {
+    if (!aOutInterface) {
+      return E_INVALIDARG;
+    }
+
+    if (aIid == IID_IUnknown || aIid == IID_IClassFactory) {
+      RefPtr<IClassFactory> punk(this);
+      punk.forget(aOutInterface);
+      return S_OK;
+    }
+
+    *aOutInterface = nullptr;
+
+    return E_NOINTERFACE;
+  }
+
+  STDMETHODIMP_(ULONG) AddRef() override
+  {
+    Module::Lock();
+    return 2;
+  }
+
+  STDMETHODIMP_(ULONG) Release() override
+  {
+    Module::Unlock();
+    return 1;
+  }
+
+  // IClassFactory
+  STDMETHODIMP CreateInstance(IUnknown* aOuter, REFIID aIid,
+                              void** aOutInterface) override
+  {
+    return DoCreate(&T::Create, aOuter, aIid, aOutInterface);
+  }
+
+  STDMETHODIMP LockServer(BOOL aLock) override
+  {
+    if (aLock) {
+      Module::Lock();
+    } else {
+      Module::Unlock();
+    }
+    return S_OK;
+  }
+};
+
+template <typename T>
+class MOZ_NONHEAP_CLASS SingletonFactory : public Factory<T>
+{
+public:
+  STDMETHODIMP CreateInstance(IUnknown* aOuter, REFIID aIid,
+                              void** aOutInterface) override
+  {
+    if (aOuter || !aOutInterface) {
+      return E_INVALIDARG;
+    }
+
+    RefPtr<T> obj(sInstance);
+    if (!obj) {
+      obj = GetOrCreateSingleton();
+    }
+
+    return obj->QueryInterface(aIid, aOutInterface);
+  }
+
+  RefPtr<T> GetOrCreateSingleton()
+  {
+    if (!sInstance) {
+      RefPtr<T> object;
+      if (FAILED(T::Create(getter_AddRefs(object)))) {
+        return nullptr;
+      }
+
+      sInstance = object.forget();
+    }
+
+    return sInstance;
+  }
+
+  RefPtr<T> GetSingleton()
+  {
+    return sInstance;
+  }
+
+  void ClearSingleton()
+  {
+    if (!sInstance) {
+      return;
+    }
+
+    DebugOnly<HRESULT> hr = ::CoDisconnectObject(sInstance.get(), 0);
+    MOZ_ASSERT(SUCCEEDED(hr));
+    sInstance = nullptr;
+  }
+
+private:
+  static StaticRefPtr<T> sInstance;
+};
+
+template <typename T>
+StaticRefPtr<T> SingletonFactory<T>::sInstance;
+
+} // namespace mscom
+} // namespace mozilla
+
+#endif // mozilla_mscom_Fa