Merge autoland to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 24 Aug 2017 20:20:08 -0400
changeset 376725 2306e153fba9ca55726ffcce889eaca7a479c29f
parent 376724 3199bacd6b3827d711155a9e81c081434cf7cd8f (current diff)
parent 376723 ff56b0b306286bb5dde2dad1d5c0ed64b53e084e (diff)
child 376726 942d868646cf915a51ce5c5f6c61d6c43e3c17db
child 376791 a3f57d191ae26e1357d681730998688514722855
push id94144
push userryanvm@gmail.com
push dateFri, 25 Aug 2017 00:21:50 +0000
treeherdermozilla-inbound@942d868646cf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone57.0a1
first release with
nightly linux32
2306e153fba9 / 57.0a1 / 20170825100126 / files
nightly linux64
2306e153fba9 / 57.0a1 / 20170825100126 / files
nightly mac
2306e153fba9 / 57.0a1 / 20170825100126 / files
nightly win32
2306e153fba9 / 57.0a1 / 20170825100126 / files
nightly win64
2306e153fba9 / 57.0a1 / 20170825100126 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to m-c. a=merge
browser/extensions/formautofill/content/editAddress.js
browser/extensions/formautofill/skin/linux/editAddress.css
browser/extensions/formautofill/skin/osx/editAddress.css
browser/extensions/formautofill/skin/shared/editAddress.css
browser/extensions/formautofill/skin/windows/editAddress.css
browser/themes/linux/reload-stop-go.png
browser/themes/linux/reload-stop-go@2x.png
browser/themes/osx/reload-stop-go-yosemite.png
browser/themes/osx/reload-stop-go-yosemite@2x.png
browser/themes/osx/reload-stop-go.png
browser/themes/osx/reload-stop-go@2x.png
browser/themes/windows/reload-stop-go-win7.png
browser/themes/windows/reload-stop-go-win7@2x.png
browser/themes/windows/reload-stop-go.png
browser/themes/windows/reload-stop-go@2x.png
build/moz.configure/warnings.configure
gfx/webrender/res/debug_color.fs.glsl
gfx/webrender/res/debug_color.vs.glsl
gfx/webrender/res/debug_font.fs.glsl
gfx/webrender/res/debug_font.vs.glsl
mobile/android/app/src/main/res/drawable-hdpi/progress.9.png
mobile/android/app/src/main/res/drawable-ldrtl-hdpi/progress.9.png
mobile/android/app/src/main/res/drawable-ldrtl-xhdpi/progress.9.png
mobile/android/app/src/main/res/drawable-xhdpi/progress.9.png
mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarProgressView.java
taskcluster/ci/nightly-fennec/docker_build.yml
taskcluster/docs/yaml-templates.rst
testing/web-platform/meta/html/semantics/embedded-content/media-elements/playing-the-media-resource/playbackRate.html.ini
third_party/rust/lazy_static/LICENSE
toolkit/components/places/UnifiedComplete.js
toolkit/components/telemetry/Scalars.yaml
toolkit/content/tests/reftests/audio-height-with-padding-ref.html
toolkit/content/tests/reftests/audio-height-with-padding.html
--- a/accessible/tests/mochitest/common.js
+++ b/accessible/tests/mochitest/common.js
@@ -163,17 +163,23 @@ function addA11yLoadEvent(aFunc, aWindow
           return waitForDocLoad();
 
         window.setTimeout(aFunc, 0);
       },
       0
     );
   }
 
-  SimpleTest.waitForFocus(waitForDocLoad, aWindow);
+  if (aWindow &&
+      aWindow.document.activeElement &&
+      aWindow.document.activeElement.localName == "browser") {
+    waitForDocLoad();
+  } else {
+    SimpleTest.waitForFocus(waitForDocLoad, aWindow);
+  }
 }
 
 /**
  * Analogy of SimpleTest.is function used to compare objects.
  */
 function isObject(aObj, aExpectedObj, aMsg) {
   if (aObj == aExpectedObj) {
     ok(true, aMsg);
--- a/addon-sdk/source/python-lib/cuddlefish/prefs.py
+++ b/addon-sdk/source/python-lib/cuddlefish/prefs.py
@@ -50,27 +50,33 @@ DEFAULT_NO_CONNECTIONS_PREFS = {
     'media.gmp-manager.cert.requireBuiltIn' : False,
     'media.gmp-manager.url' : 'http://localhost/media-dummy/gmpmanager',
     'media.gmp-manager.url.override': 'http://localhost/dummy-gmp-manager.xml',
     'media.gmp-manager.updateEnabled': False,
     'browser.aboutHomeSnippets.updateUrl': 'https://localhost/snippet-dummy',
     'browser.newtab.url' : 'about:blank',
     'browser.search.update': False,
     'browser.search.suggest.enabled' : False,
+
+    # Disable all Safe Browsing lists
     'browser.safebrowsing.downloads.remote.url': 'http://localhost/safebrowsing-dummy/downloads',
     'browser.safebrowsing.malware.enabled' : False,
     'browser.safebrowsing.phishing.enabled' : False,
     'browser.safebrowsing.blockedURIs.enabled' : False,
     'browser.safebrowsing.passwords.enabled' : False,
     'browser.safebrowsing.provider.google.updateURL': 'http://localhost/safebrowsing-dummy/update',
     'browser.safebrowsing.provider.google.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
     'browser.safebrowsing.provider.google4.updateURL': 'http://localhost/safebrowsing4-dummy/update',
     'browser.safebrowsing.provider.google4.gethashURL': 'http://localhost/safebrowsing4-dummy/gethash',
     'browser.safebrowsing.provider.mozilla.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
     'browser.safebrowsing.provider.mozilla.updateURL': 'http://localhost/safebrowsing-dummy/update',
+    'plugins.flashBlock.enabled': False,
+    'privacy.trackingprotection.annotate_channels': False,
+    'privacy.trackingprotection.enabled': False,
+    'privacy.trackingprotection.pbmode.enabled': False,
 
     # Disable app update
     'app.update.enabled' : False,
     'app.update.staging.enabled': False,
 
     # Disable about:newtab content fetch
     'browser.newtabpage.directory.source': 'data:application/json,{"jetpack":1}',
 
--- a/addon-sdk/source/test/preferences/no-connections.json
+++ b/addon-sdk/source/test/preferences/no-connections.json
@@ -23,16 +23,20 @@
   "browser.safebrowsing.provider.google.updateURL": "http://localhost/safebrowsing-dummy/update",
   "browser.safebrowsing.provider.google.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
   "browser.safebrowsing.provider.google.reportURL": "http://localhost/safebrowsing-dummy/malwarereport",
   "browser.safebrowsing.provider.google4.updateURL": "http://localhost/safebrowsing4-dummy/update",
   "browser.safebrowsing.provider.google4.gethashURL": "http://localhost/safebrowsing4-dummy/gethash",
   "browser.safebrowsing.provider.google4.reportURL": "http://localhost/safebrowsing4-dummy/malwarereport",
   "browser.safebrowsing.provider.mozilla.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
   "browser.safebrowsing.provider.mozilla.updateURL": "http://localhost/safebrowsing-dummy/update",
+  "plugins.flashBlock.enabled": false,
+  "privacy.trackingprotection.annotate_channels": false,
+  "privacy.trackingprotection.enabled": false,
+  "privacy.trackingprotection.pbmode.enabled": false,
   "browser.newtabpage.directory.source": "data:application/json,{'jetpack':1}",
   "extensions.update.url": "http://localhost/extensions-dummy/updateURL",
   "extensions.update.background.url": "http://localhost/extensions-dummy/updateBackgroundURL",
   "extensions.blocklist.url": "http://localhost/extensions-dummy/blocklistURL",
   "extensions.webservice.discoverURL": "http://localhost/extensions-dummy/discoveryURL",
   "extensions.getAddons.maxResults": 0,
   "services.blocklist.base": "http://localhost/dummy-kinto/v1",
   "geo.wifi.uri": "http://localhost/location-dummy/locationURL",
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1277,16 +1277,18 @@ pref("browser.newtabpage.directory.sourc
 
 // activates Activity Stream
 #ifdef NIGHTLY_BUILD
 pref("browser.newtabpage.activity-stream.enabled", true);
 #else
 pref("browser.newtabpage.activity-stream.enabled", false);
 #endif
 
+pref("browser.newtabpage.activity-stream.aboutHome.enabled", false);
+
 // Enable the DOM fullscreen API.
 pref("full-screen-api.enabled", true);
 
 // Startup Crash Tracking
 // number of startup crashes that can occur before starting into safe mode automatically
 // (this pref has no effect if more than 6 hours have passed since the last crash)
 pref("toolkit.startup.max_resumed_crashes", 3);
 
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -74,23 +74,27 @@ var StarUI = {
       elt.removeAttribute("wasDisabled");
     });
   },
 
   // nsIDOMEventListener
   handleEvent(aEvent) {
     switch (aEvent.type) {
       case "animationend": {
-        let libraryButton = document.getElementById("library-button");
+        let animatableBox = document.getElementById("library-animatable-box");
         if (aEvent.animationName.startsWith("library-bookmark-animation")) {
-          libraryButton.setAttribute("fade", "true");
+          animatableBox.setAttribute("fade", "true");
         } else if (aEvent.animationName == "library-bookmark-fade") {
-          libraryButton.removeEventListener("animationend", this);
+          animatableBox.removeEventListener("animationend", this);
+          animatableBox.removeAttribute("animate");
+          animatableBox.removeAttribute("fade");
+          let libraryButton = document.getElementById("library-button");
+          // Put the 'fill' back in the normal icon.
           libraryButton.removeAttribute("animate");
-          libraryButton.removeAttribute("fade");
+          gNavToolbox.removeAttribute("animate");
         }
         break;
       }
       case "mousemove":
         clearTimeout(this._autoCloseTimer);
         // The autoclose timer is not disabled on generic mouseout
         // because the user may not have actually interacted with the popup.
         break;
@@ -141,20 +145,36 @@ var StarUI = {
             PlacesTransactions.Remove(guidsForRemoval)
                               .transact().catch(Cu.reportError);
           } else if (this._isNewBookmark &&
                      Services.prefs.getBoolPref("toolkit.cosmeticAnimations.enabled") &&
                      (libraryButton = document.getElementById("library-button")) &&
                      libraryButton.getAttribute("cui-areatype") != "menu-panel" &&
                      libraryButton.getAttribute("overflowedItem") != "true" &&
                      libraryButton.closest("#nav-bar")) {
-            BrowserUtils.setToolbarButtonHeightProperty(libraryButton);
-            libraryButton.removeAttribute("fade");
+            let animatableBox = document.getElementById("library-animatable-box");
+            let navBar = document.getElementById("nav-bar");
+            let libraryIcon = document.getAnonymousElementByAttribute(libraryButton, "class", "toolbarbutton-icon");
+            let dwu = window.getInterface(Ci.nsIDOMWindowUtils);
+            let iconBounds = dwu.getBoundsWithoutFlushing(libraryIcon);
+            let libraryBounds = dwu.getBoundsWithoutFlushing(libraryButton);
+
+            animatableBox.style.setProperty("--library-button-y", libraryBounds.y + "px");
+            animatableBox.style.setProperty("--library-button-height", libraryBounds.height + "px");
+            animatableBox.style.setProperty("--library-icon-x", iconBounds.x + "px");
+            if (navBar.hasAttribute("brighttext")) {
+              animatableBox.setAttribute("brighttext", "true");
+            } else {
+              animatableBox.removeAttribute("brighttext");
+            }
+            animatableBox.removeAttribute("fade");
+            gNavToolbox.setAttribute("animate", "bookmark");
             libraryButton.setAttribute("animate", "bookmark");
-            libraryButton.addEventListener("animationend", this);
+            animatableBox.setAttribute("animate", "bookmark");
+            animatableBox.addEventListener("animationend", this);
           }
         }
         break;
       }
       case "keypress":
         clearTimeout(this._autoCloseTimer);
         this._autoCloseTimerEnabled = false;
 
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -661,16 +661,17 @@ html|input.urlbar-input[textoverflow]:no
 }
 
 #DateTimePickerPanel[active="true"] {
   -moz-binding: url("chrome://global/content/bindings/datetimepopup.xml#datetime-popup");
 }
 
 #urlbar[pageproxystate="invalid"] > #page-action-buttons > .urlbar-icon,
 #urlbar[pageproxystate="invalid"] > #page-action-buttons > .urlbar-icon-wrapper > .urlbar-icon,
+#urlbar[pageproxystate="invalid"] > #page-action-buttons > #pageActionSeparator,
 .urlbar-go-button[pageproxystate="valid"],
 .urlbar-go-button:not([parentfocused="true"]),
 #urlbar[pageproxystate="invalid"] > #identity-box > #blocked-permissions-container,
 #urlbar[pageproxystate="invalid"] > #identity-box > #notification-popup-box,
 #urlbar[pageproxystate="invalid"] > #identity-box > #identity-icon-labels {
   visibility: collapse;
 }
 
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1374,47 +1374,47 @@ var gBrowserInit = {
         document.documentElement.setAttribute("darkwindowframe", "true");
       }
     }
 
     ToolbarIconColor.init();
 
     gRemoteControl.updateVisualCue(Marionette.running);
 
-    let uriToLoad = this._getUriToLoad();
-    gIdentityHandler.initIdentityBlock(uriToLoad);
+    this._uriToLoadPromise.then(uriToLoad => {
+      gIdentityHandler.initIdentityBlock(uriToLoad);
+    });
 
     // Wait until chrome is painted before executing code not critical to making the window visible
-    this._boundDelayedStartup = this._delayedStartup.bind(this, uriToLoad);
+    this._boundDelayedStartup = this._delayedStartup.bind(this);
     window.addEventListener("MozAfterPaint", this._boundDelayedStartup);
 
     this._loadHandled = true;
   },
 
   _cancelDelayedStartup() {
     window.removeEventListener("MozAfterPaint", this._boundDelayedStartup);
     this._boundDelayedStartup = null;
   },
 
-  _delayedStartup(uriToLoad) {
+  _delayedStartup() {
     let tmp = {};
     Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", tmp);
     let TelemetryTimestamps = tmp.TelemetryTimestamps;
     TelemetryTimestamps.add("delayedStartupStarted");
 
     this._cancelDelayedStartup();
 
     // We need to set the OfflineApps message listeners up before we
     // load homepages, which might need them.
     OfflineApps.init();
 
     // This pageshow listener needs to be registered before we may call
     // swapBrowsersAndCloseOther() to receive pageshow events fired by that.
-    let mm = window.messageManager;
-    mm.addMessageListener("PageVisibility:Show", function(message) {
+    window.messageManager.addMessageListener("PageVisibility:Show", function(message) {
       if (message.target == gBrowser.selectedBrowser) {
         setTimeout(pageShowEventHandlers, 0, message.data.persisted);
       }
     });
 
     gBrowser.addEventListener("AboutTabCrashedLoad", function(event) {
       let ownerDoc = event.originalTarget;
 
@@ -1435,99 +1435,17 @@ var gBrowserInit = {
       gIdentityHandler.refreshIdentityBlock();
     });
 
     // Get the service so that it initializes and registers listeners for new
     // tab pages in order to be ready for any early-loading about:newtab pages,
     // e.g., start/home page, command line / startup uris to load, sessionstore
     gAboutNewTabService.QueryInterface(Ci.nsISupports);
 
-    if (uriToLoad && uriToLoad != "about:blank") {
-      if (uriToLoad instanceof Ci.nsIArray) {
-        let count = uriToLoad.length;
-        let specs = [];
-        for (let i = 0; i < count; i++) {
-          let urisstring = uriToLoad.queryElementAt(i, Ci.nsISupportsString);
-          specs.push(urisstring.data);
-        }
-
-        // This function throws for certain malformed URIs, so use exception handling
-        // so that we don't disrupt startup
-        try {
-          gBrowser.loadTabs(specs, {
-            inBackground: false,
-            replace: true,
-            triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
-          });
-        } catch (e) {}
-      } else if (uriToLoad instanceof XULElement) {
-        // swap the given tab with the default about:blank tab and then close
-        // the original tab in the other window.
-        let tabToOpen = uriToLoad;
-
-        // If this tab was passed as a window argument, clear the
-        // reference to it from the arguments array.
-        if (window.arguments[0] == tabToOpen) {
-          window.arguments[0] = null;
-        }
-
-        // Stop the about:blank load
-        gBrowser.stop();
-        // make sure it has a docshell
-        gBrowser.docShell;
-
-        // We must set usercontextid before updateBrowserRemoteness()
-        // so that the newly created remote tab child has correct usercontextid
-        if (tabToOpen.hasAttribute("usercontextid")) {
-          let usercontextid = tabToOpen.getAttribute("usercontextid");
-          gBrowser.selectedBrowser.setAttribute("usercontextid", usercontextid);
-        }
-
-        try {
-          // Make sure selectedBrowser has the same remote settings as the one
-          // we are swapping in.
-          gBrowser.updateBrowserRemoteness(gBrowser.selectedBrowser,
-                                           tabToOpen.linkedBrowser.isRemoteBrowser,
-                                           { remoteType: tabToOpen.linkedBrowser.remoteType });
-          gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, tabToOpen);
-        } catch (e) {
-          Cu.reportError(e);
-        }
-      } else if (window.arguments.length >= 3) {
-        // window.arguments[2]: referrer (nsIURI | string)
-        //                 [3]: postData (nsIInputStream)
-        //                 [4]: allowThirdPartyFixup (bool)
-        //                 [5]: referrerPolicy (int)
-        //                 [6]: userContextId (int)
-        //                 [7]: originPrincipal (nsIPrincipal)
-        //                 [8]: triggeringPrincipal (nsIPrincipal)
-        let referrerURI = window.arguments[2];
-        if (typeof(referrerURI) == "string") {
-          try {
-            referrerURI = makeURI(referrerURI);
-          } catch (e) {
-            referrerURI = null;
-          }
-        }
-        let referrerPolicy = (window.arguments[5] != undefined ?
-            window.arguments[5] : Ci.nsIHttpChannel.REFERRER_POLICY_UNSET);
-        let userContextId = (window.arguments[6] != undefined ?
-            window.arguments[6] : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID);
-        loadURI(uriToLoad, referrerURI, window.arguments[3] || null,
-                window.arguments[4] || false, referrerPolicy, userContextId,
-                // pass the origin principal (if any) and force its use to create
-                // an initial about:blank viewer if present:
-                window.arguments[7], !!window.arguments[7], window.arguments[8]);
-        window.focus();
-      } else {
-        // Note: loadOneOrMoreURIs *must not* be called if window.arguments.length >= 3.
-        // Such callers expect that window.arguments[0] is handled as a single URI.
-        loadOneOrMoreURIs(uriToLoad, Services.scriptSecurityManager.getSystemPrincipal());
-      }
-    }
+    this._handleURIToLoad();
 
     Services.obs.addObserver(gIdentityHandler, "perm-changed");
     Services.obs.addObserver(gRemoteControl, "remote-active");
     Services.obs.addObserver(gSessionHistoryObserver, "browser:purge-session-history");
     Services.obs.addObserver(gStoragePressureObserver, "QuotaManager::StoragePressure");
     Services.obs.addObserver(gXPInstallObserver, "addon-install-disabled");
     Services.obs.addObserver(gXPInstallObserver, "addon-install-started");
     Services.obs.addObserver(gXPInstallObserver, "addon-install-blocked");
@@ -1546,37 +1464,16 @@ var gBrowserInit = {
     // 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();
 
     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.
-        let focusedElement = document.commandDispatcher.focusedElement;
-        mm.addMessageListener("Browser:FirstPaint", function onFirstPaint() {
-          mm.removeMessageListener("Browser:FirstPaint", onFirstPaint);
-          // If focus didn't move while we were waiting for first paint, we're okay
-          // to move to the browser.
-          if (document.commandDispatcher.focusedElement == focusedElement) {
-            gBrowser.selectedBrowser.focus();
-          }
-        });
-      } else {
-        // If the initial browser is not remote, we can focus the browser
-        // immediately with no paint performance impact.
-        gBrowser.selectedBrowser.focus();
-      }
-    }
-
     // Enable/Disable auto-hide tabbar
     gBrowser.tabContainer.updateVisibility();
 
     BookmarkingUI.init();
     AutoShowBookmarksToolbar.init();
 
     gPrefService.addObserver(gHomeButton.prefDomain, gHomeButton);
 
@@ -1688,16 +1585,142 @@ var gBrowserInit = {
       this._schedulePerWindowIdleTasks();
       document.documentElement.setAttribute("sessionrestored", "true");
     });
 
     Services.obs.notifyObservers(window, "browser-delayed-startup-finished");
     TelemetryTimestamps.add("delayedStartupFinished");
   },
 
+  _handleURIToLoad() {
+    let initiallyFocusedElement = document.commandDispatcher.focusedElement;
+
+    let firstBrowserPaintDeferred = {};
+    firstBrowserPaintDeferred.promise = new Promise(resolve => {
+      firstBrowserPaintDeferred.resolve = resolve;
+    });
+
+    let mm = window.messageManager;
+    mm.addMessageListener("Browser:FirstPaint", function onFirstPaint() {
+      mm.removeMessageListener("Browser:FirstPaint", onFirstPaint);
+      firstBrowserPaintDeferred.resolve();
+    });
+
+    this._uriToLoadPromise.then(uriToLoad => {
+      if (!uriToLoad || uriToLoad == "about:blank") {
+        return;
+      }
+
+      if (uriToLoad instanceof Ci.nsIArray) {
+        let count = uriToLoad.length;
+        let specs = [];
+        for (let i = 0; i < count; i++) {
+          let urisstring = uriToLoad.queryElementAt(i, Ci.nsISupportsString);
+          specs.push(urisstring.data);
+        }
+
+        // This function throws for certain malformed URIs, so use exception handling
+        // so that we don't disrupt startup
+        try {
+          gBrowser.loadTabs(specs, {
+            inBackground: false,
+            replace: true,
+            triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+          });
+        } catch (e) {}
+      } else if (uriToLoad instanceof XULElement) {
+        // swap the given tab with the default about:blank tab and then close
+        // the original tab in the other window.
+        let tabToOpen = uriToLoad;
+
+        // If this tab was passed as a window argument, clear the
+        // reference to it from the arguments array.
+        if (window.arguments[0] == tabToOpen) {
+          window.arguments[0] = null;
+        }
+
+        // Stop the about:blank load
+        gBrowser.stop();
+        // make sure it has a docshell
+        gBrowser.docShell;
+
+        // We must set usercontextid before updateBrowserRemoteness()
+        // so that the newly created remote tab child has correct usercontextid
+        if (tabToOpen.hasAttribute("usercontextid")) {
+          let usercontextid = tabToOpen.getAttribute("usercontextid");
+          gBrowser.selectedBrowser.setAttribute("usercontextid", usercontextid);
+        }
+
+        try {
+          // Make sure selectedBrowser has the same remote settings as the one
+          // we are swapping in.
+          gBrowser.updateBrowserRemoteness(gBrowser.selectedBrowser,
+                                           tabToOpen.linkedBrowser.isRemoteBrowser,
+                                           { remoteType: tabToOpen.linkedBrowser.remoteType });
+          gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, tabToOpen);
+        } catch (e) {
+          Cu.reportError(e);
+        }
+      } else if (window.arguments.length >= 3) {
+        // window.arguments[2]: referrer (nsIURI | string)
+        //                 [3]: postData (nsIInputStream)
+        //                 [4]: allowThirdPartyFixup (bool)
+        //                 [5]: referrerPolicy (int)
+        //                 [6]: userContextId (int)
+        //                 [7]: originPrincipal (nsIPrincipal)
+        //                 [8]: triggeringPrincipal (nsIPrincipal)
+        let referrerURI = window.arguments[2];
+        if (typeof(referrerURI) == "string") {
+          try {
+            referrerURI = makeURI(referrerURI);
+          } catch (e) {
+            referrerURI = null;
+          }
+        }
+        let referrerPolicy = (window.arguments[5] != undefined ?
+            window.arguments[5] : Ci.nsIHttpChannel.REFERRER_POLICY_UNSET);
+        let userContextId = (window.arguments[6] != undefined ?
+            window.arguments[6] : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID);
+        loadURI(uriToLoad, referrerURI, window.arguments[3] || null,
+                window.arguments[4] || false, referrerPolicy, userContextId,
+                // pass the origin principal (if any) and force its use to create
+                // an initial about:blank viewer if present:
+                window.arguments[7], !!window.arguments[7], window.arguments[8]);
+        window.focus();
+      } else {
+        // Note: loadOneOrMoreURIs *must not* be called if window.arguments.length >= 3.
+        // Such callers expect that window.arguments[0] is handled as a single URI.
+        loadOneOrMoreURIs(uriToLoad, Services.scriptSecurityManager.getSystemPrincipal());
+      }
+    });
+
+    this._uriToLoadPromise.then(uriToLoad => {
+      if ((isBlankPageURL(uriToLoad) || uriToLoad == "about:privatebrowsing") &&
+          focusAndSelectUrlBar()) {
+        return;
+      }
+
+      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.
+        firstBrowserPaintDeferred.promise.then(() => {
+          // If focus didn't move while we were waiting for first paint, we're okay
+          // to move to the browser.
+          if (document.commandDispatcher.focusedElement == initiallyFocusedElement) {
+            gBrowser.selectedBrowser.focus();
+          }
+        });
+      } else {
+        // If the initial browser is not remote, we can focus the browser
+        // immediately with no paint performance impact.
+        gBrowser.selectedBrowser.focus();
+      }
+    });
+  },
+
   /**
    * Use this function as an entry point to schedule tasks that
    * need to run once per window after startup, and can be scheduled
    * by using an idle callback.
    *
    * The functions scheduled here will fire from idle callbacks
    * once every window has finished being restored by session
    * restore, and after the equivalent only-once tasks
@@ -1750,38 +1773,48 @@ var gBrowserInit = {
           .DownloadsTaskbar.registerIndicator(window);
       } catch (ex) {
         Cu.reportError(ex);
       }
     }, {timeout: 10000});
   },
 
   // Returns the URI(s) to load at startup.
-  _getUriToLoad() {
-    // window.arguments[0]: URI to load (string), or an nsIArray of
-    //                      nsISupportsStrings to load, or a xul:tab of
-    //                      a tabbrowser, which will be replaced by this
-    //                      window (for this case, all other arguments are
-    //                      ignored).
-    if (!window.arguments || !window.arguments[0])
-      return null;
-
-    let uri = window.arguments[0];
-    let sessionStartup = Cc["@mozilla.org/browser/sessionstartup;1"]
-                           .getService(Ci.nsISessionStartup);
-    let defaultArgs = Cc["@mozilla.org/browser/clh;1"]
-                        .getService(Ci.nsIBrowserHandler)
-                        .defaultArgs;
-
-    // If the given URI matches defaultArgs (the default homepage) we want
-    // to block its load if we're going to restore a session anyway.
-    if (uri == defaultArgs && sessionStartup.willOverrideHomepage)
-      return null;
-
-    return uri;
+  get _uriToLoadPromise() {
+    delete this._uriToLoadPromise;
+    return this._uriToLoadPromise = new Promise(resolve => {
+      // window.arguments[0]: URI to load (string), or an nsIArray of
+      //                      nsISupportsStrings to load, or a xul:tab of
+      //                      a tabbrowser, which will be replaced by this
+      //                      window (for this case, all other arguments are
+      //                      ignored).
+      if (!window.arguments || !window.arguments[0]) {
+        resolve(null);
+        return;
+      }
+
+      let uri = window.arguments[0];
+      let defaultArgs = Cc["@mozilla.org/browser/clh;1"]
+                          .getService(Ci.nsIBrowserHandler)
+                          .defaultArgs;
+
+      // If the given URI is different from the homepage, we want to load it.
+      if (uri != defaultArgs) {
+        resolve(uri);
+        return;
+      }
+
+      // The URI appears to be the the homepage. We want to load it only if
+      // session restore isn't about to override the homepage.
+      let sessionStartup = Cc["@mozilla.org/browser/sessionstartup;1"]
+                             .getService(Ci.nsISessionStartup);
+      sessionStartup.willOverrideHomepagePromise.then(willOverrideHomepage => {
+        resolve(willOverrideHomepage ? null : uri);
+      });
+    });
   },
 
   onUnload() {
     // In certain scenarios it's possible for unload to be fired before onload,
     // (e.g. if the window is being closed after browser.js loads but before the
     // load completes). In that case, there's nothing to do here.
     if (!this._loadHandled)
       return;
@@ -7721,16 +7754,21 @@ var gIdentityHandler = {
   },
 
   /**
    * Used to initialize the identity block before first paint to avoid
    * flickering when opening a new window showing a secure internal page
    * (eg. about:home)
    */
   initIdentityBlock(initialURI) {
+    if (this._uri) {
+      // Apparently we already loaded something, so there's nothing to do here.
+      return;
+    }
+
     if ((typeof initialURI != "string") || !initialURI.startsWith("about:"))
       return;
 
     let uri = Services.io.newURI(initialURI);
     if (this._secureInternalUIWhitelist.test(uri.pathQueryRef)) {
       this._isSecureInternalUI = true;
       this._ignoreAboutBlankUntilFirstLoad = true;
       this.refreshIdentityBlock();
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -851,29 +851,34 @@
                   <label id="identity-icon-country-label" class="plain"/>
                 </hbox>
               </box>
               <box id="urlbar-display-box" align="center">
                 <label id="switchtab" class="urlbar-display urlbar-display-switchtab" value="&urlbar.switchToTab.label;"/>
                 <label id="extension" class="urlbar-display urlbar-display-extension" value="&urlbar.extension.label;"/>
               </box>
               <hbox id="page-action-buttons">
+                <hbox id="userContext-icons" hidden="true">
+                  <label id="userContext-label"/>
+                  <image id="userContext-indicator"/>
+                </hbox>
                 <image id="page-report-button"
                        class="urlbar-icon"
                        hidden="true"
                        tooltiptext="&pageReportIcon.tooltip;"
                        onmousedown="gPopupBlockerObserver.onReportButtonMousedown(event);"/>
                 <image id="reader-mode-button"
                        class="urlbar-icon"
                        hidden="true"
                        onclick="ReaderParent.buttonClick(event);"/>
                 <toolbarbutton id="urlbar-zoom-button"
                        onclick="FullZoom.reset();"
                        tooltip="dynamic-shortcut-tooltip"
                        hidden="true"/>
+                <box id="pageActionSeparator"/>
                 <image id="pageActionButton"
                        class="urlbar-icon"
                        tooltiptext="&pageActionButton.tooltip;"
                        onclick="BrowserPageActions.mainButtonClicked(event);"/>
                 <hbox id="star-button-box"
                       hidden="true"
                       class="urlbar-icon-wrapper"
                       context="pageActionPanelContextMenu"
@@ -883,20 +888,16 @@
                          class="urlbar-icon"
                          observes="bookmarkThisPageBroadcaster"/>
                   <hbox id="star-button-animatable-box">
                     <image id="star-button-animatable-image"
                            observes="bookmarkThisPageBroadcaster"/>
                   </hbox>
                 </hbox>
               </hbox>
-              <hbox id="userContext-icons" hidden="true">
-                <label id="userContext-label"/>
-                <image id="userContext-indicator"/>
-              </hbox>
             </textbox>
         </toolbaritem>
 
         <toolbaritem id="search-container" title="&searchItem.title;"
                      align="center" class="chromeclass-toolbar-additional panel-wide-item"
                      cui-areatype="toolbar"
                      flex="100" persist="width" removable="true">
           <searchbar id="searchbar" flex="1"/>
@@ -919,21 +920,17 @@
                        tooltip="dynamic-shortcut-tooltip"/>
 
         <toolbarbutton id="library-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
                        removable="true"
                        oncommand="PanelUI.showSubView('appMenu-libraryView', this, null, event);"
                        closemenu="none"
                        cui-areatype="toolbar"
                        tooltiptext="&libraryButton.tooltip;"
-                       label="&places.library.title;">
-          <box class="toolbarbutton-animatable-box">
-            <image class="toolbarbutton-animatable-image"/>
-          </box>
-        </toolbarbutton>
+                       label="&places.library.title;"/>
 
       </hbox>
 
       <toolbarbutton id="nav-bar-overflow-button"
                      class="toolbarbutton-1 chromeclass-toolbar-additional overflow-button"
                      skipintoolbarset="true"
                      tooltiptext="&navbarOverflow.label;">
         <box class="toolbarbutton-animatable-box">
@@ -1147,16 +1144,19 @@
           <menuitem id="BMB_bookmarksShowAll"
                     class="subviewbutton panel-subview-footer"
                     label="&showAllBookmarks2.label;"
                     command="Browser:ShowAllBookmarks"
                     key="manBookmarkKb"/>
         </menupopup>
       </toolbarbutton>
     </toolbarpalette>
+    <box id="library-animatable-box" class="toolbarbutton-animatable-box">
+      <image class="toolbarbutton-animatable-image"/>
+    </box>
   </toolbox>
 
   <hbox id="fullscr-toggler" hidden="true"/>
 
   <deck id="content-deck" flex="1">
     <hbox flex="1" id="browser">
       <vbox id="browser-border-start" hidden="true" layer="true"/>
       <vbox id="sidebar-box" hidden="true" class="chromeclass-extrachrome">
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -720,16 +720,25 @@
                     this.mTabBrowser.mIsBusy = true;
                   }
                 }
               } else if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
                          aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
 
                 if (this.mTab.hasAttribute("busy")) {
                   this.mTab.removeAttribute("busy");
+
+                  // Only animate the "burst" indicating the page has loaded if
+                  // the top-level page is the one that finished loading.
+                  if (aWebProgress.isTopLevel && !aWebProgress.isLoadingDocument &&
+                      !this.mTabBrowser.tabAnimationsInProgress &&
+                      Services.prefs.getBoolPref("toolkit.cosmeticAnimations.enabled")) {
+                    this.mTab.setAttribute("bursting", "true");
+                  }
+
                   this.mTabBrowser._tabAttrModified(this.mTab, ["busy"]);
                   if (!this.mTab.selected)
                     this.mTab.setAttribute("unread", "true");
                 }
                 this.mTab.removeAttribute("progress");
 
                 if (aWebProgress.isTopLevel) {
                   let isSuccessful = Components.isSuccessCode(aStatus);
@@ -2902,16 +2911,17 @@
               return;
             }
 
             // We're animating, so we can cancel the non-animation stopwatch.
             TelemetryStopwatch.cancel("FX_TAB_CLOSE_TIME_NO_ANIM_MS", aTab);
 
             aTab.style.maxWidth = ""; // ensure that fade-out transition happens
             aTab.removeAttribute("fadein");
+            aTab.removeAttribute("bursting");
 
             setTimeout(function(tab, tabbrowser) {
               if (tab.parentNode &&
                   window.getComputedStyle(tab).maxWidth == "0.1px") {
                 NS_ASSERT(false, "Giving up waiting for the tab closing animation to finish (bug 608589)");
                 tabbrowser._endRemoveTab(tab);
               }
             }, 3000, aTab, this);
@@ -7350,16 +7360,19 @@
       <xul:stack class="tab-stack" flex="1">
         <xul:vbox xbl:inherits="selected=visuallyselected,fadein"
                   class="tab-background">
           <xul:hbox xbl:inherits="selected=visuallyselected"
                     class="tab-line"/>
           <xul:spacer flex="1"/>
           <xul:hbox class="tab-bottom-line"/>
         </xul:vbox>
+        <xul:hbox xbl:inherits="pinned,bursting"
+                  anonid="tab-loading-burst"
+                  class="tab-loading-burst"/>
         <xul:hbox xbl:inherits="pinned,selected=visuallyselected,titlechanged,attention"
                   class="tab-content" align="center">
           <xul:hbox xbl:inherits="fadein,pinned,busy,progress,selected=visuallyselected"
                     class="tab-throbber"
                     layer="true"/>
           <xul:image xbl:inherits="src=image,loadingprincipal=iconLoadingPrincipal,fadein,pinned,selected=visuallyselected,busy,crashed,sharing"
                      anonid="tab-icon-image"
                      class="tab-icon-image"
@@ -7743,16 +7756,24 @@
           return;
         }
 
         if (this._overPlayingIcon) {
           this.toggleMuteAudio();
         }
       ]]>
       </handler>
+      <handler event="animationend">
+      <![CDATA[
+        let anonid = event.originalTarget.getAttribute("anonid");
+        if (anonid == "tab-loading-burst") {
+          this.removeAttribute("bursting");
+        }
+      ]]>
+      </handler>
     </handlers>
   </binding>
 
   <binding id="tabbrowser-alltabs-popup"
            extends="chrome://global/content/bindings/popup.xml#popup">
     <implementation implements="nsIDOMEventListener">
       <method name="_tabOnAttrModified">
         <parameter name="aEvent"/>
--- a/browser/base/content/test/performance/browser.ini
+++ b/browser/base/content/test/performance/browser.ini
@@ -10,13 +10,15 @@ skip-if = !e10s
 skip-if = !debug
 [browser_tabclose_grow_reflows.js]
 [browser_tabclose_reflows.js]
 [browser_tabopen_reflows.js]
 [browser_tabopen_squeeze_reflows.js]
 [browser_tabstrip_overflow_underflow_reflows.js]
 [browser_tabswitch_reflows.js]
 [browser_toolbariconcolor_restyles.js]
+[browser_urlbar_keyed_search_reflows.js]
+skip-if = (os == 'linux') || (os == 'win' && debug) # Disabled on Linux and Windows debug due to perma failures. Bug 1392320.
 [browser_urlbar_search_reflows.js]
 skip-if = (os == 'linux') || (os == 'mac' && !debug) # Disabled on Linux and OS X opt due to frequent failures. Bug 1385932 and Bug 1384582
 [browser_windowclose_reflows.js]
 [browser_windowopen_reflows.js]
 skip-if = os == 'linux' # Disabled due to frequent failures. Bug 1380465.
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/performance/browser_urlbar_keyed_search_reflows.js
@@ -0,0 +1,183 @@
+"use strict";
+
+// There are a _lot_ of reflows in this test, and processing them takes
+// time. On slower builds, we need to boost our allowed test time.
+requestLongerTimeout(5);
+
+/**
+ * WHOA THERE: We should never be adding new things to EXPECTED_REFLOWS. This
+ * is a whitelist that should slowly go away as we improve the performance of
+ * the front-end. Instead of adding more reflows to the whitelist, you should
+ * be modifying your code to avoid the reflow.
+ *
+ * See https://developer.mozilla.org/en-US/Firefox/Performance_best_practices_for_Firefox_fe_engineers
+ * for tips on how to do that.
+ */
+
+/* These reflows happen only the first time the awesomebar panel opens. */
+const EXPECTED_REFLOWS_FIRST_OPEN = [
+  // Bug 1357054
+  {
+    stack: [
+      "_rebuild@chrome://browser/content/search/search.xml",
+      "set_popup@chrome://browser/content/search/search.xml",
+      "enableOneOffSearches@chrome://browser/content/urlbarBindings.xml",
+      "_enableOrDisableOneOffSearches@chrome://browser/content/urlbarBindings.xml",
+      "urlbar_XBL_Constructor/<@chrome://browser/content/urlbarBindings.xml",
+      "openPopup@chrome://global/content/bindings/popup.xml",
+      "_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
+      "openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
+      "openPopup@chrome://global/content/bindings/autocomplete.xml",
+      "set_popupOpen@chrome://global/content/bindings/autocomplete.xml"
+    ],
+    times: 1, // This number should only ever go down - never up.
+  },
+
+  {
+    stack: [
+      "adjustHeight@chrome://global/content/bindings/autocomplete.xml",
+      "onxblpopupshown@chrome://global/content/bindings/autocomplete.xml"
+    ],
+    times: 5, // This number should only ever go down - never up.
+  },
+
+  {
+    stack: [
+      "_handleOverflow@chrome://global/content/bindings/autocomplete.xml",
+      "handleOverUnderflow@chrome://global/content/bindings/autocomplete.xml",
+      "set_siteIconStart@chrome://global/content/bindings/autocomplete.xml",
+    ],
+    times: 6, // This number should only ever go down - never up.
+  },
+
+  {
+    stack: [
+      "adjustHeight@chrome://global/content/bindings/autocomplete.xml",
+      "_invalidate/this._adjustHeightTimeout<@chrome://global/content/bindings/autocomplete.xml",
+    ],
+    times: 36, // This number should only ever go down - never up.
+  },
+
+  {
+    stack: [
+      "_handleOverflow@chrome://global/content/bindings/autocomplete.xml",
+      "handleOverUnderflow@chrome://global/content/bindings/autocomplete.xml",
+      "_reuseAcItem@chrome://global/content/bindings/autocomplete.xml",
+      "_appendCurrentResult@chrome://global/content/bindings/autocomplete.xml",
+      "_invalidate@chrome://global/content/bindings/autocomplete.xml",
+      "invalidate@chrome://global/content/bindings/autocomplete.xml"
+    ],
+    times: 1584, // This number should only ever go down - never up.
+  },
+
+  {
+    stack: [
+      "_handleOverflow@chrome://global/content/bindings/autocomplete.xml",
+      "handleOverUnderflow@chrome://global/content/bindings/autocomplete.xml",
+      "_onChanged@chrome://global/content/bindings/autocomplete.xml",
+      "_appendCurrentResult/<@chrome://global/content/bindings/autocomplete.xml",
+    ],
+    times: 6,
+  },
+
+  {
+    stack: [
+      "_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
+      "openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
+      "openPopup@chrome://global/content/bindings/autocomplete.xml",
+      "set_popupOpen@chrome://global/content/bindings/autocomplete.xml",
+    ],
+    times: 3, // This number should only ever go down - never up.
+  },
+
+  // Bug 1359989
+  {
+    stack: [
+      "openPopup@chrome://global/content/bindings/popup.xml",
+      "_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
+      "openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
+      "openPopup@chrome://global/content/bindings/autocomplete.xml",
+      "set_popupOpen@chrome://global/content/bindings/autocomplete.xml",
+    ],
+  },
+];
+
+/**
+ * Returns a Promise that resolves once the AwesomeBar popup for a particular
+ * window has appeared after having done a search for its input text.
+ *
+ * @param win (browser window)
+ *        The window to do the search in.
+ * @returns Promise
+ */
+async function promiseSearchComplete(win) {
+  let URLBar = win.gURLBar;
+  if (URLBar.popup.state != "open") {
+    await BrowserTestUtils.waitForEvent(URLBar.popup, "popupshown");
+  }
+  await BrowserTestUtils.waitForCondition(() => {
+    return URLBar.controller.searchStatus >=
+      Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH;
+  });
+
+  // There are several setTimeout(fn, 0); calls inside autocomplete.xml
+  // that we need to wait for. Since those have higher priority than
+  // idle callbacks, we can be sure they will have run once this
+  // idle callback is called. The timeout seems to be required in
+  // automation - presumably because the machines can be pretty busy
+  // especially if it's GC'ing from previous tests.
+  await new Promise(resolve => win.requestIdleCallback(resolve, { timeout: 1000 }));
+}
+
+add_task(async function setup() {
+  await addDummyHistoryEntries();
+});
+
+/**
+ * This test ensures that there are no unexpected uninterruptible reflows when
+ * typing into the URL bar with the default values in Places.
+ */
+add_task(async function() {
+  let win = await BrowserTestUtils.openNewBrowserWindow();
+  await ensureNoPreloadedBrowser(win);
+
+  let URLBar = win.gURLBar;
+  let popup = URLBar.popup;
+
+  URLBar.focus();
+  URLBar.value = "";
+
+  await withReflowObserver(async function(dirtyFrameFn) {
+    let oldInvalidate = popup.invalidate.bind(popup);
+    let oldResultsAdded = popup.onResultsAdded.bind(popup);
+
+    // We need to invalidate the frame tree outside of the normal
+    // mechanism since invalidations and result additions to the
+    // URL bar occur without firing JS events (which is how we
+    // normally know to dirty the frame tree).
+    popup.invalidate = (reason) => {
+      dirtyFrameFn();
+      oldInvalidate(reason);
+    };
+
+    popup.onResultsAdded = () => {
+      dirtyFrameFn();
+      oldResultsAdded();
+    };
+
+    // Only keying in 6 characters because the number of reflows triggered
+    // is so high that we risk timing out the test if we key in any more.
+    const SEARCH_TERM = "ows-10";
+    for (let i = 0; i < SEARCH_TERM.length; ++i) {
+      let char = SEARCH_TERM[i];
+      EventUtils.synthesizeKey(char, {}, win);
+      await promiseSearchComplete(win);
+    }
+
+    let hiddenPromise = BrowserTestUtils.waitForEvent(URLBar.popup, "popuphidden");
+    EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
+    await hiddenPromise;
+  }, EXPECTED_REFLOWS_FIRST_OPEN, win);
+
+  await BrowserTestUtils.closeWindow(win);
+});
--- a/browser/base/content/test/performance/browser_urlbar_search_reflows.js
+++ b/browser/base/content/test/performance/browser_urlbar_search_reflows.js
@@ -1,17 +1,14 @@
 "use strict";
 
 // There are a _lot_ of reflows in this test, and processing them takes
 // time. On slower builds, we need to boost our allowed test time.
 requestLongerTimeout(5);
 
-XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
-                                  "resource://testing-common/PlacesTestUtils.jsm");
-
 /**
  * WHOA THERE: We should never be adding new things to EXPECTED_REFLOWS. This
  * is a whitelist that should slowly go away as we improve the performance of
  * the front-end. Instead of adding more reflows to the whitelist, you should
  * be modifying your code to avoid the reflow.
  *
  * See https://developer.mozilla.org/en-US/Firefox/Performance_best_practices_for_Firefox_fe_engineers
  * for tips on how to do that.
@@ -143,69 +140,20 @@ const EXPECTED_REFLOWS_SECOND_OPEN = [
       "_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
       "openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
       "openPopup@chrome://global/content/bindings/autocomplete.xml",
       "set_popupOpen@chrome://global/content/bindings/autocomplete.xml",
     ],
   },
 ];
 
-/**
- * Returns a Promise that resolves once the AwesomeBar popup for a particular
- * window has appeared after having done a search for its input text.
- *
- * @param win (browser window)
- *        The window to do the search in.
- * @returns Promise
- */
-async function promiseAutocompleteResultPopup(win) {
-  let URLBar = win.gURLBar;
-  URLBar.controller.startSearch(URLBar.value);
-  await BrowserTestUtils.waitForEvent(URLBar.popup, "popupshown");
-  await BrowserTestUtils.waitForCondition(() => {
-    return URLBar.controller.searchStatus >=
-      Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH;
-  });
-  let matchCount = URLBar.popup._matchCount;
-  await BrowserTestUtils.waitForCondition(() => {
-    return URLBar.popup.richlistbox.childNodes.length == matchCount;
-  });
-
-  URLBar.controller.stopSearch();
-  // There are several setTimeout(fn, 0); calls inside autocomplete.xml
-  // that we need to wait for. Since those have higher priority than
-  // idle callbacks, we can be sure they will have run once this
-  // idle callback is called. The timeout seems to be required in
-  // automation - presumably because the machines can be pretty busy
-  // especially if it's GC'ing from previous tests.
-  await new Promise(resolve => win.requestIdleCallback(resolve, { timeout: 1000 }));
-
-  let hiddenPromise = BrowserTestUtils.waitForEvent(URLBar.popup, "popuphidden");
-  EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
-  await hiddenPromise;
-}
-
 const SEARCH_TERM = "urlbar-reflows";
 
 add_task(async function setup() {
-  const NUM_VISITS = 10;
-  let visits = [];
-
-  for (let i = 0; i < NUM_VISITS; ++i) {
-    visits.push({
-      uri: `http://example.com/urlbar-reflows-${i}`,
-      title: `Reflow test for URL bar entry #${i}`,
-    });
-  }
-
-  await PlacesTestUtils.addVisits(visits);
-
-  registerCleanupFunction(async function() {
-    await PlacesTestUtils.clearHistory();
-  });
+  await addDummyHistoryEntries();
 });
 
 /**
  * This test ensures that there are no unexpected
  * uninterruptible reflows when typing into the URL bar
  * with the default values in Places.
  */
 add_task(async function() {
@@ -230,17 +178,39 @@ add_task(async function() {
       oldInvalidate(reason);
     };
 
     popup.onResultsAdded = () => {
       dirtyFrameFn();
       oldResultsAdded();
     };
 
-    await promiseAutocompleteResultPopup(win);
+    URLBar.controller.startSearch(URLBar.value);
+    await BrowserTestUtils.waitForEvent(URLBar.popup, "popupshown");
+    await BrowserTestUtils.waitForCondition(() => {
+      return URLBar.controller.searchStatus >=
+        Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH;
+    });
+    let matchCount = URLBar.popup._matchCount;
+    await BrowserTestUtils.waitForCondition(() => {
+      return URLBar.popup.richlistbox.childNodes.length == matchCount;
+    });
+
+    URLBar.controller.stopSearch();
+    // There are several setTimeout(fn, 0); calls inside autocomplete.xml
+    // that we need to wait for. Since those have higher priority than
+    // idle callbacks, we can be sure they will have run once this
+    // idle callback is called. The timeout seems to be required in
+    // automation - presumably because the machines can be pretty busy
+    // especially if it's GC'ing from previous tests.
+    await new Promise(resolve => win.requestIdleCallback(resolve, { timeout: 1000 }));
+
+    let hiddenPromise = BrowserTestUtils.waitForEvent(URLBar.popup, "popuphidden");
+    EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
+    await hiddenPromise;
   };
 
   await withReflowObserver(testFn, EXPECTED_REFLOWS_FIRST_OPEN, win);
 
   await withReflowObserver(testFn, EXPECTED_REFLOWS_SECOND_OPEN, win);
 
   await BrowserTestUtils.closeWindow(win);
 });
--- a/browser/base/content/test/performance/browser_windowopen_reflows.js
+++ b/browser/base/content/test/performance/browser_windowopen_reflows.js
@@ -12,17 +12,16 @@
  * See https://developer.mozilla.org/en-US/Firefox/Performance_best_practices_for_Firefox_fe_engineers
  * for tips on how to do that.
  */
 const EXPECTED_REFLOWS = [
   {
     stack: [
       "select@chrome://global/content/bindings/textbox.xml",
       "focusAndSelectUrlBar@chrome://browser/content/browser.js",
-      "_delayedStartup@chrome://browser/content/browser.js",
     ],
   },
 ];
 
 if (Services.appinfo.OS == "Linux") {
   if (gMultiProcessBrowser) {
     EXPECTED_REFLOWS.push({
       stack: [
--- a/browser/base/content/test/performance/head.js
+++ b/browser/base/content/test/performance/head.js
@@ -1,8 +1,13 @@
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+  "resource://testing-common/PlacesTestUtils.jsm");
+
 /**
  * Async utility function for ensuring that no unexpected uninterruptible
  * reflows occur during some period of time in a window.
  *
  * @param testFn (async function)
  *        The async function that will exercise the browser activity that is
  *        being tested for reflows.
  *
@@ -87,16 +92,22 @@ async function withReflowObserver(testFn
       dirtyFrameFn();
 
       // Stack trace is empty. Reflow was triggered by native code, which
       // we ignore.
       if (path === "") {
         return;
       }
 
+      // synthesizeKey from EventUtils.js causes us to reflow. That's the test
+      // harness and we don't care about that, so we'll filter that out.
+      if (path.startsWith("synthesizeKey@chrome://mochikit/content/tests/SimpleTest/EventUtils.js")) {
+        return;
+      }
+
       let index = expectedReflows.findIndex(reflow => path.startsWith(reflow.stack.join("|")));
 
       if (index != -1) {
         Assert.ok(true, "expected uninterruptible reflow: '" +
                   JSON.stringify(pathWithLineNumbers, null, "\t") + "'");
         if (--expectedReflows[index].times == 0) {
           expectedReflows.splice(index, 1);
         }
@@ -219,8 +230,30 @@ async function createTabs(howMany) {
 async function removeAllButFirstTab() {
   await SpecialPowers.pushPrefEnv({
     set: [["browser.tabs.warnOnCloseOtherTabs", false]],
   });
   gBrowser.removeAllTabsBut(gBrowser.tabs[0]);
   await BrowserTestUtils.waitForCondition(() => gBrowser.tabs.length == 1);
   await SpecialPowers.popPrefEnv();
 }
+
+/**
+ * Adds some entries to the Places database so that we can
+ * do semi-realistic look-ups in the URL bar.
+ */
+async function addDummyHistoryEntries() {
+  const NUM_VISITS = 10;
+  let visits = [];
+
+  for (let i = 0; i < NUM_VISITS; ++i) {
+    visits.push({
+      uri: `http://example.com/urlbar-reflows-${i}`,
+      title: `Reflow test for URL bar entry #${i}`,
+    });
+  }
+
+  await PlacesTestUtils.addVisits(visits);
+
+  registerCleanupFunction(async function() {
+    await PlacesTestUtils.clearHistory();
+  });
+}
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -126,16 +126,18 @@ var whitelist = [
   {file: "resource://gre/modules/Localization.jsm"},
 
   // Starting from here, files in the whitelist are bugs that need fixing.
   // Bug 1339420
   {file: "chrome://branding/content/icon128.png"},
   // Bug 1339424 (wontfix?)
   {file: "chrome://browser/locale/taskbar.properties",
    platforms: ["linux", "macosx"]},
+  // Bug 1370768 will reference this file
+  {file: "chrome://formautofill/content/editCreditCard.xhtml"},
   // Bug 1316187
   {file: "chrome://global/content/customizeToolbar.xul"},
   // Bug 1343837
   {file: "chrome://global/content/findUtils.js"},
   // Bug 1343843
   {file: "chrome://global/content/url-classifier/unittests.xul"},
   // Bug 1348362
   {file: "chrome://global/skin/icons/warning-64.png", platforms: ["linux", "win"]},
--- a/browser/base/content/test/urlbar/browser.ini
+++ b/browser/base/content/test/urlbar/browser.ini
@@ -110,16 +110,17 @@ support-files =
 support-files =
   file_urlbar_edit_dos.html
 [browser_urlbar_searchsettings.js]
 [browser_urlbar_search_speculative_connect.js]
 [browser_urlbar_search_speculative_connect_engine.js]
 support-files =
   searchSuggestionEngine2.xml
   searchSuggestionEngine.sjs
+[browser_urlbar_search_speculative_connect_mousedown.js]
 [browser_urlbar_stop_pending.js]
 support-files =
   slow-page.sjs
 [browser_urlbar_remoteness_switch.js]
 run-if = e10s
 [browser_urlHighlight.js]
 [browser_wyciwyg_urlbarCopying.js]
 subsuite = clipboard
--- a/browser/base/content/test/urlbar/browser_urlbar_search_speculative_connect.js
+++ b/browser/base/content/test/urlbar/browser_urlbar_search_speculative_connect.js
@@ -2,40 +2,31 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 // This test ensures that we setup a speculative network
 // connection for autoFilled values.
 
-let {HttpServer} = Cu.import("resource://testing-common/httpd.js", {});
 let gHttpServer = null;
 let gScheme = "http";
 let gHost = "localhost"; // 'localhost' by default.
 let gPort = -1;
 let gPrivateWin = null;
 let gIsSpeculativeConnected = false;
 
 add_task(async function setup() {
-  if (!gHttpServer) {
-    gHttpServer = new HttpServer();
-    try {
-      gHttpServer.start(gPort);
-      gPort = gHttpServer.identity.primaryPort;
-      gHttpServer.identity.setPrimary(gScheme, gHost, gPort);
-    } catch (ex) {
-      info("We can't launch our http server successfully.")
-    }
-  }
-  is(gHttpServer.identity.has(gScheme, gHost, gPort), true, "make sure we have this domain listed");
+  gHttpServer = runHttpServer(gScheme, gHost);
+  // The server will be run on a random port if the port number wasn't given.
+  gPort = gHttpServer.identity.primaryPort;
 
   await SpecialPowers.pushPrefEnv({
     set: [["browser.urlbar.autoFill", true],
-          // Turn off speculative connect to the search engine.
+          // Turn off search suggestion so we won't speculative connect to the search engine.
           ["browser.search.suggest.enabled", false],
           ["browser.urlbar.speculativeConnect.enabled", true],
           // In mochitest this number is 0 by default but we have to turn it on.
           ["network.http.speculative-parallel-limit", 6],
           // The http server is using IPv4, so it's better to disable IPv6 to avoid weird
           // networking problem.
           ["network.dns.disableIPv6", true]],
   });
@@ -45,17 +36,17 @@ add_task(async function setup() {
     title: "test visit for speculative connection",
     transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
   }]);
 
   gPrivateWin = await BrowserTestUtils.openNewBrowserWindow({private: true});
   is(PrivateBrowsingUtils.isWindowPrivate(gPrivateWin), true, "A private window created.");
 
   // Bug 764062 - we can't get port number from autocomplete result, so we have to mock
-  // this function to add it manually.
+  // this function and add it manually.
   let oldSpeculativeConnect = gURLBar.popup.maybeSetupSpeculativeConnect.bind(gURLBar.popup);
   let newSpeculativeConnect = (uriString) => {
     gIsSpeculativeConnected = true;
     info(`Original uri is ${uriString}`);
     let newUriString = uriString.substr(0, uriString.length - 1) +
                        ":" + gPort + "/";
     info(`New uri is ${newUriString}`);
     oldSpeculativeConnect(newUriString);
@@ -83,16 +74,17 @@ const test = {
 add_task(async function autofill_tests() {
   gIsSpeculativeConnected = false;
   info(`Searching for '${test.search}'`);
   await promiseAutocompleteResultPopup(test.search, window, true);
   is(gURLBar.inputField.value, test.autofilledValue,
      `Autofilled value is as expected for search '${test.search}'`);
   is(gIsSpeculativeConnected, true, "Speculative connection should be called");
   await promiseSpeculativeConnection(gHttpServer);
+  is(gHttpServer.connectionNumber, 1, `${gHttpServer.connectionNumber} speculative connection has been setup.`);
 });
 
 add_task(async function privateContext_test() {
   info("In private context.");
   gIsSpeculativeConnected = false;
   info(`Searching for '${test.search}'`);
   await promiseAutocompleteResultPopup(test.search, gPrivateWin, true);
   is(gPrivateWin.gURLBar.inputField.value, test.autofilledValue,
--- a/browser/base/content/test/urlbar/browser_urlbar_search_speculative_connect_engine.js
+++ b/browser/base/content/test/urlbar/browser_urlbar_search_speculative_connect_engine.js
@@ -2,39 +2,28 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 // This test ensures that we setup a speculative network connection to
 // current search engine if the first result is 'searchengine'.
 
-let {HttpServer} = Cu.import("resource://testing-common/httpd.js", {});
 let gHttpServer = null;
 let gScheme = "http";
 let gHost = "localhost"; // 'localhost' by default.
 let gPort = 20709; // the port number must be identical to what we said in searchSuggestionEngine2.xml
 const TEST_ENGINE_BASENAME = "searchSuggestionEngine2.xml";
 
 add_task(async function setup() {
-  if (!gHttpServer) {
-    gHttpServer = new HttpServer();
-    try {
-      gHttpServer.start(gPort);
-      gPort = gHttpServer.identity.primaryPort;
-      gHttpServer.identity.setPrimary(gScheme, gHost, gPort);
-    } catch (ex) {
-      info("We can't launch our http server successfully.")
-    }
-  }
-  is(gHttpServer.identity.has(gScheme, gHost, gPort), true, "make sure we have this domain listed");
+  gHttpServer = runHttpServer(gScheme, gHost, gPort);
 
   await SpecialPowers.pushPrefEnv({
     set: [["browser.urlbar.autoFill", true],
-          // Turn off speculative connect to the search engine.
+          // Make sure search suggestion for location bar is enabled
           ["browser.search.suggest.enabled", true],
           ["browser.urlbar.suggest.searches", true],
           ["browser.urlbar.speculativeConnect.enabled", true],
           // In mochitest this number is 0 by default but we have to turn it on.
           ["network.http.speculative-parallel-limit", 6],
           // The http server is using IPv4, so it's better to disable IPv6 to avoid weird
           // networking problem.
           ["network.dns.disableIPv6", true]],
@@ -49,18 +38,19 @@ add_task(async function setup() {
     Services.search.currentEngine = oldCurrentEngine;
     gHttpServer.identity.remove(gScheme, gHost, gPort);
     gHttpServer.stop(() => {
       gHttpServer = null;
     });
   });
 });
 
-add_task(async function autofill_tests() {
+add_task(async function connect_search_engine_tests() {
   info("Searching for 'foo'");
   await promiseAutocompleteResultPopup("foo", window, true);
   // Check if the first result is with type "searchengine"
   let controller = gURLBar.popup.input.controller;
   let style = controller.getStyleAt(0);
   is(style.includes("searchengine"), true, "The first result type is searchengine");
   await promiseSpeculativeConnection(gHttpServer);
+  is(gHttpServer.connectionNumber, 1, `${gHttpServer.connectionNumber} speculative connection has been setup.`);
 });
 
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbar_search_speculative_connect_mousedown.js
@@ -0,0 +1,73 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// This test ensures that we setup a speculative network connection to
+// the site in mousedown event before the http request happens(in mouseup).
+
+let gHttpServer = null;
+let gScheme = "http";
+let gHost = "localhost"; // 'localhost' by default.
+let gPort = -1;
+let gIsSpeculativeConnected = false;
+
+add_task(async function setup() {
+  gHttpServer = runHttpServer(gScheme, gHost, gPort);
+  // The server will be run on a random port if the port number wasn't given.
+  gPort = gHttpServer.identity.primaryPort;
+
+  await PlacesTestUtils.addVisits([{
+    uri: `${gScheme}://${gHost}:${gPort}`,
+    title: "test visit for speculative connection",
+    transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
+  }]);
+
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.urlbar.autoFill", true],
+          // Turn off search suggestion so we won't speculative connect to the search engine.
+          ["browser.search.suggest.enabled", false],
+          ["browser.urlbar.speculativeConnect.enabled", true],
+          // In mochitest this number is 0 by default but we have to turn it on.
+          ["network.http.speculative-parallel-limit", 6],
+          // The http server is using IPv4, so it's better to disable IPv6 to avoid weird
+          // networking problem.
+          ["network.dns.disableIPv6", true]],
+  });
+
+  registerCleanupFunction(async function() {
+    await PlacesUtils.history.clear();
+    gHttpServer.identity.remove(gScheme, gHost, gPort);
+    gHttpServer.stop(() => {
+      gHttpServer = null;
+    });
+  });
+});
+
+add_task(async function popup_mousedown_tests() {
+  const test = {
+    // To not trigger autofill, search keyword starts from the second character.
+    search: gHost.substr(1, 4),
+    completeValue: `${gScheme}://${gHost}:${gPort}/`
+  };
+  info(`Searching for '${test.search}'`);
+  await promiseAutocompleteResultPopup(test.search, window, true);
+  // Check if the first result is with type "searchengine"
+  let controller = gURLBar.popup.input.controller;
+  // The first item should be 'Search with ...' thus we wan the second.
+  let value = controller.getFinalCompleteValueAt(1);
+  info(`The value of the second item is ${value}`);
+  is(value, test.completeValue, "The second item has the url we visited.");
+
+  await BrowserTestUtils.waitForCondition(() => {
+    return !!gURLBar.popup.richlistbox.childNodes[1] &&
+           is_visible(gURLBar.popup.richlistbox.childNodes[1]);
+  }, "the node is there.");
+
+  let listitem = gURLBar.popup.richlistbox.childNodes[1];
+  EventUtils.synthesizeMouse(listitem, 10, 10, {type: "mousedown"}, window);
+  is(gURLBar.popup.richlistbox.selectedIndex, 1, "The second item is selected");
+  await promiseSpeculativeConnection(gHttpServer);
+  is(gHttpServer.connectionNumber, 1, `${gHttpServer.connectionNumber} speculative connection has been setup.`);
+});
--- a/browser/base/content/test/urlbar/head.js
+++ b/browser/base/content/test/urlbar/head.js
@@ -3,16 +3,18 @@
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
   "resource://testing-common/PlacesTestUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
   "resource://gre/modules/Preferences.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "HttpServer",
+  "resource://testing-common/httpd.js");
 
 /**
  * Waits for the next top-level document load in the current browser.  The URI
  * of the document is compared against aExpectedURL.  The load is then stopped
  * before it actually starts.
  *
  * @param aExpectedURL
  *        The URL of the document that is expected to load.
@@ -124,16 +126,29 @@ function is_element_visible(element, msg
   ok(is_visible(element), msg || "Element should be visible");
 }
 
 function is_element_hidden(element, msg) {
   isnot(element, null, "Element should not be null, when checking visibility");
   ok(is_hidden(element), msg || "Element should be hidden");
 }
 
+function runHttpServer(scheme, host, port = -1) {
+  let httpserver = new HttpServer();
+  try {
+    httpserver.start(port);
+    port = httpserver.identity.primaryPort;
+    httpserver.identity.setPrimary(scheme, host, port);
+  } catch (ex) {
+    info("We can't launch our http server successfully.")
+  }
+  is(httpserver.identity.has(scheme, host, port), true, `${scheme}://${host}:${port} is listening.`);
+  return httpserver;
+}
+
 function promisePopupEvent(popup, eventSuffix) {
   let endState = {shown: "open", hidden: "closed"}[eventSuffix];
 
   if (popup.state == endState)
     return Promise.resolve();
 
   let eventType = "popup" + eventSuffix;
   return new Promise(resolve => {
@@ -147,44 +162,52 @@ function promisePopupEvent(popup, eventS
 function promisePopupShown(popup) {
   return promisePopupEvent(popup, "shown");
 }
 
 function promisePopupHidden(popup) {
   return promisePopupEvent(popup, "hidden");
 }
 
-function promiseSearchComplete(win = window) {
+function promiseSearchComplete(win = window, dontAnimate = false) {
   return promisePopupShown(win.gURLBar.popup).then(() => {
     function searchIsComplete() {
-      return win.gURLBar.controller.searchStatus >=
-        Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH;
+      let isComplete = win.gURLBar.controller.searchStatus >=
+                       Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH;
+      if (isComplete) {
+        info(`Restore popup dontAnimate value to ${dontAnimate}`);
+        win.gURLBar.popup.setAttribute("dontanimate", dontAnimate);
+      }
+      return isComplete;
     }
 
     // Wait until there are at least two matches.
     return BrowserTestUtils.waitForCondition(searchIsComplete, "waiting urlbar search to complete");
   });
 }
 
 function promiseAutocompleteResultPopup(inputText,
                                         win = window,
                                         fireInputEvent = false) {
+  let dontAnimate = !!win.gURLBar.popup.getAttribute("dontanimate");
   waitForFocus(() => {
+    info(`Disable popup animation. Change dontAnimate value from ${dontAnimate} to true.`);
+    win.gURLBar.popup.setAttribute("dontanimate", "true");
     win.gURLBar.focus();
     win.gURLBar.value = inputText;
     if (fireInputEvent) {
       // This is necessary to get the urlbar to set gBrowser.userTypedValue.
       let event = document.createEvent("Events");
       event.initEvent("input", true, true);
       win.gURLBar.dispatchEvent(event);
     }
     win.gURLBar.controller.startSearch(inputText);
   }, win);
 
-  return promiseSearchComplete(win);
+  return promiseSearchComplete(win, dontAnimate);
 }
 
 function promiseNewSearchEngine(basename) {
   return new Promise((resolve, reject) => {
     info("Waiting for engine to be added: " + basename);
     let url = getRootDirectory(gTestPath) + basename;
     Services.search.addEngine(url, null, "", false, {
       onSuccess(engine) {
@@ -250,15 +273,13 @@ function promisePageActionViewShown() {
       }, 5000);
     }, { once: true });
   });
 }
 
 function promiseSpeculativeConnection(httpserver) {
   return BrowserTestUtils.waitForCondition(() => {
     if (httpserver) {
-      is(httpserver.connectionNumber, 1,
-         `${httpserver.connectionNumber} speculative connection has been setup.`)
       return httpserver.connectionNumber == 1;
     }
     return false;
   }, "Waiting for connection setup");
 }
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -36,18 +36,18 @@ file, You can obtain one at http://mozil
                   class="textbox-input-box urlbar-input-box"
                   flex="1" xbl:inherits="tooltiptext=inputtooltiptext">
           <children/>
           <html:input anonid="input"
                       class="autocomplete-textbox urlbar-input textbox-input uri-element-right-align"
                       allowevents="true"
                       inputmode="url"
                       xbl:inherits="tooltiptext=inputtooltiptext,value,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,focused,textoverflow"/>
-          <xul:image anonid="go-button"
-                     class="urlbar-go-button"
+          <xul:image anonid="urlbar-go-button"
+                     class="urlbar-go-button urlbar-icon"
                      onclick="gURLBar.handleCommand(event);"
                      tooltiptext="&goEndCap.tooltip;"
                      xbl:inherits="pageproxystate,parentfocused=focused"/>
         </xul:hbox>
         <xul:dropmarker anonid="historydropmarker"
                         class="autocomplete-history-dropmarker urlbar-history-dropmarker urlbar-icon"
                         tooltiptext="&urlbar.openHistoryPopup.tooltip;"
                         allowevents="true"
@@ -154,17 +154,17 @@ file, You can obtain one at http://mozil
         // Null out the one-offs' popup and textbox so that it cleans up its
         // internal state for both.  Most importantly, it removes the event
         // listeners that it added to both.
         this.popup.oneOffSearchButtons.popup = null;
         this.popup.oneOffSearchButtons.textbox = null;
       ]]></destructor>
 
       <field name="goButton">
-        document.getAnonymousElementByAttribute(this, "anonid", "go-button");
+        document.getAnonymousElementByAttribute(this, "anonid", "urlbar-go-button");
       </field>
 
       <field name="_value">""</field>
       <field name="gotResultForCurrentQuery">false</field>
 
       <!--
         This is set around HandleHenter so it can be used in handleCommand.
         It is also used to track whether we must handle a delayed handleEnter,
@@ -2310,16 +2310,62 @@ file, You can obtain one at http://mozil
 
       <handler event="mousedown"><![CDATA[
         // Required to make the xul:label.text-link elements in the search
         // suggestions notification work correctly when clicked on Linux.
         // This is copied from the mousedown handler in
         // browser-search-autocomplete-result-popup, which apparently had a
         // similar problem.
         event.preventDefault();
+
+        if (!this.input.speculativeConnectEnabled) {
+          return;
+        }
+        if (event.button == 2) {
+          // Ignore right-clicks.
+          return;
+        }
+        // Ensure the user is clicking on an url instead of other buttons
+        // on the popup.
+        let elt = event.originalTarget;
+        while (elt && elt.localName != "richlistitem" && elt != this) {
+          elt = elt.parentNode;
+        }
+        if (!elt || elt.localName != "richlistitem") {
+          return;
+        }
+        // The user might click on a ghost entry which was removed because of
+        // the coming new results.
+        if (this.input.controller.matchCount <= this.selectedIndex) {
+          return;
+        }
+
+        let url = this.input.controller.getFinalCompleteValueAt(this.selectedIndex);
+
+        // Whitelist the cases that we want to speculative connect, and ignore
+        // other moz-action uris or fancy protocols.
+        // Note that it's likely we've speculatively connected to the first
+        // url because it is a heuristic "autofill" result (see bug 1348275).
+        // "moz-action:searchengine" is also the same case. (see bug 1355443)
+        // So we won't duplicate the effort here.
+        if (url.startsWith("http") && this.selectedIndex > 0) {
+          this.maybeSetupSpeculativeConnect(url);
+        } else if (url.startsWith("moz-action:remotetab")) {
+          // URL is in the format moz-action:ACTION,PARAMS
+          // Where PARAMS is a JSON encoded object.
+          const MOZ_ACTION_REGEX = /^moz-action:([^,]+),(.*)$/;
+          if (!MOZ_ACTION_REGEX.test(url))
+            return;
+
+          let params = JSON.parse(url.match(MOZ_ACTION_REGEX)[2]);
+          if (params.url) {
+            this.maybeSetupSpeculativeConnect(decodeURIComponent(params.url));
+          }
+        }
+
       ]]></handler>
 
     </handlers>
   </binding>
 
   <binding id="addon-progress-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
     <implementation>
       <constructor><![CDATA[
--- a/browser/components/about/AboutRedirector.cpp
+++ b/browser/components/about/AboutRedirector.cpp
@@ -18,16 +18,17 @@
 
 namespace mozilla {
 namespace browser {
 
 NS_IMPL_ISUPPORTS(AboutRedirector, nsIAboutModule)
 
 bool AboutRedirector::sUseOldPreferences = false;
 bool AboutRedirector::sActivityStreamEnabled = false;
+bool AboutRedirector::sActivityStreamAboutHomeEnabled = false;
 
 struct RedirEntry {
   const char* id;
   const char* url;
   uint32_t flags;
 };
 
 /*
@@ -115,16 +116,29 @@ GetAboutModuleName(nsIURI *aURI)
   f = path.FindChar('?');
   if (f >= 0)
     path.SetLength(f);
 
   ToLowerCase(path);
   return path;
 }
 
+void
+AboutRedirector::LoadActivityStreamPrefs()
+{
+  static bool sASEnabledCacheInited = false;
+  if (!sASEnabledCacheInited) {
+    Preferences::AddBoolVarCache(&AboutRedirector::sActivityStreamEnabled,
+                                 "browser.newtabpage.activity-stream.enabled");
+    Preferences::AddBoolVarCache(&AboutRedirector::sActivityStreamAboutHomeEnabled,
+                                 "browser.newtabpage.activity-stream.aboutHome.enabled");
+    sASEnabledCacheInited = true;
+  }
+}
+
 NS_IMETHODIMP
 AboutRedirector::NewChannel(nsIURI* aURI,
                             nsILoadInfo* aLoadInfo,
                             nsIChannel** result)
 {
   NS_ENSURE_ARG_POINTER(aURI);
   NS_ENSURE_ARG_POINTER(aLoadInfo);
 
@@ -137,22 +151,24 @@ AboutRedirector::NewChannel(nsIURI* aURI
   NS_ENSURE_SUCCESS(rv, rv);
 
   static bool sPrefCacheInited = false;
   if (!sPrefCacheInited) {
     Preferences::AddBoolVarCache(&sUseOldPreferences,
                                  "browser.preferences.useOldOrganization");
     sPrefCacheInited = true;
   }
+  LoadActivityStreamPrefs();
 
   for (auto & redir : kRedirMap) {
     if (!strcmp(path.get(), redir.id)) {
       nsAutoCString url;
 
-      if (path.EqualsLiteral("newtab")) {
+      if (path.EqualsLiteral("newtab") ||
+          (path.EqualsLiteral("home") && sActivityStreamEnabled && sActivityStreamAboutHomeEnabled)) {
         // let the aboutNewTabService decide where to redirect
         nsCOMPtr<nsIAboutNewTabService> aboutNewTabService =
           do_GetService("@mozilla.org/browser/aboutnewtab-service;1", &rv);
         NS_ENSURE_SUCCESS(rv, rv);
         rv = aboutNewTabService->GetDefaultURL(url);
         NS_ENSURE_SUCCESS(rv, rv);
       } else if (path.EqualsLiteral("preferences") && !sUseOldPreferences) {
         url.AssignASCII("chrome://browser/content/preferences/in-content-new/preferences.xul");
@@ -196,30 +212,25 @@ AboutRedirector::NewChannel(nsIURI* aURI
 
 NS_IMETHODIMP
 AboutRedirector::GetURIFlags(nsIURI *aURI, uint32_t *result)
 {
   NS_ENSURE_ARG_POINTER(aURI);
 
   nsAutoCString name = GetAboutModuleName(aURI);
 
-  static bool sASEnabledCacheInited = false;
-  if (!sASEnabledCacheInited) {
-    Preferences::AddBoolVarCache(&sActivityStreamEnabled,
-                                 "browser.newtabpage.activity-stream.enabled");
-    sASEnabledCacheInited = true;
-  }
+  LoadActivityStreamPrefs();
 
   for (auto & redir : kRedirMap) {
     if (name.Equals(redir.id)) {
 
       // Once ActivityStream is fully rolled out and we've removed Tiles,
       // this special case can go away and the flag can just become part
       // of the normal about:newtab entry in kRedirMap.
-      if (name.EqualsLiteral("newtab")) {
+      if (name.EqualsLiteral("newtab") || (name.EqualsLiteral("home") && sActivityStreamAboutHomeEnabled)) {
         if (sActivityStreamEnabled) {
           *result = redir.flags |
             nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
             nsIAboutModule::ENABLE_INDEXED_DB |
             nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT;
           return NS_OK;
         }
       }
--- a/browser/components/about/AboutRedirector.h
+++ b/browser/components/about/AboutRedirector.h
@@ -23,14 +23,17 @@ public:
     Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
 
 protected:
   virtual ~AboutRedirector() {}
 
 private:
   static bool sUseOldPreferences;
   static bool sActivityStreamEnabled;
+  static bool sActivityStreamAboutHomeEnabled;
+
+  static void LoadActivityStreamPrefs();
 };
 
 } // namespace browser
 } // namespace mozilla
 
 #endif // AboutRedirector_h__
--- a/browser/components/contextualidentity/content/usercontext.css
+++ b/browser/components/contextualidentity/content/usercontext.css
@@ -87,16 +87,17 @@
 }
 
 #userContext-indicator {
   height: 16px;
   width: 16px;
 }
 
 #userContext-label {
+  margin-inline-start: 0;
   margin-inline-end: 3px;
   color: var(--identity-tab-color);
 }
 
 #userContext-icons {
   -moz-box-align: center;
 }
 
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -1282,16 +1282,17 @@ var CustomizableUIInternal = {
       return kSpecialWidgetPfx + aId + (++gNewElementCount);
     }
     return aId;
   },
 
   createSpecialWidget(aId, aDocument) {
     let nodeName = "toolbar" + aId.match(/spring|spacer|separator/)[0];
     let node = aDocument.createElementNS(kNSXUL, nodeName);
+    node.className = "chromeclass-toolbar-additional";
     node.id = this.ensureSpecialWidgetId(aId);
     return node;
   },
 
   /* Find a XUL-provided widget in a window. Don't try to use this
    * for an API-provided widget or a special widget.
    */
   findWidgetInWindow(aId, aWindow) {
--- a/browser/components/extensions/ext-chrome-settings-overrides.js
+++ b/browser/components/extensions/ext-chrome-settings-overrides.js
@@ -1,16 +1,23 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionPreferencesManager",
                                   "resource://gre/modules/ExtensionPreferencesManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ExtensionSettingsStore",
+                                  "resource://gre/modules/ExtensionSettingsStore.jsm");
+
+const DEFAULT_SEARCH_STORE_TYPE = "default_search";
+const DEFAULT_SEARCH_SETTING_NAME = "defaultSearch";
 
 const searchInitialized = () => {
   return new Promise(resolve => {
     if (Services.search.isInitialized) {
       resolve();
     }
     const SEARCH_SERVICE_TOPIC = "browser-search-service";
     Services.obs.addObserver(function observer(subject, topic, data) {
@@ -20,27 +27,89 @@ const searchInitialized = () => {
 
       Services.obs.removeObserver(observer, SEARCH_SERVICE_TOPIC);
       resolve();
     }, SEARCH_SERVICE_TOPIC);
   });
 };
 
 this.chrome_settings_overrides = class extends ExtensionAPI {
+  processDefaultSearchSetting(action) {
+    let {extension} = this;
+    let item = ExtensionSettingsStore.getSetting(DEFAULT_SEARCH_STORE_TYPE, DEFAULT_SEARCH_SETTING_NAME);
+    if (!item) {
+      return;
+    }
+    if (Services.search.currentEngine.name != item.value &&
+        Services.search.currentEngine.name != item.initialValue) {
+      // The current engine is not the same as the value that the ExtensionSettingsStore has.
+      // This means that the user changed the engine, so we shouldn't control it anymore.
+      // Do nothing and remove our entry from the ExtensionSettingsStore.
+      ExtensionSettingsStore.removeSetting(extension, DEFAULT_SEARCH_STORE_TYPE, DEFAULT_SEARCH_SETTING_NAME);
+      return;
+    }
+    item = ExtensionSettingsStore[action](extension, DEFAULT_SEARCH_STORE_TYPE, DEFAULT_SEARCH_SETTING_NAME);
+    if (item) {
+      try {
+        let engine = Services.search.getEngineByName(item.value || item.initialValue);
+        if (engine) {
+          Services.search.currentEngine = engine;
+        }
+      } catch (e) {
+        Components.utils.reportError(e);
+      }
+    }
+  }
+
   async onManifestEntry(entryName) {
     let {extension} = this;
     let {manifest} = extension;
 
+    await ExtensionSettingsStore.initialize();
     if (manifest.chrome_settings_overrides.homepage) {
       ExtensionPreferencesManager.setSetting(extension, "homepage_override",
                                              manifest.chrome_settings_overrides.homepage);
     }
     if (manifest.chrome_settings_overrides.search_provider) {
       await searchInitialized();
       let searchProvider = manifest.chrome_settings_overrides.search_provider;
+      if (searchProvider.is_default) {
+        let engineName = searchProvider.name.trim();
+        let engine = Services.search.getEngineByName(engineName);
+        if (engine && Services.search.getDefaultEngines().includes(engine)) {
+          // Only add onclose handlers if we would definitely
+          // be setting the default engine.
+          extension.callOnClose({
+            close: () => {
+              switch (extension.shutdownReason) {
+                case "ADDON_DISABLE":
+                  this.processDefaultSearchSetting("disable");
+                  break;
+
+                case "ADDON_UNINSTALL":
+                  this.processDefaultSearchSetting("removeSetting");
+                  break;
+              }
+            },
+          });
+          if (extension.startupReason === "ADDON_INSTALL") {
+            let item = await ExtensionSettingsStore.addSetting(
+              extension, DEFAULT_SEARCH_STORE_TYPE, DEFAULT_SEARCH_SETTING_NAME, engineName, () => {
+                return Services.search.currentEngine.name;
+              });
+            Services.search.currentEngine = Services.search.getEngineByName(item.value);
+          } else if (extension.startupReason === "ADDON_ENABLE") {
+            this.processDefaultSearchSetting("enable");
+          }
+          // If we would have set the default engine,
+          // we don't allow a search provider to be added.
+          return;
+        }
+        Components.utils.reportError("is_default can only be used for built-in engines.");
+      }
       let isCurrent = false;
       let index = -1;
       if (extension.startupReason === "ADDON_UPGRADE") {
         let engines = Services.search.getEnginesByExtensionID(extension.id);
         if (engines.length > 0) {
           // There can be only one engine right now
           isCurrent = Services.search.currentEngine == engines[0];
           // Get position of engine and store it
@@ -65,16 +134,27 @@ this.chrome_settings_overrides = class e
           if (index != -1) {
             Services.search.moveEngine(engine, index);
           }
         }
       } catch (e) {
         Components.utils.reportError(e);
       }
     }
+    // If the setting exists for the extension, but is missing from the manifest,
+    // remove it. This can happen if the extension removes is_default.
+    // There's really no good place to put this, because the entire search section
+    // could be removed.
+    // We'll never get here in the normal case because we always return early
+    // if we have an is_default value that we use.
+    if (ExtensionSettingsStore.hasSetting(
+               extension, DEFAULT_SEARCH_STORE_TYPE, DEFAULT_SEARCH_SETTING_NAME)) {
+      await searchInitialized();
+      this.processDefaultSearchSetting("removeSetting");
+    }
   }
   async onShutdown(reason) {
     let {extension} = this;
     if (reason == "ADDON_DISABLE" ||
         reason == "ADDON_UNINSTALL") {
       if (extension.manifest.chrome_settings_overrides.search_provider) {
         await searchInitialized();
         let engines = Services.search.getEnginesByExtensionID(extension.id);
--- a/browser/components/extensions/schemas/chrome_settings_overrides.json
+++ b/browser/components/extensions/schemas/chrome_settings_overrides.json
@@ -93,17 +93,17 @@
                   "prepopulated_id": {
                     "type": "integer",
                     "optional": true,
                     "deprecated": "Unsupported on Firefox."
                   },
                   "is_default": {
                     "type": "boolean",
                     "optional": true,
-                    "deprecated": "Unsupported on Firefox at this time."
+                    "description": "Sets the default engine to a built-in engine only."
                   }
                 }
               }
             }
           }
         }
       }
     ]
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -101,16 +101,17 @@ skip-if = debug || asan # Bug 1354681
 [browser_ext_runtime_openOptionsPage_uninstall.js]
 [browser_ext_runtime_setUninstallURL.js]
 [browser_ext_sessions_forgetClosedTab.js]
 [browser_ext_sessions_forgetClosedWindow.js]
 [browser_ext_sessions_getRecentlyClosed.js]
 [browser_ext_sessions_getRecentlyClosed_private.js]
 [browser_ext_sessions_getRecentlyClosed_tabs.js]
 [browser_ext_sessions_restore.js]
+[browser_ext_settings_overrides_default_search.js]
 [browser_ext_settings_overrides_search.js]
 [browser_ext_sidebarAction.js]
 [browser_ext_sidebarAction_browser_style.js]
 [browser_ext_sidebarAction_context.js]
 [browser_ext_sidebarAction_contextMenu.js]
 [browser_ext_sidebarAction_tabs.js]
 [browser_ext_sidebarAction_windows.js]
 [browser_ext_simple.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_settings_overrides_default_search.js
@@ -0,0 +1,506 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+
+"use strict";
+
+XPCOMUtils.defineLazyGetter(this, "Management", () => {
+  const {Management} = Cu.import("resource://gre/modules/Extension.jsm", {});
+  return Management;
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
+                                  "resource://gre/modules/AddonManager.jsm");
+
+const EXTENSION1_ID = "extension1@mozilla.com";
+const EXTENSION2_ID = "extension2@mozilla.com";
+
+function awaitEvent(eventName, id) {
+  return new Promise(resolve => {
+    let listener = (_eventName, ...args) => {
+      let extension = args[0];
+      if (_eventName === eventName &&
+          extension.id == id) {
+        Management.off(eventName, listener);
+        resolve(...args);
+      }
+    };
+
+    Management.on(eventName, listener);
+  });
+}
+
+/* This tests setting a default engine. */
+add_task(async function test_extension_setting_default_engine() {
+  let defaultEngineName = Services.search.currentEngine.name;
+
+  let ext1 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "DuckDuckGo",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  await ext1.startup();
+
+  is(Services.search.currentEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+
+  await ext1.unload();
+
+  is(Services.search.currentEngine.name, defaultEngineName, `Default engine is ${defaultEngineName}`);
+});
+
+/* This tests that using an invalid engine does nothing. */
+add_task(async function test_extension_setting_invalid_name_default_engine() {
+  let defaultEngineName = Services.search.currentEngine.name;
+
+  let ext1 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "InvalidName",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  await ext1.startup();
+
+  is(Services.search.currentEngine.name, defaultEngineName, `Default engine is ${defaultEngineName}`);
+
+  await ext1.unload();
+
+  is(Services.search.currentEngine.name, defaultEngineName, `Default engine is ${defaultEngineName}`);
+});
+
+/* This tests that uninstalling add-ons maintains the proper
+ * search default. */
+add_task(async function test_extension_setting_multiple_default_engine() {
+  let defaultEngineName = Services.search.currentEngine.name;
+  let ext1 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "DuckDuckGo",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  let ext2 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "Twitter",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  await ext1.startup();
+
+  is(Services.search.currentEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+
+  await ext2.startup();
+
+  is(Services.search.currentEngine.name, "Twitter", "Default engine is Twitter");
+
+  await ext2.unload();
+
+  is(Services.search.currentEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+
+  await ext1.unload();
+
+  is(Services.search.currentEngine.name, defaultEngineName, `Default engine is ${defaultEngineName}`);
+});
+
+/* This tests that uninstalling add-ons in reverse order maintains the proper
+ * search default. */
+add_task(async function test_extension_setting_multiple_default_engine_reversed() {
+  let defaultEngineName = Services.search.currentEngine.name;
+  let ext1 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "DuckDuckGo",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  let ext2 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "Twitter",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  await ext1.startup();
+
+  is(Services.search.currentEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+
+  await ext2.startup();
+
+  is(Services.search.currentEngine.name, "Twitter", "Default engine is Twitter");
+
+  await ext1.unload();
+
+  is(Services.search.currentEngine.name, "Twitter", "Default engine is Twitter");
+
+  await ext2.unload();
+
+  is(Services.search.currentEngine.name, defaultEngineName, `Default engine is ${defaultEngineName}`);
+});
+
+/* This tests adding an engine with one add-on and trying to make it the
+ *default with anoth. */
+add_task(async function test_extension_setting_invalid_default_engine() {
+  let defaultEngineName = Services.search.currentEngine.name;
+  let ext1 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "MozSearch",
+          "keyword": "MozSearch",
+          "search_url": "https://example.com/?q={searchTerms}",
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  let ext2 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "MozSearch",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  await ext1.startup();
+
+  is(Services.search.currentEngine.name, defaultEngineName, `Default engine is ${defaultEngineName}`);
+
+  let engine = Services.search.getEngineByName("MozSearch");
+  ok(engine, "Engine should exist.");
+
+  await ext2.startup();
+
+  is(Services.search.currentEngine.name, defaultEngineName, `Default engine is ${defaultEngineName}`);
+
+  await ext2.unload();
+
+  is(Services.search.currentEngine.name, defaultEngineName, `Default engine is ${defaultEngineName}`);
+
+  await ext1.unload();
+
+  is(Services.search.currentEngine.name, defaultEngineName, `Default engine is ${defaultEngineName}`);
+});
+
+/* This tests that when the user changes the search engine and the add-on
+ * is unistalled, search stays with the user's choice. */
+add_task(async function test_user_changing_default_engine() {
+  let ext1 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "DuckDuckGo",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  await ext1.startup();
+
+  is(Services.search.currentEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+
+  let engine = Services.search.getEngineByName("Twitter");
+  Services.search.currentEngine = engine;
+
+  await ext1.unload();
+
+  is(Services.search.currentEngine.name, "Twitter", "Default engine is Twitter");
+});
+
+/* This tests that when the user changes the search engine while it is
+ * disabled, user choice is maintained when the add-on is reenabled. */
+add_task(async function test_user_change_with_disabling() {
+  let ext1 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      applications: {
+        gecko: {
+          id: EXTENSION1_ID,
+        },
+      },
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "DuckDuckGo",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  await ext1.startup();
+
+  is(Services.search.currentEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+
+  let engine = Services.search.getEngineByName("Twitter");
+  Services.search.currentEngine = engine;
+
+  is(Services.search.currentEngine.name, "Twitter", "Default engine is Twitter");
+
+  let disabledPromise = awaitEvent("shutdown", EXTENSION1_ID);
+  let addon = await AddonManager.getAddonByID(EXTENSION1_ID);
+  addon.userDisabled = true;
+  await disabledPromise;
+
+  is(Services.search.currentEngine.name, "Twitter", "Default engine is Twitter");
+
+  let enabledPromise = awaitEvent("ready", EXTENSION1_ID);
+  addon.userDisabled = false;
+  await enabledPromise;
+
+  is(Services.search.currentEngine.name, "Twitter", "Default engine is Twitter");
+  await ext1.unload();
+});
+
+/* This tests that when two add-ons are installed that change default
+ * search and the first one is disabled, before the second one is installed,
+ * when the first one is reenabled, the second add-on keeps the search. */
+add_task(async function test_two_addons_with_first_disabled_before_second() {
+  let defaultEngineName = Services.search.currentEngine.name;
+  let ext1 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      applications: {
+        gecko: {
+          id: EXTENSION1_ID,
+        },
+      },
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "DuckDuckGo",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  let ext2 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      applications: {
+        gecko: {
+          id: EXTENSION2_ID,
+        },
+      },
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "Twitter",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  await ext1.startup();
+
+  is(Services.search.currentEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+
+  let disabledPromise = awaitEvent("shutdown", EXTENSION1_ID);
+  let addon1 = await AddonManager.getAddonByID(EXTENSION1_ID);
+  addon1.userDisabled = true;
+  await disabledPromise;
+
+  is(Services.search.currentEngine.name, defaultEngineName, `Default engine is ${defaultEngineName}`);
+
+  await ext2.startup();
+
+  is(Services.search.currentEngine.name, "Twitter", "Default engine is Twitter");
+
+  let enabledPromise = awaitEvent("ready", EXTENSION1_ID);
+  addon1.userDisabled = false;
+  await enabledPromise;
+
+  is(Services.search.currentEngine.name, "Twitter", "Default engine is Twitter");
+  await ext2.unload();
+
+  is(Services.search.currentEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+  await ext1.unload();
+
+  is(Services.search.currentEngine.name, defaultEngineName, `Default engine is ${defaultEngineName}`);
+});
+
+/* This tests that when two add-ons are installed that change default
+ * search and the first one is disabled, the second one maintains
+ * the search. */
+add_task(async function test_two_addons_with_first_disabled() {
+  let defaultEngineName = Services.search.currentEngine.name;
+  let ext1 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      applications: {
+        gecko: {
+          id: EXTENSION1_ID,
+        },
+      },
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "DuckDuckGo",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  let ext2 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      applications: {
+        gecko: {
+          id: EXTENSION2_ID,
+        },
+      },
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "Twitter",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  await ext1.startup();
+
+  is(Services.search.currentEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+
+  await ext2.startup();
+
+  is(Services.search.currentEngine.name, "Twitter", "Default engine is Twitter");
+
+  let disabledPromise = awaitEvent("shutdown", EXTENSION1_ID);
+  let addon1 = await AddonManager.getAddonByID(EXTENSION1_ID);
+  addon1.userDisabled = true;
+  await disabledPromise;
+
+  is(Services.search.currentEngine.name, "Twitter", "Default engine is Twitter");
+
+  let enabledPromise = awaitEvent("ready", EXTENSION1_ID);
+  addon1.userDisabled = false;
+  await enabledPromise;
+
+  is(Services.search.currentEngine.name, "Twitter", "Default engine is Twitter");
+  await ext2.unload();
+
+  is(Services.search.currentEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+  await ext1.unload();
+
+  is(Services.search.currentEngine.name, defaultEngineName, `Default engine is ${defaultEngineName}`);
+});
+
+/* This tests that when two add-ons are installed that change default
+ * search and the second one is disabled, the first one properly
+ * gets the search. */
+add_task(async function test_two_addons_with_second_disabled() {
+  let defaultEngineName = Services.search.currentEngine.name;
+  let ext1 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      applications: {
+        gecko: {
+          id: EXTENSION1_ID,
+        },
+      },
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "DuckDuckGo",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  let ext2 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      applications: {
+        gecko: {
+          id: EXTENSION2_ID,
+        },
+      },
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "Twitter",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  await ext1.startup();
+
+  is(Services.search.currentEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+
+  await ext2.startup();
+
+  is(Services.search.currentEngine.name, "Twitter", "Default engine is Twitter");
+
+  let disabledPromise = awaitEvent("shutdown", EXTENSION2_ID);
+  let addon2 = await AddonManager.getAddonByID(EXTENSION2_ID);
+  addon2.userDisabled = true;
+  await disabledPromise;
+
+  is(Services.search.currentEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+
+  let enabledPromise = awaitEvent("ready", EXTENSION2_ID);
+  addon2.userDisabled = false;
+  await enabledPromise;
+
+  is(Services.search.currentEngine.name, "Twitter", "Default engine is Twitter");
+  await ext2.unload();
+
+  is(Services.search.currentEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+  await ext1.unload();
+
+  is(Services.search.currentEngine.name, defaultEngineName, `Default engine is ${defaultEngineName}`);
+});
--- a/browser/components/search/content/search.xml
+++ b/browser/components/search/content/search.xml
@@ -54,17 +54,17 @@
           <xul:hbox class="searchbar-search-button-container">
             <xul:image class="searchbar-search-button"
                        anonid="searchbar-search-button"
                        xbl:inherits="addengines"
                        tooltiptext="&searchIcon.tooltip;"/>
           </xul:hbox>
         </xul:box>
         <xul:hbox class="search-go-container">
-          <xul:image class="search-go-button" hidden="true"
+          <xul:image class="search-go-button urlbar-icon" hidden="true"
                      anonid="search-go-button"
                      onclick="handleSearchCommand(event);"
                      tooltiptext="&contentSearchSubmit.tooltip;"/>
         </xul:hbox>
       </xul:textbox>
     </content>
 
     <implementation implements="nsIObserver">
--- a/browser/components/sessionstore/nsISessionStartup.idl
+++ b/browser/components/sessionstore/nsISessionStartup.idl
@@ -31,32 +31,21 @@ interface nsISessionStartup: nsISupports
   /**
    * Determines whether automatic session restoration is enabled for this
    * launch of the browser. This does not include crash restoration, and will
    * return false if restoration will only be caused by a crash.
    */
   boolean isAutomaticRestoreEnabled();
 
   /**
-   * Returns whether we will restore a session that ends up replacing the
-   * homepage. The browser uses this to not start loading the homepage if
-   * we're going to stop its load anyway shortly after.
-   *
-   * This is meant to be an optimization for the average case that loading the
-   * session file finishes before we may want to start loading the default
-   * homepage. Should this be called before the session file has been read it
-   * will just return false.
-   */
-  readonly attribute bool willOverrideHomepage;
-
-  /**
-   * Returns a promise that resolves to a boolean indicating whether we will
-   * restore a session that ends up replacing the homepage. The browser uses
-   * this to not start loading the homepage if we're going to stop its load
-   * anyway shortly after.
+   * Returns a promise that resolves to a boolean, indicating whether we will
+   * restore a session that ends up replacing the homepage. True guarantees
+   * that we'll restore a session; false means that we /probably/ won't do so.
+   * The browser uses this to avoid unnecessarily loading the homepage when
+   * restoring a session.
    */
   readonly attribute jsval willOverrideHomepagePromise;
 
   /**
    * What type of session we're restoring.
    * NO_SESSION       There is no data available from the previous session
    * RECOVER_SESSION  The last session crashed. It will either be restored or
    *                  about:sessionrestore will be shown.
--- a/browser/components/sessionstore/nsSessionStartup.js
+++ b/browser/components/sessionstore/nsSessionStartup.js
@@ -85,32 +85,38 @@ SessionStartup.prototype = {
   // the state to restore at startup
   _initialState: null,
   _sessionType: Ci.nsISessionStartup.NO_SESSION,
   _initialized: false,
 
   // Stores whether the previous session crashed.
   _previousSessionCrashed: null,
 
+  _resumeSessionEnabled: null,
+
 /* ........ Global Event Handlers .............. */
 
   /**
    * Initialize the component
    */
   init: function sss_init() {
     Services.obs.notifyObservers(null, "sessionstore-init-started");
     StartupPerformance.init();
 
     // do not need to initialize anything in auto-started private browsing sessions
     if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
       this._initialized = true;
       gOnceInitializedDeferred.resolve();
       return;
     }
 
+    this._resumeSessionEnabled =
+      Services.prefs.getBoolPref("browser.sessionstore.resume_session_once") ||
+      Services.prefs.getIntPref("browser.startup.page") == BROWSER_STARTUP_RESUME_SESSION;
+
     SessionFile.read().then(
       this._onSessionFileRead.bind(this),
       console.error
     );
   },
 
   // Wrap a string as a nsISupports
   _createSupportsString: function ssfi_createSupportsString(aData) {
@@ -161,22 +167,18 @@ SessionStartup.prototype = {
       let pinnedTabCount = initialState.windows.reduce((winAcc, win) => {
         return winAcc + win.tabs.reduce((tabAcc, tab) => {
           return tabAcc + (tab.pinned ? 1 : 0);
         }, 0);
       }, 0);
       Services.telemetry.scalarSet("browser.engagement.restored_pinned_tabs_count", pinnedTabCount);
     }, 60000);
 
-    let shouldResumeSessionOnce = Services.prefs.getBoolPref("browser.sessionstore.resume_session_once");
-    let shouldResumeSession = shouldResumeSessionOnce ||
-          Services.prefs.getIntPref("browser.startup.page") == BROWSER_STARTUP_RESUME_SESSION;
-
     // If this is a normal restore then throw away any previous session
-    if (!shouldResumeSessionOnce && this._initialState) {
+    if (!this._resumeSessionEnabled && this._initialState) {
       delete this._initialState.lastSessionState;
     }
 
     let resumeFromCrash = Services.prefs.getBoolPref("browser.sessionstore.resume_from_crash");
 
     CrashMonitor.previousCheckpoints.then(checkpoints => {
       if (checkpoints) {
         // If the previous session finished writing the final state, we'll
@@ -211,17 +213,17 @@ SessionStartup.prototype = {
       // Report shutdown success via telemetry. Shortcoming here are
       // being-killed-by-OS-shutdown-logic, shutdown freezing after
       // session restore was written, etc.
       Services.telemetry.getHistogramById("SHUTDOWN_OK").add(!this._previousSessionCrashed);
 
       // set the startup type
       if (this._previousSessionCrashed && resumeFromCrash)
         this._sessionType = Ci.nsISessionStartup.RECOVER_SESSION;
-      else if (!this._previousSessionCrashed && shouldResumeSession)
+      else if (!this._previousSessionCrashed && this._resumeSessionEnabled)
         this._sessionType = Ci.nsISessionStartup.RESUME_SESSION;
       else if (this._initialState)
         this._sessionType = Ci.nsISessionStartup.DEFER_SESSION;
       else
         this._initialState = null; // reset the state
 
       Services.obs.addObserver(this, "sessionstore-windows-restored", true);
 
@@ -307,46 +309,41 @@ SessionStartup.prototype = {
    * @returns bool
    */
   _willRestore() {
     return this._sessionType == Ci.nsISessionStartup.RECOVER_SESSION ||
            this._sessionType == Ci.nsISessionStartup.RESUME_SESSION;
   },
 
   /**
-   * Returns whether we will restore a session that ends up replacing the
-   * homepage. The browser uses this to not start loading the homepage if
-   * we're going to stop its load anyway shortly after.
-   *
-   * This is meant to be an optimization for the average case that loading the
-   * session file finishes before we may want to start loading the default
-   * homepage. Should this be called before the session file has been read it
-   * will just return false.
-   *
-   * @returns bool
-   */
-  get willOverrideHomepage() {
-    if (this._initialState && this._willRestore()) {
-      let windows = this._initialState.windows || null;
-      // If there are valid windows with not only pinned tabs, signal that we
-      // will override the default homepage by restoring a session.
-      return windows && windows.some(w => w.tabs.some(t => !t.pinned));
-    }
-    return false;
-  },
-
-  /**
-   * Returns a promise that resolves to a boolean indicating whether we will
-   * restore a session that ends up replacing the homepage. The browser uses
-   * this to not start loading the homepage if we're going to stop its load
-   * anyway shortly after.
+   * Returns a promise that resolves to a boolean, indicating whether we will
+   * restore a session that ends up replacing the homepage. True guarantees
+   * that we'll restore a session; false means that we /probably/ won't do so.
+   * The browser uses this to avoid unnecessarily loading the homepage when
+   * restoring a session.
    */
   get willOverrideHomepagePromise() {
+    // If the session file hasn't been read yet and resuming the session isn't
+    // enabled via prefs, go ahead and load the homepage. We may still replace
+    // it when recovering from a crash, which we'll only know after reading the
+    // session file, but waiting for that would delay loading the homepage in
+    // the non-crash case.
+    if (!this._initialState && !this._resumeSessionEnabled) {
+      return Promise.resolve(false);
+    }
+
     return new Promise(resolve => {
-      resolve(this.willOverrideHomepage);
+      this.onceInitialized.then(() => {
+        // If there are valid windows with not only pinned tabs, signal that we
+        // will override the default homepage by restoring a session.
+        resolve(this._willRestore() &&
+                this._initialState &&
+                this._initialState.windows &&
+                this._initialState.windows.some(w => w.tabs.some(t => !t.pinned)));
+      });
     });
   },
 
   /**
    * Get the type of pending session store, if any.
    */
   get sessionType() {
     return this._sessionType;
--- a/browser/experiments/Experiments.jsm
+++ b/browser/experiments/Experiments.jsm
@@ -50,17 +50,16 @@ const PREF_LOGGING              = "loggi
 const PREF_LOGGING_LEVEL        = PREF_LOGGING + ".level"; // experiments.logging.level
 const PREF_LOGGING_DUMP         = PREF_LOGGING + ".dump"; // experiments.logging.dump
 const PREF_MANIFEST_URI         = "manifest.uri"; // experiments.logging.manifest.uri
 const PREF_FORCE_SAMPLE         = "force-sample-value"; // experiments.force-sample-value
 
 const PREF_TELEMETRY_ENABLED      = "toolkit.telemetry.enabled";
 
 const URI_EXTENSION_STRINGS     = "chrome://mozapps/locale/extensions/extensions.properties";
-const STRING_TYPE_NAME          = "type.%ID%.name";
 
 const CACHE_WRITE_RETRY_DELAY_SEC = 60 * 3;
 const MANIFEST_FETCH_TIMEOUT_MSEC = 60 * 3 * 1000; // 3 minutes
 
 const TELEMETRY_LOG = {
   // log(key, [kind, experimentId, details])
   ACTIVATION_KEY: "EXPERIMENT_ACTIVATION",
   ACTIVATION: {
@@ -533,17 +532,17 @@ Experiments.Experiments.prototype = {
     if (!gAddonProvider) {
       // The properties of this AddonType should be kept in sync with the
       // experiment AddonType registered in XPIProvider.
       this._log.trace("Registering previous experiment add-on provider.");
       gAddonProvider = previousExperimentsProvider || new Experiments.PreviousExperimentProvider(this);
       AddonManagerPrivate.registerProvider(gAddonProvider, [
           new AddonManagerPrivate.AddonType("experiment",
                                             URI_EXTENSION_STRINGS,
-                                            STRING_TYPE_NAME,
+                                            "type.experiment.name",
                                             AddonManager.VIEW_TYPE_LIST,
                                             11000,
                                             AddonManager.TYPE_UI_HIDE_EMPTY),
       ]);
     }
 
   },
 
--- a/browser/extensions/activity-stream/data/content/activity-stream.bundle.js
+++ b/browser/extensions/activity-stream/data/content/activity-stream.bundle.js
@@ -2278,17 +2278,17 @@ class Section extends React.Component {
           emptyState = _props.emptyState,
           dispatch = _props.dispatch,
           maxRows = _props.maxRows,
           contextMenuOptions = _props.contextMenuOptions,
           intl = _props.intl;
 
     const maxCards = 3 * maxRows;
     const initialized = rows && rows.length > 0;
-    const shouldShowTopics = id === "TopStories" && this.props.topics && this.props.read_more_endpoint;
+    const shouldShowTopics = id === "TopStories" && this.props.topics && this.props.topics.length > 0 && this.props.read_more_endpoint;
 
     const infoOptionIconA11yAttrs = {
       "aria-haspopup": "true",
       "aria-controls": "info-option",
       "aria-expanded": this.state.infoActive ? "true" : "false",
       "role": "note",
       "tabIndex": 0
     };
--- a/browser/extensions/activity-stream/lib/ActivityStreamMessageChannel.jsm
+++ b/browser/extensions/activity-stream/lib/ActivityStreamMessageChannel.jsm
@@ -6,16 +6,17 @@
 
 const {utils: Cu} = Components;
 Cu.import("resource:///modules/AboutNewTab.jsm");
 Cu.import("resource://gre/modules/RemotePageManager.jsm");
 
 const {actionCreators: ac, actionTypes: at, actionUtils: au} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
 
 const ABOUT_NEW_TAB_URL = "about:newtab";
+const ABOUT_HOME_URL = "about:home";
 
 const DEFAULT_OPTIONS = {
   dispatch(action) {
     throw new Error(`\nMessageChannel: Received action ${action.type}, but no dispatcher was defined.\n`);
   },
   pageURL: ABOUT_NEW_TAB_URL,
   outgoingMessageName: "ActivityStream:MainToContent",
   incomingMessageName: "ActivityStream:ContentToMain"
@@ -123,17 +124,17 @@ this.ActivityStreamMessageChannel = clas
 
   /**
    * createChannel - Create RemotePages channel to establishing message passing
    *                 between the main process and child pages
    */
   createChannel() {
     //  Receive AboutNewTab's Remote Pages instance, if it exists, on override
     const channel = this.pageURL === ABOUT_NEW_TAB_URL && AboutNewTab.override(true);
-    this.channel = channel || new RemotePages(this.pageURL);
+    this.channel = channel || new RemotePages([ABOUT_HOME_URL, ABOUT_NEW_TAB_URL]);
     this.channel.addMessageListener("RemotePage:Init", this.onNewTabInit);
     this.channel.addMessageListener("RemotePage:Load", this.onNewTabLoad);
     this.channel.addMessageListener("RemotePage:Unload", this.onNewTabUnload);
     this.channel.addMessageListener(this.incomingMessageName, this.onMessage);
 
     // Some pages might have already loaded, so we won't get the usual message
     for (const {loaded, portID} of this.channel.messagePorts) {
       if (loaded) {
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -27,16 +27,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://formautofill/FormAutofillHandler.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FormLikeFactory",
                                   "resource://gre/modules/FormLikeFactory.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
                                   "resource://gre/modules/InsecurePasswordUtils.jsm");
 
 const formFillController = Cc["@mozilla.org/satchel/form-fill-controller;1"]
                              .getService(Ci.nsIFormFillController);
+const {ADDRESSES_COLLECTION_NAME, CREDITCARDS_COLLECTION_NAME} = FormAutofillUtils;
 
 // Register/unregister a constructor as a factory.
 function AutocompleteFactory() {}
 AutocompleteFactory.prototype = {
   register(targetConstructor) {
     let proto = targetConstructor.prototype;
     this._classID = proto.classID;
 
@@ -98,35 +99,39 @@ AutofillProfileAutoCompleteSearch.protot
     let savedFieldNames = FormAutofillContent.savedFieldNames;
 
     let focusedInput = formFillController.focusedInput;
     let info = FormAutofillContent.getInputDetails(focusedInput);
     let isAddressField = FormAutofillUtils.isAddressField(info.fieldName);
     let handler = FormAutofillContent.getFormHandler(focusedInput);
     let allFieldNames = handler.allFieldNames;
     let filledRecordGUID = isAddressField ? handler.address.filledRecordGUID : handler.creditCard.filledRecordGUID;
+    let searchPermitted = isAddressField ?
+                          FormAutofillUtils.isAutofillAddressesEnabled :
+                          FormAutofillUtils.isAutofillCreditCardsEnabled;
 
     // Fallback to form-history if ...
+    //   - specified autofill feature is pref off.
     //   - no profile can fill the currently-focused input.
     //   - the current form has already been populated.
     //   - (address only) less than 3 inputs are covered by all saved fields in the storage.
-    if (!savedFieldNames.has(info.fieldName) || filledRecordGUID || (isAddressField &&
+    if (!searchPermitted || !savedFieldNames.has(info.fieldName) || filledRecordGUID || (isAddressField &&
         allFieldNames.filter(field => savedFieldNames.has(field)).length < FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD)) {
       let formHistory = Cc["@mozilla.org/autocomplete/search;1?name=form-history"]
                           .createInstance(Ci.nsIAutoCompleteSearch);
       formHistory.startSearch(searchString, searchParam, previousResult, {
         onSearchResult: (search, result) => {
           listener.onSearchResult(this, result);
           ProfileAutocomplete.setProfileAutoCompleteResult(result);
         },
       });
       return;
     }
 
-    let collectionName = isAddressField ? "addresses" : "creditCards";
+    let collectionName = isAddressField ? ADDRESSES_COLLECTION_NAME : CREDITCARDS_COLLECTION_NAME;
 
     this._getRecords({collectionName, info, searchString}).then((records) => {
       if (this.forceStop) {
         return;
       }
       // Sort addresses by timeLastUsed for showing the lastest used address at top.
       records.sort((a, b) => b.timeLastUsed - a.timeLastUsed);
 
@@ -336,22 +341,23 @@ var FormAutofillContent = {
   init() {
     FormAutofillUtils.defineLazyLogGetter(this, "FormAutofillContent");
 
     Services.cpmm.addMessageListener("FormAutofill:enabledStatus", this);
     Services.cpmm.addMessageListener("FormAutofill:savedFieldNames", this);
     Services.obs.addObserver(this, "earlyformsubmit");
 
     let autofillEnabled = Services.cpmm.initialProcessData.autofillEnabled;
-    if (autofillEnabled ||
-        // If storage hasn't be initialized yet autofillEnabled is undefined but we need to ensure
-        // autocomplete is registered before the focusin so register it in this case as long as the
-        // pref is true.
-        (autofillEnabled === undefined &&
-         Services.prefs.getBoolPref("extensions.formautofill.addresses.enabled"))) {
+    // If storage hasn't be initialized yet autofillEnabled is undefined but we need to ensure
+    // autocomplete is registered before the focusin so register it in this case as long as the
+    // pref is true.
+    let shouldEnableAutofill = autofillEnabled === undefined &&
+                               (FormAutofillUtils.isAutofillAddressesEnabled ||
+                               FormAutofillUtils.isAutofillCreditCardsEnabled);
+    if (autofillEnabled || shouldEnableAutofill) {
       ProfileAutocomplete.ensureRegistered();
     }
 
     this.savedFieldNames =
       Services.cpmm.initialProcessData.autofillSavedFieldNames;
   },
 
   /**
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -45,17 +45,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "MasterPassword",
                                   "resource://formautofill/MasterPassword.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
                                   "resource:///modules/RecentWindow.jsm");
 
 this.log = null;
 FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
 
-const ENABLED_PREF = "extensions.formautofill.addresses.enabled";
+const {ENABLED_AUTOFILL_ADDRESSES_PREF, ENABLED_AUTOFILL_CREDITCARDS_PREF} = FormAutofillUtils;
 
 function FormAutofillParent() {
   // Lazily load the storage JSM to avoid disk I/O until absolutely needed.
   // Once storage is loaded we need to update saved field names and inform content processes.
   XPCOMUtils.defineLazyGetter(this, "profileStorage", () => {
     let {profileStorage} = Cu.import("resource://formautofill/ProfileStorage.jsm", {});
     log.debug("Loading profileStorage");
 
@@ -86,17 +86,18 @@ FormAutofillParent.prototype = {
     Services.ppmm.addMessageListener("FormAutofill:SaveAddress", this);
     Services.ppmm.addMessageListener("FormAutofill:SaveCreditCard", this);
     Services.ppmm.addMessageListener("FormAutofill:RemoveAddresses", this);
     Services.ppmm.addMessageListener("FormAutofill:RemoveCreditCards", this);
     Services.ppmm.addMessageListener("FormAutofill:OpenPreferences", this);
     Services.mm.addMessageListener("FormAutofill:OnFormSubmit", this);
 
     // Observing the pref and storage changes
-    Services.prefs.addObserver(ENABLED_PREF, this);
+    Services.prefs.addObserver(ENABLED_AUTOFILL_ADDRESSES_PREF, this);
+    Services.prefs.addObserver(ENABLED_AUTOFILL_CREDITCARDS_PREF, this);
     Services.obs.addObserver(this, "formautofill-storage-changed");
   },
 
   observe(subject, topic, data) {
     log.debug("observe:", topic, "with data:", data);
     switch (topic) {
       case "advanced-pane-loaded": {
         let useOldOrganization = Services.prefs.getBoolPref("browser.preferences.useOldOrganization",
@@ -149,21 +150,22 @@ FormAutofillParent.prototype = {
 
   /**
    * Query preference and storage status to determine the overall status of the
    * form autofill feature.
    *
    * @returns {boolean} whether form autofill is active (enabled and has data)
    */
   _computeStatus() {
-    if (!Services.prefs.getBoolPref(ENABLED_PREF)) {
-      return false;
-    }
+    const savedFieldNames = Services.ppmm.initialProcessData.autofillSavedFieldNames;
 
-    return Services.ppmm.initialProcessData.autofillSavedFieldNames.size > 0;
+    return (Services.prefs.getBoolPref(ENABLED_AUTOFILL_ADDRESSES_PREF) ||
+           Services.prefs.getBoolPref(ENABLED_AUTOFILL_CREDITCARDS_PREF)) &&
+           savedFieldNames &&
+           savedFieldNames.size > 0;
   },
 
   /**
    * Update the status and trigger _onStatusChanged, if necessary.
    */
   _updateStatus() {
     let wasActive = this._active;
     this._active = this._computeStatus();
@@ -231,17 +233,18 @@ FormAutofillParent.prototype = {
 
     Services.ppmm.removeMessageListener("FormAutofill:InitStorage", this);
     Services.ppmm.removeMessageListener("FormAutofill:GetRecords", this);
     Services.ppmm.removeMessageListener("FormAutofill:SaveAddress", this);
     Services.ppmm.removeMessageListener("FormAutofill:SaveCreditCard", this);
     Services.ppmm.removeMessageListener("FormAutofill:RemoveAddresses", this);
     Services.ppmm.removeMessageListener("FormAutofill:RemoveCreditCards", this);
     Services.obs.removeObserver(this, "advanced-pane-loaded");
-    Services.prefs.removeObserver(ENABLED_PREF, this);
+    Services.prefs.removeObserver(ENABLED_AUTOFILL_ADDRESSES_PREF, this);
+    Services.prefs.removeObserver(ENABLED_AUTOFILL_CREDITCARDS_PREF, this);
   },
 
   /**
    * Get the records from profile store and return results back to content
    * process.
    *
    * @private
    * @param  {string} data.collectionName
--- a/browser/extensions/formautofill/FormAutofillPreferences.jsm
+++ b/browser/extensions/formautofill/FormAutofillPreferences.jsm
@@ -8,48 +8,40 @@
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["FormAutofillPreferences"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 // Add addresses enabled flag in telemetry environment for recording the number of
 // users who disable/enable the address autofill feature.
-const PREF_AUTOFILL_ENABLED = "extensions.formautofill.addresses.enabled";
-// Add credit card enabled flag in telemetry environment for recording the number of
-// users who disable/enable the credit card autofill feature.
-// TODO: Add const PREF_CREDITCARD_ENABLED = "extensions.formautofill.creditCards.enabled";
-//       when the credit card preferences UI is ready
 const BUNDLE_URI = "chrome://formautofill/locale/formautofill.properties";
 const MANAGE_ADDRESSES_URL = "chrome://formautofill/content/manageAddresses.xhtml";
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://formautofill/FormAutofillUtils.jsm");
 
+const {ENABLED_AUTOFILL_ADDRESSES_PREF} = FormAutofillUtils;
+// Add credit card enabled flag in telemetry environment for recording the number of
+// users who disable/enable the credit card autofill feature.
+// TODO: Add const PREF_CREDITCARD_ENABLED = "extensions.formautofill.creditCards.enabled";
+//       when the credit card preferences UI is ready
+
 this.log = null;
 FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
 
 function FormAutofillPreferences({useOldOrganization}) {
   this.useOldOrganization = useOldOrganization;
   this.bundle = Services.strings.createBundle(BUNDLE_URI);
 }
 
 FormAutofillPreferences.prototype = {
   /**
-   * Check if Form Autofill feature is enabled.
-   *
-   * @returns {boolean}
-   */
-  get isAutofillEnabled() {
-    return Services.prefs.getBoolPref(PREF_AUTOFILL_ENABLED);
-  },
-
-  /**
    * Create the Form Autofill preference group.
    *
    * @param   {XULDocument} document
    * @returns {XULElement}
    */
   init(document) {
     this.createPreferenceGroup(document);
     this.attachEventListeners();
@@ -99,17 +91,17 @@ FormAutofillPreferences.prototype = {
     };
 
     formAutofillGroup.id = "formAutofillGroup";
     addressAutofill.id = "addressAutofill";
     savedAddressesBtn.setAttribute("label", this.bundle.GetStringFromName("savedAddresses"));
     addressAutofillCheckbox.setAttribute("label", this.bundle.GetStringFromName("enableAddressAutofill"));
 
     // Manually set the checked state
-    if (this.isAutofillEnabled) {
+    if (FormAutofillUtils.isAutofillAddressesEnabled) {
       addressAutofillCheckbox.setAttribute("checked", true);
     }
 
     addressAutofillCheckbox.flex = 1;
 
     formAutofillGroup.appendChild(addressAutofill);
     addressAutofill.appendChild(addressAutofillCheckbox);
     addressAutofill.appendChild(savedAddressesBtn);
@@ -122,17 +114,17 @@ FormAutofillPreferences.prototype = {
    */
   handleEvent(event) {
     switch (event.type) {
       case "command": {
         let target = event.target;
 
         if (target == this.refs.addressAutofillCheckbox) {
           // Set preference directly instead of relying on <Preference>
-          Services.prefs.setBoolPref(PREF_AUTOFILL_ENABLED, target.checked);
+          Services.prefs.setBoolPref(ENABLED_AUTOFILL_ADDRESSES_PREF, target.checked);
         } else if (target == this.refs.savedAddressesBtn) {
           target.ownerGlobal.gSubDialog.open(MANAGE_ADDRESSES_URL);
         }
         break;
       }
     }
   },
 
--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -11,22 +11,32 @@ const {classes: Cc, interfaces: Ci, util
 const ADDRESS_REFERENCES = "chrome://formautofill/content/addressReferences.js";
 
 // TODO: We only support US in MVP. We are going to support more countries in
 //       bug 1370193.
 const ALTERNATIVE_COUNTRY_NAMES = {
   "US": ["US", "United States of America", "United States", "America", "U.S.", "USA", "U.S.A.", "U.S.A"],
 };
 
+const ADDRESSES_COLLECTION_NAME = "addresses";
+const CREDITCARDS_COLLECTION_NAME = "creditCards";
+const ENABLED_AUTOFILL_ADDRESSES_PREF = "extensions.formautofill.addresses.enabled";
+const ENABLED_AUTOFILL_CREDITCARDS_PREF = "extensions.formautofill.creditCards.enabled";
+
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 this.FormAutofillUtils = {
   get AUTOFILL_FIELDS_THRESHOLD() { return 3; },
 
+  ADDRESSES_COLLECTION_NAME,
+  CREDITCARDS_COLLECTION_NAME,
+  ENABLED_AUTOFILL_ADDRESSES_PREF,
+  ENABLED_AUTOFILL_CREDITCARDS_PREF,
+
   _fieldNameInfo: {
     "name": "name",
     "given-name": "name",
     "additional-name": "name",
     "family-name": "name",
     "organization": "organization",
     "street-address": "address",
     "address-line1": "address",
@@ -59,16 +69,23 @@ this.FormAutofillUtils = {
   isAddressField(fieldName) {
     return !!this._fieldNameInfo[fieldName] && !this.isCreditCardField(fieldName);
   },
 
   isCreditCardField(fieldName) {
     return this._fieldNameInfo[fieldName] == "creditCard";
   },
 
+  isCCNumber(ccNumber) {
+    // Based on the information on wiki[1], the shortest valid length should be
+    // 12 digits(Maestro).
+    // [1] https://en.wikipedia.org/wiki/Payment_card_number
+    return ccNumber ? ccNumber.replace(/\s/g, "").match(/^\d{12,}$/) : false;
+  },
+
   getCategoryFromFieldName(fieldName) {
     return this._fieldNameInfo[fieldName];
   },
 
   getCategoriesFromFieldNames(fieldNames) {
     let categories = new Set();
     for (let fieldName of fieldNames) {
       let info = this.getCategoryFromFieldName(fieldName);
@@ -466,8 +483,13 @@ XPCOMUtils.defineLazyGetter(this.FormAut
 });
 
 this.log = null;
 this.FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
 
 XPCOMUtils.defineLazyGetter(FormAutofillUtils, "stringBundle", function() {
   return Services.strings.createBundle("chrome://formautofill/locale/formautofill.properties");
 });
+
+XPCOMUtils.defineLazyPreferenceGetter(this.FormAutofillUtils,
+                                      "isAutofillAddressesEnabled", ENABLED_AUTOFILL_ADDRESSES_PREF);
+XPCOMUtils.defineLazyPreferenceGetter(this.FormAutofillUtils,
+                                      "isAutofillCreditCardsEnabled", ENABLED_AUTOFILL_CREDITCARDS_PREF);
--- a/browser/extensions/formautofill/ProfileStorage.jsm
+++ b/browser/extensions/formautofill/ProfileStorage.jsm
@@ -1508,25 +1508,18 @@ class CreditCards extends AutofillRecord
     // Fields that should not be set by content.
     delete creditCard["cc-number-encrypted"];
 
     // Validate and encrypt credit card numbers, and calculate the masked numbers
     if (creditCard["cc-number"]) {
       let ccNumber = creditCard["cc-number"].replace(/\s/g, "");
       delete creditCard["cc-number"];
 
-      if (!/^\d+$/.test(ccNumber)) {
-        throw new Error("Credit card number contains invalid characters.");
-      }
-
-      // Based on the information on wiki[1], the shortest valid length should be
-      // 12 digits(Maestro).
-      // [1] https://en.wikipedia.org/wiki/Payment_card_number
-      if (ccNumber.length < 12) {
-        throw new Error("Invalid credit card number because length is under 12 digits.");
+      if (!FormAutofillUtils.isCCNumber(ccNumber)) {
+        throw new Error("Credit card number contains invalid characters or is under 12 digits.");
       }
 
       creditCard["cc-number-encrypted"] = await MasterPassword.encrypt(ccNumber);
       creditCard["cc-number"] = "*".repeat(ccNumber.length - 4) + ccNumber.substr(-4);
     }
   }
 }
 
--- a/browser/extensions/formautofill/content/FormAutofillFrameScript.js
+++ b/browser/extensions/formautofill/content/FormAutofillFrameScript.js
@@ -30,17 +30,18 @@ var FormAutofillFrameScript = {
     addMessageListener("FormAutoComplete:PopupOpened", this);
   },
 
   handleEvent(evt) {
     if (!evt.isTrusted) {
       return;
     }
 
-    if (!Services.prefs.getBoolPref("extensions.formautofill.addresses.enabled")) {
+    if (!FormAutofillUtils.isAutofillAddressesEnabled &&
+        !FormAutofillUtils.isAutofillCreditCardsEnable) {
       return;
     }
 
     switch (evt.type) {
       case "focusin": {
         let element = evt.target;
         let doc = element.ownerDocument;
 
@@ -57,17 +58,18 @@ var FormAutofillFrameScript = {
           doIdentifyAutofillFields();
         }
         break;
       }
     }
   },
 
   receiveMessage(message) {
-    if (!Services.prefs.getBoolPref("extensions.formautofill.addresses.enabled")) {
+    if (!FormAutofillUtils.isAutofillAddressesEnabled &&
+        !FormAutofillUtils.isAutofillCreditCardsEnable) {
       return;
     }
 
     const doc = content.document;
     const {chromeEventHandler} = doc.ownerGlobal.getInterface(Ci.nsIDocShell);
 
     switch (message.name) {
       case "FormAutofill:PreviewProfile": {
--- a/browser/extensions/formautofill/content/editAddress.xhtml
+++ b/browser/extensions/formautofill/content/editAddress.xhtml
@@ -1,22 +1,23 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 <!DOCTYPE html>
 <html xmlns="http://www.w3.org/1999/xhtml" width="620">
 <head>
-  <title data-localization="addNewDialogTitle"/>
-  <link rel="stylesheet" href="chrome://formautofill-shared/skin/editAddress.css" />
-  <link rel="stylesheet" href="chrome://formautofill/skin/editAddress.css" />
-  <script src="chrome://formautofill/content/editAddress.js"></script>
+  <title data-localization="addNewAddressTitle"/>
+  <link rel="stylesheet" href="chrome://formautofill-shared/skin/editDialog.css"/>
+  <link rel="stylesheet" href="chrome://formautofill-shared/skin/editAddress.css"/>
+  <link rel="stylesheet" href="chrome://formautofill/skin/editDialog.css"/>
+  <script src="chrome://formautofill/content/editDialog.js"></script>
 </head>
 <body>
-  <form autocomplete="off">
+  <form id="form" autocomplete="off">
     <label id="given-name-container">
       <span data-localization="givenName"/>
       <input id="given-name" type="text"/>
     </label>
     <label id="additional-name-container">
       <span data-localization="additionalName"/>
       <input id="additional-name" type="text"/>
     </label>
@@ -62,13 +63,22 @@
     </label>
   </form>
   <div id="controls-container">
     <button id="cancel" data-localization="cancel"/>
     <button id="save" disabled="disabled" data-localization="save"/>
   </div>
   <script type="application/javascript"><![CDATA[
     "use strict";
-    // Localize strings before DOMContentLoaded to prevent flash
-    window.dialog.localizeDocument();
+    /* global EditAddress */
+    new EditAddress({
+      title: document.querySelector("title"),
+      form: document.getElementById("form"),
+      addressLevel1Label: document.querySelector("#address-level1-container > span"),
+      postalCodeLabel: document.querySelector("#postal-code-container > span"),
+      country: document.getElementById("country"),
+      controlsContainer: document.getElementById("controls-container"),
+      cancel: document.getElementById("cancel"),
+      save: document.getElementById("save"),
+    }, window.arguments && window.arguments[0]);
   ]]></script>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/content/editCreditCard.xhtml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" width="500" style="width: 500px">
+<head>
+  <title data-localization="addNewCreditCardTitle"/>
+  <link rel="stylesheet" href="chrome://formautofill-shared/skin/editDialog.css"/>
+  <link rel="stylesheet" href="chrome://formautofill-shared/skin/editCreditCard.css"/>
+  <link rel="stylesheet" href="chrome://formautofill/skin/editDialog.css"/>
+  <script src="chrome://formautofill/content/editDialog.js"></script>
+</head>
+<body>
+  <form id="form" autocomplete="off">
+    <label>
+      <span data-localization="cardNumber"/>
+      <input id="cc-number" type="text"/>
+    </label>
+    <label>
+      <span data-localization="nameOnCard"/>
+      <input id="cc-name" type="text"/>
+    </label>
+    <div>
+      <span data-localization="cardExpires"/>
+      <select id="cc-exp-month">
+        <option/>
+        <option value="1">01</option>
+        <option value="2">02</option>
+        <option value="3">03</option>
+        <option value="4">04</option>
+        <option value="5">05</option>
+        <option value="6">06</option>
+        <option value="7">07</option>
+        <option value="8">08</option>
+        <option value="9">09</option>
+        <option value="10">10</option>
+        <option value="11">11</option>
+        <option value="12">12</option>
+      </select>
+      <select id="cc-exp-year">
+        <option/>
+      </select>
+    </div>
+  </form>
+  <div id="controls-container">
+    <button id="cancel" data-localization="cancel"/>
+    <button id="save" disabled="disabled" data-localization="save"/>
+  </div>
+  <script type="application/javascript"><![CDATA[
+    "use strict";
+    /* global EditCreditCard */
+    new EditCreditCard({
+      title: document.querySelector("title"),
+      form: document.getElementById("form"),
+      ccNumber: document.getElementById("cc-number"),
+      year: document.getElementById("cc-exp-year"),
+      controlsContainer: document.getElementById("controls-container"),
+      cancel: document.getElementById("cancel"),
+      save: document.getElementById("save"),
+    }, window.arguments && window.arguments[0]);
+  ]]></script>
+</body>
+</html>
rename from browser/extensions/formautofill/content/editAddress.js
rename to browser/extensions/formautofill/content/editDialog.js
--- a/browser/extensions/formautofill/content/editAddress.js
+++ b/browser/extensions/formautofill/content/editDialog.js
@@ -1,201 +1,280 @@
 /* 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 EditAddress, EditCreditCard */
+
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 const AUTOFILL_BUNDLE_URI = "chrome://formautofill/locale/formautofill.properties";
 const REGIONS_BUNDLE_URI = "chrome://global/locale/regionNames.properties";
 
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://formautofill/FormAutofillUtils.jsm");
 
-function EditDialog() {
-  this._address = window.arguments && window.arguments[0];
-  window.addEventListener("DOMContentLoaded", this, {once: true});
-}
+XPCOMUtils.defineLazyModuleGetter(this, "profileStorage",
+                                  "resource://formautofill/ProfileStorage.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "MasterPassword",
+                                  "resource://formautofill/MasterPassword.jsm");
 
-EditDialog.prototype = {
-  get _elements() {
-    if (this._elementRefs) {
-      return this._elementRefs;
+class EditDialog {
+  constructor(subStorageName, elements, record) {
+    this._storageInitPromise = profileStorage.initialize();
+    this._subStorageName = subStorageName;
+    this._elements = elements;
+    this._record = record;
+    this.localizeDocument();
+    window.addEventListener("DOMContentLoaded", this, {once: true});
+  }
+
+  async init() {
+    if (this._record) {
+      await this.loadInitialValues(this._record);
     }
-    this._elementRefs = {
-      title: document.querySelector("title"),
-      addressLevel1Label: document.querySelector("#address-level1-container > span"),
-      postalCodeLabel: document.querySelector("#postal-code-container > span"),
-      country: document.getElementById("country"),
-      controlsContainer: document.getElementById("controls-container"),
-      cancel: document.getElementById("cancel"),
-      save: document.getElementById("save"),
-    };
-    return this._elementRefs;
-  },
-
-  set _elements(refs) {
-    this._elementRefs = refs;
-  },
-
-  init() {
     this.attachEventListeners();
-  },
+    // For testing only: loadInitialValues for credit card is an async method, and tests
+    // need to wait until the values have been filled before editing the fields.
+    window.dispatchEvent(new CustomEvent("FormReady"));
+  }
 
   uninit() {
     this.detachEventListeners();
     this._elements = null;
-  },
+  }
+
+  /**
+   * Fill the form with a record object.
+   * @param  {object} record
+   */
+  loadInitialValues(record) {
+    for (let field in record) {
+      let input = document.getElementById(field);
+      if (input) {
+        input.value = record[field];
+      }
+    }
+  }
+
+  /**
+   * Get inputs from the form.
+   * @returns {object}
+   */
+  buildFormObject() {
+    return Array.from(document.forms[0].elements).reduce((obj, input) => {
+      if (input.value) {
+        obj[input.id] = input.value;
+      }
+      return obj;
+    }, {});
+  }
+
+  /**
+   * Get storage and ensure it has been initialized.
+   * @returns {object}
+   */
+  async getStorage() {
+    await this._storageInitPromise;
+    return profileStorage[this._subStorageName];
+  }
+
+  /**
+   * Asks FormAutofillParent to save or update an record.
+   * @param  {object} record
+   * @param  {string} guid [optional]
+   */
+  async saveRecord(record, guid) {
+    let storage = await this.getStorage();
+    if (guid) {
+      storage.update(guid, record);
+    } else {
+      storage.add(record);
+    }
+  }
+
+  /**
+   * Handle events
+   *
+   * @param  {DOMEvent} event
+   */
+  handleEvent(event) {
+    switch (event.type) {
+      case "DOMContentLoaded": {
+        this.init();
+        break;
+      }
+      case "unload": {
+        this.uninit();
+        break;
+      }
+      case "click": {
+        this.handleClick(event);
+        break;
+      }
+      case "input": {
+        this.handleInput(event);
+        break;
+      }
+      case "keypress": {
+        this.handleKeyPress(event);
+        break;
+      }
+    }
+  }
+
+  /**
+   * Handle click events
+   *
+   * @param  {DOMEvent} event
+   */
+  handleClick(event) {
+    if (event.target == this._elements.cancel) {
+      window.close();
+    }
+    if (event.target == this._elements.save) {
+      this.handleSubmit();
+    }
+  }
+
+  /**
+   * Handle input events
+   *
+   * @param  {DOMEvent} event
+   */
+  handleInput(event) {
+    // Toggle disabled attribute on the save button based on
+    // whether the form is filled or empty.
+    if (Object.keys(this.buildFormObject()).length == 0) {
+      this._elements.save.setAttribute("disabled", true);
+    } else {
+      this._elements.save.removeAttribute("disabled");
+    }
+  }
+
+  /**
+   * Handle key press events
+   *
+   * @param  {DOMEvent} event
+   */
+  handleKeyPress(event) {
+    if (event.keyCode == KeyEvent.DOM_VK_ESCAPE) {
+      window.close();
+    }
+  }
+
+  /**
+   * Attach event listener
+   */
+  attachEventListeners() {
+    window.addEventListener("keypress", this);
+    this._elements.controlsContainer.addEventListener("click", this);
+    document.addEventListener("input", this);
+  }
+
+  /**
+   * Remove event listener
+   */
+  detachEventListeners() {
+    window.removeEventListener("keypress", this);
+    this._elements.controlsContainer.removeEventListener("click", this);
+    document.removeEventListener("input", this);
+  }
+}
+
+class EditAddress extends EditDialog {
+  constructor(elements, record) {
+    super("addresses", elements, record);
+    this.formatForm(record && record.country);
+  }
 
   /**
    * Format the form based on country. The address-level1 and postal-code labels
    * should be specific to the given country.
    * @param  {string} country
    */
   formatForm(country) {
     // TODO: Use fmt to show/hide and order fields (Bug 1383687)
     const {addressLevel1Label, postalCodeLabel} = FormAutofillUtils.getFormFormat(country);
     this._elements.addressLevel1Label.dataset.localization = addressLevel1Label;
     this._elements.postalCodeLabel.dataset.localization = postalCodeLabel;
     FormAutofillUtils.localizeMarkup(AUTOFILL_BUNDLE_URI, document);
-  },
+  }
 
   localizeDocument() {
-    if (this._address) {
-      this._elements.title.dataset.localization = "editDialogTitle";
+    if (this._record) {
+      this._elements.title.dataset.localization = "editAddressTitle";
     }
     FormAutofillUtils.localizeMarkup(REGIONS_BUNDLE_URI, this._elements.country);
-    this.formatForm(this._address && this._address.country);
-  },
+  }
+
+  async handleSubmit() {
+    await this.saveRecord(this.buildFormObject(), this._record ? this._record.guid : null);
+    window.close();
+  }
+}
 
-  /**
-   * Asks FormAutofillParent to save or update an address.
-   * @param  {object} data
-   *         {
-   *           {string} guid [optional]
-   *           {object} address
-   *         }
-   */
-  saveAddress(data) {
-    Services.cpmm.sendAsyncMessage("FormAutofill:SaveAddress", data);
-  },
+class EditCreditCard extends EditDialog {
+  constructor(elements, record) {
+    super("creditCards", elements, record);
+    this.generateYears();
+  }
+
+  generateYears() {
+    const count = 11;
+    const currentYear = new Date().getFullYear();
+    const ccExpYear = this._record && this._record["cc-exp-year"];
 
-  /**
-   * Fill the form with an address object.
-   * @param  {object} address
-   */
-  loadInitialValues(address) {
-    for (let field in address) {
-      let input = document.getElementById(field);
-      if (input) {
-        input.value = address[field];
-      }
+    if (ccExpYear && ccExpYear < currentYear) {
+      this._elements.year.appendChild(new Option(ccExpYear));
     }
-  },
+
+    for (let i = 0; i < count; i++) {
+      let year = currentYear + i;
+      let option = new Option(year);
+      this._elements.year.appendChild(option);
+    }
 
-  /**
-   * Get inputs from the form.
-   * @returns {object}
-   */
-  buildAddressObject() {
-    return Array.from(document.forms[0].elements).reduce((obj, input) => {
-      if (input.value) {
-        obj[input.id] = input.value;
-      }
-      return obj;
-    }, {});
-  },
+    if (ccExpYear && ccExpYear > currentYear + count) {
+      this._elements.year.appendChild(new Option(ccExpYear));
+    }
+  }
+
+  localizeDocument() {
+    if (this._record) {
+      this._elements.title.dataset.localization = "editCreditCardTitle";
+    }
+    FormAutofillUtils.localizeMarkup(AUTOFILL_BUNDLE_URI, document);
+  }
 
   /**
-   * Handle events
-   *
-   * @param  {DOMEvent} event
+   * Decrypt cc-number first and fill the form.
+   * @param  {object} creditCard
    */
-  handleEvent(event) {
-    switch (event.type) {
-      case "DOMContentLoaded": {
-        this.init();
-        if (this._address) {
-          this.loadInitialValues(this._address);
-        }
-        break;
-      }
-      case "click": {
-        this.handleClick(event);
-        break;
-      }
-      case "input": {
-        // Toggle disabled attribute on the save button based on
-        // whether the form is filled or empty.
-        if (Object.keys(this.buildAddressObject()).length == 0) {
-          this._elements.save.setAttribute("disabled", true);
-        } else {
-          this._elements.save.removeAttribute("disabled");
-        }
-        break;
-      }
-      case "unload": {
-        this.uninit();
-        break;
-      }
-      case "keypress": {
-        this.handleKeyPress(event);
-        break;
-      }
-    }
-  },
+  async loadInitialValues(creditCard) {
+    let decryptedCC = await MasterPassword.decrypt(creditCard["cc-number-encrypted"]);
+    super.loadInitialValues(Object.assign({}, creditCard, {"cc-number": decryptedCC}));
+  }
 
-  /**
-   * Handle click events
-   *
-   * @param  {DOMEvent} event
-   */
-  handleClick(event) {
-    if (event.target == this._elements.cancel) {
-      window.close();
+  async handleSubmit() {
+    let creditCard = this.buildFormObject();
+    // Show error on the cc-number field if it's empty or invalid
+    if (!FormAutofillUtils.isCCNumber(creditCard["cc-number"])) {
+      this._elements.ccNumber.setCustomValidity(true);
+      return;
     }
-    if (event.target == this._elements.save) {
-      if (this._address) {
-        this.saveAddress({
-          guid: this._address.guid,
-          address: this.buildAddressObject(),
-        });
-      } else {
-        this.saveAddress({
-          address: this.buildAddressObject(),
-        });
-      }
-      window.close();
-    }
-  },
+    let storage = await this.getStorage();
+    await storage.normalizeCCNumberFields(creditCard);
+    await this.saveRecord(creditCard, this._record ? this._record.guid : null);
+    window.close();
+  }
 
-  /**
-   * Handle key press events
-   *
-   * @param  {DOMEvent} event
-   */
-  handleKeyPress(event) {
-    if (event.keyCode == KeyEvent.DOM_VK_ESCAPE) {
-      window.close();
+  handleInput(event) {
+    // Clear the error message if cc-number is valid
+    if (event.target == this._elements.ccNumber &&
+        FormAutofillUtils.isCCNumber(this._elements.ccNumber.value)) {
+      this._elements.ccNumber.setCustomValidity("");
     }
-  },
-
-  /**
-   * Attach event listener
-   */
-  attachEventListeners() {
-    window.addEventListener("keypress", this);
-    this._elements.controlsContainer.addEventListener("click", this);
-    document.addEventListener("input", this);
-  },
-
-  /**
-   * Remove event listener
-   */
-  detachEventListeners() {
-    window.removeEventListener("keypress", this);
-    this._elements.controlsContainer.removeEventListener("click", this);
-    document.removeEventListener("input", this);
-  },
-};
-
-window.dialog = new EditDialog();
+    super.handleInput(event);
+  }
+}
--- a/browser/extensions/formautofill/locales/en-US/formautofill.properties
+++ b/browser/extensions/formautofill/locales/en-US/formautofill.properties
@@ -34,18 +34,18 @@ phishingWarningMessage = Also autofills 
 phishingWarningMessage2 = Autofills %S
 
 manageDialogTitle = Saved Addresses
 addressListHeader = Addresses
 remove = Remove
 add = Add…
 edit = Edit…
 
-addNewDialogTitle = Add New Address
-editDialogTitle = Edit Address
+addNewAddressTitle = Add New Address
+editAddressTitle = Edit Address
 givenName = First Name
 additionalName = Middle Name
 familyName = Last Name
 organization = Company
 streetAddress = Street Address
 city = City
 province = Province
 state = State
@@ -53,9 +53,15 @@ postalCode = Postal Code
 zip = Zip Code
 country = Country or Region
 tel = Phone
 email = Email
 cancel = Cancel
 save = Save
 countryWarningMessage = Autofill is currently available only for US addresses
 
+addNewCreditCardTitle = Add New Credit Card
+editCreditCardTitle = Edit Credit Card
+cardNumber = Card Number
+nameOnCard = Name on Card
+cardExpires = Expires
+
 insecureFieldWarningDescription = %S has detected an insecure site. Credit card autofill is temporarily disabled
rename from browser/extensions/formautofill/skin/linux/editAddress.css
rename to browser/extensions/formautofill/skin/linux/editDialog.css
rename from browser/extensions/formautofill/skin/osx/editAddress.css
rename to browser/extensions/formautofill/skin/osx/editDialog.css
--- a/browser/extensions/formautofill/skin/shared/editAddress.css
+++ b/browser/extensions/formautofill/skin/shared/editAddress.css
@@ -1,85 +1,39 @@
 /* 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/. */
 
-body {
-  font-size: 1rem;
-}
-
-form,
-label,
-div,
-p {
-  display: flex;
-}
-
-form {
-  flex-wrap: wrap;
-}
-
-label,
-p {
-  margin: 0 0 0.5em;
-}
 
 label > span {
-  box-sizing: border-box;
   flex: 0 0 9.5em;
-  padding-inline-end: 0.5em;
-  align-self: center;
-  text-align: end;
-  -moz-user-select: none;
 }
 
 input,
 select {
-  box-sizing: border-box;
-  flex: 1 0 auto;
   width: calc(50% - 9.5em);
 }
 
-option {
-  padding: 5px 10px;
-}
-
-textarea {
-  resize: none;
-}
-
-button {
-  font-size: 1.2em;
-  padding: 3px 2em;
-  margin-inline-start: 10px;
-  margin-inline-end: 0;
-}
-
 #given-name-container,
 #additional-name-container,
 #address-level1-container,
 #postal-code-container,
 #country-container {
   flex: 0 1 50%;
 }
 
 #family-name-container,
 #organization-container,
 #street-address-container,
 #address-level2-container,
 #email-container,
-#tel-container,
-#controls-container {
+#tel-container {
   flex: 0 1 100%;
 }
 
-#controls-container {
-  justify-content: end;
-}
-
 #family-name,
 #organization,
 #address-level2,
 #tel {
   flex: 0 0 auto;
 }
 
 #street-address,
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/skin/shared/editCreditCard.css
@@ -0,0 +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/. */
+
+form {
+  justify-content: center;
+  /* Add extra space to ensure invalid input box is displayed properly */
+  padding: 2px;
+}
+
+form > label,
+form > div {
+  flex: 1 0 100%;
+  align-self: center;
+  font-size: 1.3em;
+  margin: 0 0 0.5em !important;
+}
+
+select {
+  flex: 0 0 5em;
+  margin: 0;
+  margin-inline-end: 0.7em;
+}
+
+label > span,
+div > span {
+  flex: 0 0 9.5em;
+}
copy from browser/extensions/formautofill/skin/shared/editAddress.css
copy to browser/extensions/formautofill/skin/shared/editDialog.css
--- a/browser/extensions/formautofill/skin/shared/editAddress.css
+++ b/browser/extensions/formautofill/skin/shared/editDialog.css
@@ -17,80 +17,43 @@ form {
   flex-wrap: wrap;
 }
 
 label,
 p {
   margin: 0 0 0.5em;
 }
 
-label > span {
+label > span,
+div > span {
   box-sizing: border-box;
-  flex: 0 0 9.5em;
-  padding-inline-end: 0.5em;
+  padding-inline-end: 0.7em;
   align-self: center;
   text-align: end;
   -moz-user-select: none;
 }
 
-input,
-select {
-  box-sizing: border-box;
-  flex: 1 0 auto;
-  width: calc(50% - 9.5em);
-}
-
 option {
-  padding: 5px 10px;
+  padding: 0.3em 0.5em;
 }
 
 textarea {
   resize: none;
 }
 
 button {
   font-size: 1.2em;
   padding: 3px 2em;
   margin-inline-start: 10px;
   margin-inline-end: 0;
 }
 
-#given-name-container,
-#additional-name-container,
-#address-level1-container,
-#postal-code-container,
-#country-container {
-  flex: 0 1 50%;
-}
-
-#family-name-container,
-#organization-container,
-#street-address-container,
-#address-level2-container,
-#email-container,
-#tel-container,
-#controls-container {
-  flex: 0 1 100%;
+input,
+select {
+  box-sizing: border-box;
+  flex: 1 0 auto;
 }
 
 #controls-container {
+  flex: 0 1 100%;
   justify-content: end;
-}
-
-#family-name,
-#organization,
-#address-level2,
-#tel {
-  flex: 0 0 auto;
+  margin: 1em 0 0;
 }
-
-#street-address,
-#email {
-  flex: 1 0 auto;
-}
-
-#country-warning-message {
-  flex: 1;
-  align-items: center;
-  text-align: start;
-  color: #737373;
-  padding-inline-start: 1em;
-}
rename from browser/extensions/formautofill/skin/windows/editAddress.css
rename to browser/extensions/formautofill/skin/windows/editDialog.css
--- a/browser/extensions/formautofill/test/browser/browser.ini
+++ b/browser/extensions/formautofill/test/browser/browser.ini
@@ -6,14 +6,15 @@ support-files =
   ../fixtures/autocomplete_creditcard_basic.html
 
 [browser_autocomplete_footer.js]
 [browser_autocomplete_marked_back_forward.js]
 [browser_autocomplete_marked_detached_tab.js]
 [browser_check_installed.js]
 [browser_dropdown_layout.js]
 [browser_editAddressDialog.js]
+[browser_editCreditCardDialog.js]
 [browser_first_time_use_doorhanger.js]
 [browser_insecure_form.js]
 [browser_manageAddressesDialog.js]
 [browser_privacyPreferences.js]
 [browser_submission_in_private_mode.js]
 [browser_update_doorhanger.js]
--- a/browser/extensions/formautofill/test/browser/browser_editAddressDialog.js
+++ b/browser/extensions/formautofill/test/browser/browser_editAddressDialog.js
@@ -1,13 +1,13 @@
 "use strict";
 
 add_task(async function test_cancelEditAddressDialog() {
   await new Promise(resolve => {
-    let win = window.openDialog(EDIT_ADDRESS_DIALOG_URL, null, null, null);
+    let win = window.openDialog(EDIT_ADDRESS_DIALOG_URL);
     win.addEventListener("load", () => {
       win.addEventListener("unload", () => {
         ok(true, "Edit address dialog is closed");
         resolve();
       }, {once: true});
       win.document.querySelector("#cancel").click();
     }, {once: true});
   });
@@ -23,17 +23,17 @@ add_task(async function test_cancelEditA
       }, {once: true});
       EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
     }, {once: true});
   });
 });
 
 add_task(async function test_saveAddress() {
   await new Promise(resolve => {
-    let win = window.openDialog(EDIT_ADDRESS_DIALOG_URL, null, null, null);
+    let win = window.openDialog(EDIT_ADDRESS_DIALOG_URL);
     win.addEventListener("load", () => {
       win.addEventListener("unload", () => {
         ok(true, "Edit address dialog is closed");
         resolve();
       }, {once: true});
       EventUtils.synthesizeKey("VK_TAB", {}, win);
       EventUtils.synthesizeKey(TEST_ADDRESS_1["given-name"], {}, win);
       EventUtils.synthesizeKey("VK_TAB", {}, win);
@@ -70,22 +70,23 @@ add_task(async function test_saveAddress
     is(addresses[0][fieldName], fieldValue, "check " + fieldName);
   }
 });
 
 add_task(async function test_editAddress() {
   let addresses = await getAddresses();
   await new Promise(resolve => {
     let win = window.openDialog(EDIT_ADDRESS_DIALOG_URL, null, null, addresses[0]);
-    win.addEventListener("load", () => {
+    win.addEventListener("FormReady", () => {
       win.addEventListener("unload", () => {
         ok(true, "Edit address dialog is closed");
         resolve();
       }, {once: true});
       EventUtils.synthesizeKey("VK_TAB", {}, win);
+      EventUtils.synthesizeKey("VK_RIGHT", {}, win);
       EventUtils.synthesizeKey("test", {}, win);
       win.document.querySelector("#save").click();
     }, {once: true});
   });
   addresses = await getAddresses();
 
   is(addresses.length, 1, "only one address is in storage");
   is(addresses[0]["given-name"], TEST_ADDRESS_1["given-name"] + "test", "given-name changed");
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/browser_editCreditCardDialog.js
@@ -0,0 +1,115 @@
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+
+"use strict";
+
+add_task(async function test_cancelEditCreditCardDialog() {
+  await new Promise(resolve => {
+    let win = window.openDialog(EDIT_CREDIT_CARD_DIALOG_URL);
+    win.addEventListener("load", () => {
+      win.addEventListener("unload", () => {
+        ok(true, "Edit credit card dialog is closed");
+        resolve();
+      }, {once: true});
+      win.document.querySelector("#cancel").click();
+    }, {once: true});
+  });
+});
+
+add_task(async function test_cancelEditCreditCardDialogWithESC() {
+  await new Promise(resolve => {
+    let win = window.openDialog(EDIT_CREDIT_CARD_DIALOG_URL);
+    win.addEventListener("load", () => {
+      win.addEventListener("unload", () => {
+        ok(true, "Edit credit card dialog is closed with ESC key");
+        resolve();
+      }, {once: true});
+      EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
+    }, {once: true});
+  });
+});
+
+add_task(async function test_saveCreditCard() {
+  await new Promise(resolve => {
+    let win = window.openDialog(EDIT_CREDIT_CARD_DIALOG_URL);
+    win.addEventListener("load", () => {
+      win.addEventListener("unload", () => {
+        ok(true, "Edit credit card dialog is closed");
+        resolve();
+      }, {once: true});
+      EventUtils.synthesizeKey("VK_TAB", {}, win);
+      EventUtils.synthesizeKey(TEST_CREDIT_CARD_1["cc-number"], {}, win);
+      EventUtils.synthesizeKey("VK_TAB", {}, win);
+      EventUtils.synthesizeKey(TEST_CREDIT_CARD_1["cc-name"], {}, win);
+      EventUtils.synthesizeKey("VK_TAB", {}, win);
+      EventUtils.synthesizeKey("0" + TEST_CREDIT_CARD_1["cc-exp-month"].toString(), {}, win);
+      EventUtils.synthesizeKey("VK_TAB", {}, win);
+      EventUtils.synthesizeKey(TEST_CREDIT_CARD_1["cc-exp-year"].toString(), {}, win);
+      EventUtils.synthesizeKey("VK_TAB", {}, win);
+      EventUtils.synthesizeKey("VK_TAB", {}, win);
+      info("saving credit card");
+      EventUtils.synthesizeKey("VK_RETURN", {}, win);
+    }, {once: true});
+  });
+  let creditCards = await getCreditCards();
+
+  is(creditCards.length, 1, "only one credit card is in storage");
+  for (let [fieldName, fieldValue] of Object.entries(TEST_CREDIT_CARD_1)) {
+    if (fieldName === "cc-number") {
+      fieldValue = "*".repeat(fieldValue.length - 4) + fieldValue.substr(-4);
+    }
+    is(creditCards[0][fieldName], fieldValue, "check " + fieldName);
+  }
+  ok(creditCards[0]["cc-number-encrypted"], "cc-number-encrypted exists");
+});
+
+add_task(async function test_editCreditCard() {
+  let creditCards = await getCreditCards();
+  is(creditCards.length, 1, "only one credit card is in storage");
+  await new Promise(resolve => {
+    let win = window.openDialog(EDIT_CREDIT_CARD_DIALOG_URL, null, null, creditCards[0]);
+    win.addEventListener("FormReady", () => {
+      win.addEventListener("unload", () => {
+        ok(true, "Edit credit card dialog is closed");
+        resolve();
+      }, {once: true});
+      EventUtils.synthesizeKey("VK_TAB", {}, win);
+      EventUtils.synthesizeKey("VK_TAB", {}, win);
+      EventUtils.synthesizeKey("VK_RIGHT", {}, win);
+      EventUtils.synthesizeKey("test", {}, win);
+      win.document.querySelector("#save").click();
+    }, {once: true});
+  });
+  creditCards = await getCreditCards();
+
+  is(creditCards.length, 1, "only one credit card is in storage");
+  is(creditCards[0]["cc-name"], TEST_CREDIT_CARD_1["cc-name"] + "test", "cc name changed");
+  await removeCreditCards([creditCards[0].guid]);
+
+  creditCards = await getCreditCards();
+  is(creditCards.length, 0, "Credit card storage is empty");
+});
+
+add_task(async function test_addInvalidCreditCard() {
+  await new Promise(resolve => {
+    let win = window.openDialog(EDIT_CREDIT_CARD_DIALOG_URL);
+    win.addEventListener("FormReady", () => {
+      const unloadHandler = () => ok(false, "Edit credit card dialog shouldn't be closed");
+      win.addEventListener("unload", unloadHandler);
+
+      EventUtils.synthesizeKey("VK_TAB", {}, win);
+      EventUtils.synthesizeKey("test", {}, win);
+      win.document.querySelector("#save").click();
+
+      is(win.document.querySelector("form").checkValidity(), false, "cc-number is invalid");
+      SimpleTest.requestFlakyTimeout("Ensure the window remains open after save attempt");
+      setTimeout(() => {
+        win.removeEventListener("unload", unloadHandler);
+        win.close();
+        resolve();
+      }, 500);
+    }, {once: true});
+  });
+  let creditCards = await getCreditCards();
+
+  is(creditCards.length, 0, "Credit card storage is empty");
+});
--- a/browser/extensions/formautofill/test/browser/head.js
+++ b/browser/extensions/formautofill/test/browser/head.js
@@ -1,20 +1,21 @@
-/* exported MANAGE_ADDRESSES_DIALOG_URL, EDIT_ADDRESS_DIALOG_URL, BASE_URL,
-            TEST_ADDRESS_1, TEST_ADDRESS_2, TEST_ADDRESS_3, TEST_ADDRESS_4, TEST_ADDRESS_5,
+/* exported MANAGE_ADDRESSES_DIALOG_URL, EDIT_ADDRESS_DIALOG_URL, EDIT_CREDIT_CARD_DIALOG_URL,
+            BASE_URL, TEST_ADDRESS_1, TEST_ADDRESS_2, TEST_ADDRESS_3, TEST_ADDRESS_4, TEST_ADDRESS_5,
             TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2, TEST_CREDIT_CARD_3, FORM_URL,
             FTU_PREF, ENABLED_PREF, SYNC_USERNAME_PREF, SYNC_ADDRESSES_PREF,
             sleep, expectPopupOpen, openPopupOn, expectPopupClose, closePopup, clickDoorhangerButton,
             getAddresses, saveAddress, removeAddresses, saveCreditCard,
             getDisplayedPopupItems, getDoorhangerCheckbox */
 
 "use strict";
 
 const MANAGE_ADDRESSES_DIALOG_URL = "chrome://formautofill/content/manageAddresses.xhtml";
 const EDIT_ADDRESS_DIALOG_URL = "chrome://formautofill/content/editAddress.xhtml";
+const EDIT_CREDIT_CARD_DIALOG_URL = "chrome://formautofill/content/editCreditCard.xhtml";
 const BASE_URL = "http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/";
 const FORM_URL = "http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/autocomplete_basic.html";
 const FTU_PREF = "extensions.formautofill.firstTimeUse";
 const ENABLED_PREF = "extensions.formautofill.addresses.enabled";
 const SYNC_USERNAME_PREF = "services.sync.username";
 const SYNC_ADDRESSES_PREF = "services.sync.engine.addresses";
 
 const TEST_ADDRESS_1 = {
--- a/browser/extensions/formautofill/test/mochitest/test_basic_autocomplete_form.html
+++ b/browser/extensions/formautofill/test/mochitest/test_basic_autocomplete_form.html
@@ -80,16 +80,17 @@ function checkFormFilled(address) {
   return Promise.all(promises);
 }
 
 async function setupAddressStorage() {
   await addAddress(MOCK_STORAGE[0]);
   await addAddress(MOCK_STORAGE[1]);
 }
 
+// TODO: Add form history fallback test cases for credit card fields. (bug 1393374)
 async function setupFormHistory() {
   await updateFormHistory([
     {op: "add", fieldname: "tel", value: "+1234567890"},
     {op: "add", fieldname: "email", value: "foo@mozilla.com"},
   ]);
 }
 
 initPopupListener();
@@ -167,16 +168,30 @@ add_task(async function check_menu_when_
 // Display history search result if no matched data in addresses.
 add_task(async function check_fallback_for_mismatched_field() {
   await setInput("#email", "");
   doKey("down");
   await expectPopup();
   checkMenuEntries(["foo@mozilla.com"], false);
 });
 
+// Display history search result if address autofill is disabled.
+add_task(async function check_search_result_for_pref_off() {
+  await SpecialPowers.pushPrefEnv({
+    set: [["extensions.formautofill.addresses.enabled", false]],
+  });
+
+  await setInput("#tel", "");
+  doKey("down");
+  await expectPopup();
+  checkMenuEntries(["+1234567890"], false);
+
+  await SpecialPowers.popPrefEnv();
+});
+
 // Autofill the address from dropdown menu.
 add_task(async function check_fields_after_form_autofill() {
   await setInput("#organization", "Moz");
   doKey("down");
   await expectPopup();
   checkMenuEntries(MOCK_STORAGE.map(address =>
     JSON.stringify({
       primary: address.organization,
--- a/browser/extensions/formautofill/test/unit/test_activeStatus.js
+++ b/browser/extensions/formautofill/test/unit/test_activeStatus.js
@@ -34,22 +34,24 @@ add_task(async function test_activeStatu
   let formAutofillParent = new FormAutofillParent();
   sinon.stub(formAutofillParent, "_computeStatus");
   sinon.spy(formAutofillParent, "_onStatusChanged");
 
   // _active = _computeStatus() => No need to trigger _onStatusChanged
   formAutofillParent._active = true;
   formAutofillParent._computeStatus.returns(true);
   formAutofillParent.observe(null, "nsPref:changed", "extensions.formautofill.addresses.enabled");
+  formAutofillParent.observe(null, "nsPref:changed", "extensions.formautofill.creditCards.enabled");
   do_check_eq(formAutofillParent._onStatusChanged.called, false);
 
   // _active != _computeStatus() => Need to trigger _onStatusChanged
   formAutofillParent._computeStatus.returns(false);
   formAutofillParent._onStatusChanged.reset();
   formAutofillParent.observe(null, "nsPref:changed", "extensions.formautofill.addresses.enabled");
+  formAutofillParent.observe(null, "nsPref:changed", "extensions.formautofill.creditCards.enabled");
   do_check_eq(formAutofillParent._onStatusChanged.called, true);
 
   // profile changed => Need to trigger _onStatusChanged
   ["add", "update", "remove", "reconcile", "merge"].forEach(event => {
     formAutofillParent._computeStatus.returns(!formAutofillParent._active);
     formAutofillParent._onStatusChanged.reset();
     formAutofillParent.observe(null, "formautofill-storage-changed", event);
     do_check_eq(formAutofillParent._onStatusChanged.called, true);
@@ -61,31 +63,45 @@ add_task(async function test_activeStatu
   formAutofillParent.observe(null, "formautofill-storage-changed", "notifyUsed");
   do_check_eq(formAutofillParent._onStatusChanged.called, false);
 });
 
 add_task(async function test_activeStatus_computeStatus() {
   let formAutofillParent = new FormAutofillParent();
   do_register_cleanup(function cleanup() {
     Services.prefs.clearUserPref("extensions.formautofill.addresses.enabled");
+    Services.prefs.clearUserPref("extensions.formautofill.creditCards.enabled");
   });
 
   sinon.stub(profileStorage.addresses, "getAll");
   profileStorage.addresses.getAll.returns([]);
 
   // pref is enabled and profile is empty.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", true);
+  Services.prefs.setBoolPref("extensions.formautofill.creditCards.enabled", true);
   do_check_eq(formAutofillParent._computeStatus(), false);
 
   // pref is disabled and profile is empty.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", false);
+  Services.prefs.setBoolPref("extensions.formautofill.creditCards.enabled", false);
   do_check_eq(formAutofillParent._computeStatus(), false);
 
   profileStorage.addresses.getAll.returns([{"given-name": "John"}]);
   formAutofillParent.observe(null, "formautofill-storage-changed", "add");
   // pref is enabled and profile is not empty.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", true);
+  Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", true);
   do_check_eq(formAutofillParent._computeStatus(), true);
 
+  // pref is partial enabled and profile is not empty.
+  Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", true);
+  Services.prefs.setBoolPref("extensions.formautofill.creditCards.enabled", false);
+  do_check_eq(formAutofillParent._computeStatus(), true);
+  Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", false);
+  Services.prefs.setBoolPref("extensions.formautofill.creditCards.enabled", true);
+  do_check_eq(formAutofillParent._computeStatus(), true);
+
+
   // pref is disabled and profile is not empty.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", false);
+  Services.prefs.setBoolPref("extensions.formautofill.creditCards.enabled", false);
   do_check_eq(formAutofillParent._computeStatus(), false);
 });
--- a/browser/extensions/formautofill/test/unit/test_creditCardRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_creditCardRecords.js
@@ -254,24 +254,24 @@ add_task(async function test_validate() 
   do_check_eq(creditCards[1]["cc-exp"], year + "-" + month.toString().padStart(2, "0"));
 
   do_check_eq(creditCards[2]["cc-number"].length, 16);
 
   try {
     await profileStorage.creditCards.normalizeCCNumberFields(TEST_CREDIT_CARD_WITH_INVALID_NUMBERS);
     throw new Error("Not receiving invalid characters error");
   } catch (e) {
-    Assert.equal(e.message, "Credit card number contains invalid characters.");
+    Assert.equal(e.message, "Credit card number contains invalid characters or is under 12 digits.");
   }
 
   try {
     await profileStorage.creditCards.normalizeCCNumberFields(TEST_CREDIT_CARD_WITH_SHORT_NUMBERS);
     throw new Error("Not receiving invalid characters error");
   } catch (e) {
-    Assert.equal(e.message, "Invalid credit card number because length is under 12 digits.");
+    Assert.equal(e.message, "Credit card number contains invalid characters or is under 12 digits.");
   }
 });
 
 add_task(async function test_notifyUsed() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
   await prepareTestCreditCards(path);
 
   let profileStorage = new ProfileStorage(path);
--- a/browser/extensions/pocket/bootstrap.js
+++ b/browser/extensions/pocket/bootstrap.js
@@ -187,38 +187,60 @@ var PocketPageAction = {
       }
     }
 
     this.pageAction.remove();
     this.pageAction = null;
   },
 
   startLibraryAnimation(doc) {
-    var libraryButton = doc.getElementById("library-button");
+    let libraryButton = doc.getElementById("library-button");
     if (!Services.prefs.getBoolPref("toolkit.cosmeticAnimations.enabled") ||
         !libraryButton ||
         libraryButton.getAttribute("cui-areatype") == "menu-panel" ||
         libraryButton.getAttribute("overflowedItem") == "true" ||
         !libraryButton.closest("#nav-bar")) {
       return;
     }
-    libraryButton.removeAttribute("fade");
+
+    let animatableBox = doc.getElementById("library-animatable-box");
+    let navBar = doc.getElementById("nav-bar");
+    let libraryIcon = doc.getAnonymousElementByAttribute(libraryButton, "class", "toolbarbutton-icon");
+    let dwu = doc.defaultView.getInterface(Ci.nsIDOMWindowUtils);
+    let iconBounds = dwu.getBoundsWithoutFlushing(libraryIcon);
+    let libraryBounds = dwu.getBoundsWithoutFlushing(libraryButton);
+
+    animatableBox.style.setProperty("--library-button-y", libraryBounds.y + "px");
+    animatableBox.style.setProperty("--library-button-height", libraryBounds.height + "px");
+    animatableBox.style.setProperty("--library-icon-x", iconBounds.x + "px");
+    if (navBar.hasAttribute("brighttext")) {
+      animatableBox.setAttribute("brighttext", "true");
+    } else {
+      animatableBox.removeAttribute("brighttext");
+    }
+    animatableBox.removeAttribute("fade");
+    doc.defaultView.gNavToolbox.setAttribute("animate", "pocket");
     libraryButton.setAttribute("animate", "pocket");
-    libraryButton.addEventListener("animationend", PocketPageAction.onLibraryButtonAnimationEnd);
+    animatableBox.setAttribute("animate", "pocket");
+    animatableBox.addEventListener("animationend", PocketPageAction.onLibraryButtonAnimationEnd);
   },
 
   onLibraryButtonAnimationEnd(event) {
     let doc = event.target.ownerDocument;
     let libraryButton = doc.getElementById("library-button");
+    let animatableBox = doc.getElementById("library-animatable-box");
     if (event.animationName.startsWith("library-pocket-animation")) {
-      libraryButton.setAttribute("fade", "true");
+      animatableBox.setAttribute("fade", "true");
     } else if (event.animationName == "library-pocket-fade") {
-      libraryButton.removeEventListener("animationend", PocketPageAction.onLibraryButtonAnimationEnd);
+      animatableBox.removeEventListener("animationend", PocketPageAction.onLibraryButtonAnimationEnd);
+      // Put the 'fill' back in the normal icon.
       libraryButton.removeAttribute("animate");
-      libraryButton.removeAttribute("fade");
+      animatableBox.removeAttribute("animate");
+      animatableBox.removeAttribute("fade");
+      event.target.ownerGlobal.gNavToolbox.removeAttribute("animate");
     }
   },
 };
 
 // PocketContextMenu
 // When the context menu is opened check if we need to build and enable pocket UI.
 var PocketContextMenu = {
   init() {
--- a/browser/extensions/pocket/skin/shared/pocket-animation.svg
+++ b/browser/extensions/pocket/skin/shared/pocket-animation.svg
@@ -7,237 +7,237 @@
       <filter id="a">
         <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
       </filter>
     </defs>
     <g>
       <g>
         <path d="M10.016 15.253c-4.525 0 -8.195 -3.669 -8.195 -8.195v-4.097a2.048 2.048 0 0 1 2.05 -2.05h12.292a2.048 2.048 0 0 1 2.049 2.05v4.097c0 4.526 -3.67 8.195 -8.196 8.195z"/>
       </g>
-      <g filter="url(#a)" transform="translate(1.749 1.092) scale(1.02438)">
+      <g filter="url(#a)" transform="translate(1.75 1.092) scale(1.02438)">
         <g>
           <path d="M11.985 3.968a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 0.22 -1.097 1 1 0 0 0 -0.935 -0.614z"/>
         </g>
       </g>
     </g>
   </svg>
   <svg x="20">
     <defs>
       <filter id="b">
         <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
       </filter>
     </defs>
     <g>
       <g>
         <path d="M10.016 15.253c-4.525 0 -8.195 -3.669 -8.195 -8.195v-4.097a2.048 2.048 0 0 1 2.05 -2.05h12.292a2.048 2.048 0 0 1 2.049 2.05v4.097c0 4.526 -3.67 8.195 -8.196 8.195z"/>
       </g>
-      <g filter="url(#b)" transform="translate(1.749 1.092) scale(1.02438)">
+      <g filter="url(#b)" transform="translate(1.75 1.092) scale(1.02438)">
         <g>
           <path d="M11.985 3.968a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 0.22 -1.097 1 1 0 0 0 -0.935 -0.614z"/>
         </g>
       </g>
     </g>
   </svg>
   <svg x="40">
     <defs>
       <filter id="c">
         <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
       </filter>
     </defs>
     <g>
       <g>
         <path d="M10.016 15.97c-4.978 0 -9.014 -4.036 -9.014 -9.014v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.521a2.253 2.253 0 0 1 2.254 2.254v4.507c0 4.978 -4.036 9.014 -9.015 9.014z"/>
       </g>
-      <g filter="url(#c)" transform="translate(1.749 1.86) scale(1.02438)">
+      <g filter="url(#c)" transform="translate(1.75 1.86) scale(1.02438)">
         <g>
           <path d="M11.985 3.968a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 0.22 -1.097 1 1 0 0 0 -0.935 -0.614z"/>
         </g>
       </g>
     </g>
   </svg>
   <svg x="60">
     <defs>
       <filter id="d">
         <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
       </filter>
     </defs>
     <g>
       <g>
         <path d="M10.016 15.97c-4.978 0 -9.014 -4.036 -9.014 -9.014v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.521a2.253 2.253 0 0 1 2.254 2.254v4.507c0 4.978 -4.036 9.014 -9.015 9.014z"/>
       </g>
-      <g filter="url(#d)" transform="translate(1.752 1.862) scale(1.02438)">
+      <g filter="url(#d)" transform="translate(1.75 1.862) scale(1.02438)">
         <g>
           <path d="M11.985 3.968a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 0.22 -1.097 1 1 0 0 0 -0.935 -0.614z"/>
         </g>
       </g>
     </g>
   </svg>
   <svg x="80">
     <defs>
       <filter id="e">
         <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
       </filter>
     </defs>
     <g>
       <g>
         <path d="M10.016 15.97c-4.978 0 -9.014 -4.036 -9.014 -9.014v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.521a2.253 2.253 0 0 1 2.254 2.254v4.507c0 4.978 -4.036 9.014 -9.015 9.014z"/>
       </g>
-      <g filter="url(#e)" transform="translate(1.749 1.86) scale(1.02438)">
+      <g filter="url(#e)" transform="translate(1.75 1.86) scale(1.02438)">
         <g>
           <path d="M11.985 3.968a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 0.22 -1.097 1 1 0 0 0 -0.935 -0.614z"/>
         </g>
       </g>
     </g>
   </svg>
   <svg x="100">
     <defs>
       <filter id="f">
         <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
       </filter>
     </defs>
     <g>
       <g>
         <path d="M10.016 15.94c-4.958 0 -8.979 -4.021 -8.979 -8.98v-4.49a2.244 2.244 0 0 1 2.245 -2.244h13.469a2.244 2.244 0 0 1 2.245 2.245v4.49c0 4.958 -4.02 8.979 -8.98 8.979z"/>
       </g>
-      <g filter="url(#f)" transform="translate(1.749 1.817) scale(1.02438)">
+      <g filter="url(#f)" transform="translate(1.75 1.817) scale(1.02438)">
         <g>
           <path d="M11.985 3.968a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 0.22 -1.097 1 1 0 0 0 -0.935 -0.614z"/>
         </g>
       </g>
     </g>
   </svg>
   <svg x="120">
     <defs>
       <filter id="g">
         <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
       </filter>
     </defs>
     <g>
       <g>
         <path d="M10.016 15.858c-4.907 0 -8.886 -3.979 -8.886 -8.886v-4.443a2.221 2.221 0 0 1 2.222 -2.222h13.33a2.221 2.221 0 0 1 2.22 2.222v4.443c0 4.907 -3.978 8.886 -8.886 8.886z"/>
       </g>
-      <g filter="url(#g)" transform="translate(1.749 1.705) scale(1.02438)">
+      <g filter="url(#g)" transform="translate(1.75 1.705) scale(1.02438)">
         <g>
           <path d="M11.985 3.968a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 0.22 -1.097 1 1 0 0 0 -0.935 -0.614z"/>
         </g>
       </g>
     </g>
   </svg>
   <svg x="140">
     <defs>
       <filter id="h">
         <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
       </filter>
     </defs>
     <g>
       <g>
         <path d="M10.016 15.743c-4.835 0 -8.755 -3.92 -8.755 -8.755v-4.377a2.188 2.188 0 0 1 2.19 -2.19h13.132c1.21 0 2.189 0.98 2.189 2.19v4.377c0 4.835 -3.92 8.755 -8.756 8.755z"/>
       </g>
-      <g filter="url(#h)" transform="translate(1.749 1.552) scale(1.02438)">
+      <g filter="url(#h)" transform="translate(1.75 1.552) scale(1.02438)">
         <g>
           <path d="M11.985 3.968a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 0.22 -1.097 1 1 0 0 0 -0.935 -0.614z"/>
         </g>
       </g>
     </g>
   </svg>
   <svg x="160">
     <defs>
       <filter id="i">
         <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
       </filter>
     </defs>
     <g>
       <g>
         <path d="M10.016 15.612c-4.752 0 -8.604 -3.853 -8.604 -8.605v-4.302a2.15 2.15 0 0 1 2.15 -2.152h12.908a2.15 2.15 0 0 1 2.151 2.152v4.302c0 4.752 -3.853 8.605 -8.605 8.605z"/>
       </g>
-      <g filter="url(#i)" transform="translate(1.749 1.387) scale(1.02438)">
+      <g filter="url(#i)" transform="translate(1.75 1.387) scale(1.02438)">
         <g>
           <path d="M11.985 3.968a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 0.22 -1.097 1 1 0 0 0 -0.935 -0.614z"/>
         </g>
       </g>
     </g>
   </svg>
   <svg x="180">
     <defs>
       <filter id="j">
         <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
       </filter>
     </defs>
     <g>
       <g>
         <path d="M10.016 15.48c-4.668 0 -8.454 -3.785 -8.454 -8.454v-4.227a2.113 2.113 0 0 1 2.114 -2.114h12.681a2.113 2.113 0 0 1 2.114 2.114v4.227c0 4.669 -3.786 8.454 -8.455 8.454z"/>
       </g>
-      <g filter="url(#j)" transform="translate(1.749 1.238) scale(1.02438)">
+      <g filter="url(#j)" transform="translate(1.75 1.238) scale(1.02438)">
         <g>
           <path d="M11.985 3.968a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 0.22 -1.097 1 1 0 0 0 -0.935 -0.614z"/>
         </g>
       </g>
     </g>
   </svg>
   <svg x="200">
     <defs>
       <filter id="k">
         <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
       </filter>
     </defs>
     <g>
       <g>
         <path d="M10.016 15.365c-4.596 0 -8.323 -3.726 -8.323 -8.323v-4.161a2.08 2.08 0 0 1 2.081 -2.081h12.485a2.08 2.08 0 0 1 2.08 2.08v4.162c0 4.597 -3.726 8.323 -8.323 8.323z"/>
       </g>
-      <g filter="url(#k)" transform="translate(1.749 1.132) scale(1.02438)">
+      <g filter="url(#k)" transform="translate(1.75 1.132) scale(1.02438)">
         <g>
           <path d="M11.985 3.968a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 0.22 -1.097 1 1 0 0 0 -0.935 -0.614z"/>
         </g>
       </g>
     </g>
   </svg>
   <svg x="220">
     <defs>
       <filter id="l">
         <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
       </filter>
     </defs>
     <g>
       <g>
         <path d="M10.016 15.284c-4.545 0 -8.23 -3.685 -8.23 -8.23v-4.115a2.057 2.057 0 0 1 2.058 -2.058h12.345a2.057 2.057 0 0 1 2.058 2.058v4.115c0 4.545 -3.685 8.23 -8.23 8.23z"/>
       </g>
-      <g filter="url(#l)" transform="translate(1.749 1.092) scale(1.02438)">
+      <g filter="url(#l)" transform="translate(1.75 1.092) scale(1.02438)">
         <g>
           <path d="M11.985 3.968a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 0.22 -1.097 1 1 0 0 0 -0.935 -0.614z"/>
         </g>
       </g>
     </g>
   </svg>
   <svg x="240">
     <defs>
       <filter id="m">
         <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
       </filter>
     </defs>
     <g>
       <g>
         <path d="M10.016 15.253c-4.525 0 -8.195 -3.669 -8.195 -8.195v-4.097a2.048 2.048 0 0 1 2.05 -2.05h12.292a2.048 2.048 0 0 1 2.049 2.05v4.097c0 4.526 -3.67 8.195 -8.196 8.195z"/>
       </g>
-      <g filter="url(#m)" transform="translate(1.749 1.092) scale(1.02438)">
+      <g filter="url(#m)" transform="translate(1.75 1.092) scale(1.02438)">
         <g>
           <path d="M11.985 3.968a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 0.22 -1.097 1 1 0 0 0 -0.935 -0.614z"/>
         </g>
       </g>
     </g>
   </svg>
   <svg x="260">
     <defs>
       <filter id="n">
         <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
       </filter>
     </defs>
     <g>
       <g>
         <path d="M10.016 15.253c-4.525 0 -8.195 -3.669 -8.195 -8.195v-4.097a2.048 2.048 0 0 1 2.05 -2.05h12.292a2.048 2.048 0 0 1 2.049 2.05v4.097c0 4.526 -3.67 8.195 -8.196 8.195z"/>
       </g>
-      <g filter="url(#n)" transform="translate(1.749 1.092) scale(1.02438)">
+      <g filter="url(#n)" transform="translate(1.75 1.092) scale(1.02438)">
         <g>
           <path d="M11.985 3.968a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 0.22 -1.097 1 1 0 0 0 -0.935 -0.614z"/>
         </g>
       </g>
     </g>
   </svg>
 </svg>
--- a/browser/extensions/pocket/skin/shared/pocket.css
+++ b/browser/extensions/pocket/skin/shared/pocket.css
@@ -133,81 +133,73 @@
     fill: #ef4056;
   }
   to {
     transform: scaleX(-1) translateX(-1056px);
     fill: #ef4056;
   }
 }
 
-/* The animations for the pocket-button and library-button are disabled
-   outside of the nav-bar due to bug 1382894. */
-:-moz-any(#pocket-button, #library-button) > .toolbarbutton-animatable-box {
-  display: none;
-}
-#nav-bar :-moz-any(#pocket-button, #library-button) > .toolbarbutton-animatable-box {
-  display: -moz-box;
-}
-
 /* We need to use an animation here instead of a transition
    to guarantee that the animation succeeds. With transitions
    if the starting value is already equal to the end value
    then no transition will occur and thus no transitionend event. */
 @keyframes library-pocket-fade {
   from {
     fill: #ef4056;
   }
   to {
     fill: inherit;
   }
 }
 
-#library-button[animate="pocket"] {
-  position: relative;
-}
-
-#library-button[animate="pocket"] > .toolbarbutton-animatable-box {
+.toolbarbutton-animatable-box[animate="pocket"] {
   position: absolute;
   overflow: hidden;
-  top: calc(50% - 27px); /* 27px is half the height of the sprite */
+  /* Position the sprite at the y-position of the library-button, then adjust
+     based on the size difference between half of the button height and half
+     of the sprite height. */
+  top: calc(var(--library-button-y) + var(--library-button-height) / 2 - 27px);
   /* Since .toolbarbutton-icon uses a different width than the animatable box,
-     we need to set a padding relative to the difference in widths. */
-  margin-inline-start: calc((16px + 2 * var(--toolbarbutton-inner-padding) - 22px) / 2);
+     we need to set a margin relative to the difference in widths.
+     margin-left is used here even in RTL because the item is positioned using `left` */
+  left: calc(var(--library-icon-x) + (16px + 2 * var(--toolbarbutton-inner-padding) - 22px) / 2);
   /* Set the min- and max- width and height of the box equal to that
      of each frame of the SVG sprite. Setting the width and height via
      the `width` and `height` CSS properties causes an assertion for
      `inline-size less than zero: 'aContainingBlockISize >= 0'` (bug 1379332). */
   min-width: 22px;
   max-width: 22px;
   /* Height of each frame within the SVG sprite. The sprite must have equal amount
      of space above and below the icon to allow it to vertically center with the
      sprite's icon on top of the toolbar icon when using position:absolute;. */
   min-height: 54px;
   max-height: 54px;
+  z-index: 2;
 }
 
-#library-button[animate="pocket"] > .toolbarbutton-animatable-box > .toolbarbutton-animatable-image {
+.toolbarbutton-animatable-box[animate="pocket"] > .toolbarbutton-animatable-image {
   height: var(--toolbarbutton-height); /* Height must be equal to height of toolbarbutton padding-box */
   min-height: 54px; /* Minimum height must be equal to the height of the SVG sprite */
 }
 
-#library-button[animate="pocket"] > .toolbarbutton-animatable-box > .toolbarbutton-animatable-image {
+.toolbarbutton-animatable-box[animate="pocket"] > .toolbarbutton-animatable-image {
   background-image: url("chrome://pocket-shared/skin/library-pocket-animation.svg");
   width: 1078px;
   animation-name: library-pocket-animation;
   animation-duration: 800ms;
   animation-timing-function: steps(48);
 }
 
-#library-button[animate="pocket"]:-moz-locale-dir(rtl) > .toolbarbutton-animatable-box > .toolbarbutton-animatable-image {
+.toolbarbutton-animatable-box[animate="pocket"]:-moz-locale-dir(rtl) > .toolbarbutton-animatable-image {
   animation-name: library-pocket-animation-rtl;
   transform: scaleX(-1);
 }
 
-#library-button[animate="pocket"][fade] > .toolbarbutton-animatable-box > .toolbarbutton-animatable-image {
+.toolbarbutton-animatable-box[animate="pocket"][fade] > .toolbarbutton-animatable-image {
   animation-name: library-pocket-fade;
   animation-duration: 2s;
   animation-timing-function: ease-out;
 }
 
 #pocket-button[cui-areatype="toolbar"][open] {
   fill: #ef4056;
 }
--- a/browser/modules/AboutNewTab.jsm
+++ b/browser/modules/AboutNewTab.jsm
@@ -25,17 +25,17 @@ var AboutNewTab = {
   pageListener: null,
 
   isOverridden: false,
 
   init(pageListener) {
     if (this.isOverridden) {
       return;
     }
-    this.pageListener = pageListener || new RemotePages("about:newtab");
+    this.pageListener = pageListener || new RemotePages(["about:home", "about:newtab"]);
     this.pageListener.addMessageListener("NewTab:Customize", this.customize);
     this.pageListener.addMessageListener("NewTab:MaybeShowMigrateMessage",
       this.maybeShowMigrateMessage);
   },
 
   maybeShowMigrateMessage({ target }) {
     AutoMigrate.shouldShowMigratePrompt(target.browser).then((prompt) => {
       if (prompt) {
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -18,18 +18,16 @@ browser.jar:
   skin/classic/browser/menuPanel-help.png
   skin/classic/browser/menuPanel-help@2x.png
   skin/classic/browser/monitor.png
   skin/classic/browser/monitor_16-10.png
 * skin/classic/browser/pageInfo.css
   skin/classic/browser/pageInfo.png
   skin/classic/browser/page-livemarks.png
   skin/classic/browser/privatebrowsing-mask.png
-  skin/classic/browser/reload-stop-go.png
-  skin/classic/browser/reload-stop-go@2x.png
   skin/classic/browser/searchbar.css
   skin/classic/browser/setDesktopBackground.css
   skin/classic/browser/slowStartup-16.png
   skin/classic/browser/webRTC-indicator.css  (../shared/webRTC-indicator.css)
 * skin/classic/browser/controlcenter/panel.css        (controlcenter/panel.css)
 * skin/classic/browser/customizableui/panelUI.css (customizableui/panelUI.css)
 * skin/classic/browser/downloads/allDownloadsViewOverlay.css   (downloads/allDownloadsViewOverlay.css)
 * skin/classic/browser/downloads/downloads.css        (downloads/downloads.css)
deleted file mode 100644
index 1017be9032a298c8aa1f4a5cd4576087d8bb6f0d..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 38b27bf0cd4499bedab1ee01c8b1c38268955a0e..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/themes/linux/searchbar.css
+++ b/browser/themes/linux/searchbar.css
@@ -5,48 +5,27 @@
 .autocomplete-textbox-container {
   -moz-box-align: stretch;
 }
 
 .textbox-input-box {
   margin: 0;
 }
 
-/* Engine button */
 .searchbar-engine-image {
   height: 16px;
   width: 16px;
   list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.svg");
   margin-inline-start: -1px;
 }
 
-/* Search go button */
 .search-go-container {
   -moz-box-align: center;
 }
 
-.search-go-button {
-  padding: 1px;
-  list-style-image: url("chrome://browser/skin/reload-stop-go.png");
-  -moz-image-region: rect(0, 42px, 14px, 28px);
-  width: 14px;
-}
-
-.search-go-button:hover {
-  -moz-image-region: rect(14px, 42px, 28px, 28px);
-}
-
-.search-go-button:hover:active {
-  -moz-image-region: rect(28px, 42px, 42px, 28px);
-}
-
-.search-go-button:-moz-locale-dir(rtl) {
-  transform: scaleX(-1);
-}
-
 menuitem[cmd="cmd_clearhistory"] {
   list-style-image: url("moz-icon://stock/gtk-clear?size=menu");
 }
 
 menuitem[cmd="cmd_clearhistory"][disabled] {
   list-style-image: url("moz-icon://stock/gtk-clear?size=menu&state=disabled");
 }
 
@@ -87,29 +66,16 @@ menuitem[cmd="cmd_clearhistory"][disable
 
   .searchbar-search-button:hover {
     -moz-image-region: rect(0, 80px, 40px, 40px);
   }
 
   .searchbar-search-button:hover:active {
     -moz-image-region: rect(0, 120px, 40px, 80px);
   }
-
-  .search-go-button {
-    list-style-image: url("chrome://browser/skin/reload-stop-go@2x.png");
-    -moz-image-region: rect(0, 84px, 28px, 56px);
-  }
-
-  .search-go-button:hover {
-    -moz-image-region: rect(28px, 84px, 56px, 56px);
-  }
-
-  .search-go-button:hover:active {
-    -moz-image-region: rect(56px, 84px, 84px, 56px);
-  }
 }
 
 .search-panel-current-engine {
   -moz-box-align: center;
 }
 
 /**
  * The borders of the various elements are specified as follows.
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -26,18 +26,16 @@ browser.jar:
   skin/classic/browser/panel-plus-sign.png
   skin/classic/browser/page-livemarks.png
   skin/classic/browser/page-livemarks@2x.png
   skin/classic/browser/pageInfo.css
   skin/classic/browser/privatebrowsing-mask.png
   skin/classic/browser/privatebrowsing-mask@2x.png
   skin/classic/browser/privatebrowsing-mask-short.png
   skin/classic/browser/privatebrowsing-mask-short@2x.png
-  skin/classic/browser/reload-stop-go.png
-  skin/classic/browser/reload-stop-go@2x.png
   skin/classic/browser/searchbar.css
   skin/classic/browser/slowStartup-16.png
   skin/classic/browser/toolbarbutton-dropmarker.png
   skin/classic/browser/toolbarbutton-dropmarker@2x.png
   skin/classic/browser/webRTC-indicator.css
 * skin/classic/browser/controlcenter/panel.css        (controlcenter/panel.css)
 * skin/classic/browser/customizableui/panelUI.css    (customizableui/panelUI.css)
 * skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css)
@@ -87,18 +85,16 @@ browser.jar:
   skin/classic/browser/sync-desktopIcon.svg  (../shared/sync-desktopIcon.svg)
   skin/classic/browser/sync-mobileIcon.svg  (../shared/sync-mobileIcon.svg)
   skin/classic/browser/yosemite/menuPanel-customize.png                (menuPanel-customize-yosemite.png)
   skin/classic/browser/yosemite/menuPanel-customize@2x.png             (menuPanel-customize-yosemite@2x.png)
   skin/classic/browser/yosemite/menuPanel-exit.png                     (menuPanel-exit-yosemite.png)
   skin/classic/browser/yosemite/menuPanel-exit@2x.png                  (menuPanel-exit-yosemite@2x.png)
   skin/classic/browser/yosemite/menuPanel-help.png                     (menuPanel-help-yosemite.png)
   skin/classic/browser/yosemite/menuPanel-help@2x.png                  (menuPanel-help-yosemite@2x.png)
-  skin/classic/browser/yosemite/reload-stop-go.png                     (reload-stop-go-yosemite.png)
-  skin/classic/browser/yosemite/reload-stop-go@2x.png                  (reload-stop-go-yosemite@2x.png)
 #ifdef E10S_TESTING_ONLY
   skin/classic/browser/e10s-64@2x.png (../shared/e10s-64@2x.png)
 #endif
 
 [extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar:
 % override chrome://browser/skin/feeds/audioFeedIcon.png                   chrome://browser/skin/feeds/feedIcon.png
 % override chrome://browser/skin/feeds/audioFeedIcon16.png                 chrome://browser/skin/feeds/feedIcon16.png
 % override chrome://browser/skin/feeds/videoFeedIcon.png                   chrome://browser/skin/feeds/feedIcon.png
@@ -106,10 +102,8 @@ browser.jar:
 % override chrome://browser/skin/notification-icons/geo-detailed.svg       chrome://browser/skin/notification-icons/geo.svg
 
 % override chrome://browser/skin/menuPanel-customize.png                   chrome://browser/skin/yosemite/menuPanel-customize.png                  os=Darwin osversion>=10.10
 % override chrome://browser/skin/menuPanel-customize@2x.png                chrome://browser/skin/yosemite/menuPanel-customize@2x.png               os=Darwin osversion>=10.10
 % override chrome://browser/skin/menuPanel-exit.png                        chrome://browser/skin/yosemite/menuPanel-exit.png                       os=Darwin osversion>=10.10
 % override chrome://browser/skin/menuPanel-exit@2x.png                     chrome://browser/skin/yosemite/menuPanel-exit@2x.png                    os=Darwin osversion>=10.10
 % override chrome://browser/skin/menuPanel-help.png                        chrome://browser/skin/yosemite/menuPanel-help.png                       os=Darwin osversion>=10.10
 % override chrome://browser/skin/menuPanel-help@2x.png                     chrome://browser/skin/yosemite/menuPanel-help@2x.png                    os=Darwin osversion>=10.10
-% override chrome://browser/skin/reload-stop-go.png                        chrome://browser/skin/yosemite/reload-stop-go.png                       os=Darwin osversion>=10.10
-% override chrome://browser/skin/reload-stop-go@2x.png                     chrome://browser/skin/yosemite/reload-stop-go@2x.png                    os=Darwin osversion>=10.10
deleted file mode 100644
index 5efbf0b94fb975f909ecd6b4b72f653345c22a30..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index b681f8930eaf310687a5c6211ace93f4115f04ff..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 699ef2326e0561a10742dff28711cfc4c77edd56..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 704c7a894af75b78a5016c08623da06ad64a31f3..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/themes/osx/searchbar.css
+++ b/browser/themes/osx/searchbar.css
@@ -18,38 +18,21 @@
 }
 
 .searchbar-engine-image {
   width: 16px;
   height: 16px;
   list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.svg");
 }
 
-.search-go-container {
-  -moz-box-align: center;
-  padding-inline-end: 6px;
-}
-
+.search-go-container,
 .searchbar-search-button-container {
   -moz-box-align: center;
 }
 
-.search-go-button {
-  list-style-image: url("chrome://browser/skin/reload-stop-go.png");
-  -moz-image-region: rect(0, 42px, 14px, 28px);
-}
-
-.search-go-button:hover:active {
-  -moz-image-region: rect(14px, 42px, 28px, 28px);
-}
-
-.search-go-button:-moz-locale-dir(rtl) {
-  transform: scaleX(-1);
-}
-
 .searchbar-search-button {
   list-style-image: url("chrome://browser/skin/search-indicator.png");
   -moz-image-region: rect(0, 20px, 20px, 0);
   margin-inline-start: 3px;
   margin-inline-end: 1px;
 }
 
 .searchbar-search-button[addengines="true"] {
@@ -60,28 +43,16 @@
   -moz-image-region: rect(0, 40px, 20px, 20px);
 }
 
 .searchbar-search-button:hover:active {
   -moz-image-region: rect(0, 60px, 20px, 40px);
 }
 
 @media (min-resolution: 2dppx) {
-  .search-go-button {
-    list-style-image: url("chrome://browser/skin/reload-stop-go@2x.png");
-    -moz-image-region: rect(0, 84px, 28px, 56px);
-    width: 14px;
-  }
-
-  .search-go-button:hover:active {
-    list-style-image: url("chrome://browser/skin/reload-stop-go@2x.png");
-    -moz-image-region: rect(28px, 84px, 56px, 56px);
-    width: 14px;
-  }
-
   .searchbar-search-button {
     list-style-image: url("chrome://browser/skin/search-indicator@2x.png");
     width: 20px;
     -moz-image-region: rect(0, 40px, 40px, 0);
   }
 
   .searchbar-search-button[addengines="true"] {
     list-style-image: url("chrome://browser/skin/search-indicator-badge-add@2x.png");
--- a/browser/themes/shared/browser.inc.css
+++ b/browser/themes/shared/browser.inc.css
@@ -50,45 +50,22 @@
   outline-offset: -3px;
   -moz-outline-radius: 2px;
   /* Avoid the toolbar having no height when there's no items in it */
   min-height: 22px;
   /* There's no border in customize mode, so we don't need extra padding. */
   padding-bottom: 0;
 }
 
-/* Go button */
-.urlbar-go-button {
-  padding: 0 3px;
-  list-style-image: url("chrome://browser/skin/reload-stop-go.png");
-}
+/* Library animation */
 
-.urlbar-go-button {
-  -moz-image-region: rect(0, 42px, 14px, 28px);
-}
-
-.urlbar-go-button:hover {
-  -moz-image-region: rect(14px, 42px, 28px, 28px);
-}
-
-.urlbar-go-button:hover:active {
-  -moz-image-region: rect(28px, 42px, 42px, 28px);
+#navigator-toolbox[animate] {
+  position: relative;
 }
 
-.urlbar-go-button:-moz-locale-dir(rtl) {
-  transform: scaleX(-1);
+#library-animatable-box {
+  display: none;
 }
 
-@media (min-resolution: 1.1dppx) {
-  .urlbar-go-button {
-    list-style-image: url("chrome://browser/skin/reload-stop-go@2x.png");
-    -moz-image-region: rect(0, 84px, 28px, 56px);
-    width: 20px;
-  }
+#library-animatable-box[animate] {
+  display: -moz-box;
+}
 
-  .urlbar-go-button:hover {
-    -moz-image-region: rect(28px, 84px, 56px, 56px);
-  }
-
-  .urlbar-go-button:hover:active {
-    -moz-image-region: rect(56px, 84px, 84px, 56px);
-  }
-}
--- a/browser/themes/shared/compacttheme.inc.css
+++ b/browser/themes/shared/compacttheme.inc.css
@@ -26,16 +26,17 @@
   --chrome-nav-buttons-background: hsla(240, 5%, 5%, .1);
   --chrome-nav-buttons-hover-background: hsla(240, 5%, 5%, .15);
   --chrome-nav-bar-controls-border-color: hsla(240, 5%, 5%, .3);
   --chrome-selection-color: #fff;
   --chrome-selection-background-color: #5675B9;
 
   /* Url and search bars */
   --url-and-searchbar-background-color: rgb(71, 71, 73);
+  --url-and-searchbar-color: var(--chrome-color);
   --urlbar-separator-color: #5F6670;
 }
 
 :root:-moz-lwtheme-darktext {
   --url-and-searchbar-background-color: #fff;
 
   --chrome-background-color: #E3E4E6;
   --chrome-color: #18191a;
@@ -44,16 +45,17 @@
   --chrome-nav-bar-separator-color: #B6B6B8;
   --chrome-nav-buttons-background: #ffffff; /* --theme-body-background */
   --chrome-nav-buttons-hover-background: #DADBDB;
   --chrome-nav-bar-controls-border-color: #ccc;
   --chrome-selection-color: #f5f7fa;
   --chrome-selection-background-color: #4c9ed9;
 }
 
+.toolbarbutton-animatable-box[brighttext],
 toolbar[brighttext] .toolbarbutton-1 {
   fill: rgba(249, 249, 250, .7);
 }
 
 #urlbar ::-moz-selection,
 #navigator-toolbox .searchbar-textbox ::-moz-selection,
 .browserContainer > findbar ::-moz-selection {
   background-color: var(--chrome-selection-background-color);
@@ -89,23 +91,16 @@ toolbar[brighttext] .toolbarbutton-1 {
   text-shadow: none;
 }
 
 #TabsToolbar {
   text-shadow: none !important;
 }
 
 /* URL bar and search bar*/
-#urlbar,
-#navigator-toolbox .searchbar-textbox {
-  background-color: var(--url-and-searchbar-background-color) !important;
-  background-image: none !important;
-  color: inherit !important;
-}
-
 #urlbar:not([focused="true"]),
 .searchbar-textbox:not([focused="true"]) {
   border-color: var(--chrome-nav-bar-controls-border-color);
 }
 
 #urlbar[pageproxystate="valid"] > #identity-box.verifiedIdentity > #identity-icon-labels:-moz-lwtheme-brighttext {
   color: #30e60b;
 }
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -186,16 +186,17 @@
   skin/classic/browser/badge-add-engine.png                    (../shared/search/badge-add-engine.png)
   skin/classic/browser/badge-add-engine@2x.png                 (../shared/search/badge-add-engine@2x.png)
   skin/classic/browser/search-indicator-badge-add.png          (../shared/search/search-indicator-badge-add.png)
   skin/classic/browser/search-indicator-badge-add@2x.png       (../shared/search/search-indicator-badge-add@2x.png)
   skin/classic/browser/search-indicator-magnifying-glass.svg   (../shared/search/search-indicator-magnifying-glass.svg)
   skin/classic/browser/search-arrow-go.svg                     (../shared/search/search-arrow-go.svg)
   skin/classic/browser/gear.svg                                (../shared/search/gear.svg)
   skin/classic/browser/tabbrowser/loading.svg                  (../shared/tabbrowser/loading.svg)
+  skin/classic/browser/tabbrowser/loading-burst.svg            (../shared/tabbrowser/loading-burst.svg)
   skin/classic/browser/tabbrowser/crashed.svg                  (../shared/tabbrowser/crashed.svg)
   skin/classic/browser/tabbrowser/newtab.svg                   (../shared/tabbrowser/newtab.svg)
   skin/classic/browser/tabbrowser/indicator-tab-attention.svg  (../shared/tabbrowser/indicator-tab-attention.svg)
   skin/classic/browser/tabbrowser/pendingpaint.png             (../shared/tabbrowser/pendingpaint.png)
   skin/classic/browser/tabbrowser/tab-audio-playing.svg        (../shared/tabbrowser/tab-audio-playing.svg)
   skin/classic/browser/tabbrowser/tab-audio-muted.svg          (../shared/tabbrowser/tab-audio-muted.svg)
   skin/classic/browser/tabbrowser/tab-audio-blocked.svg        (../shared/tabbrowser/tab-audio-blocked.svg)
   skin/classic/browser/tabbrowser/tab-audio-small.svg          (../shared/tabbrowser/tab-audio-small.svg)
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/tabbrowser/loading-burst.svg
@@ -0,0 +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 viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg">
+  <circle cx="5" cy="5" r="5" fill="context-fill"/>
+</svg>
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -57,16 +57,62 @@
 .tab-content {
   padding: 0 @horizontalTabPadding@;
 }
 
 .tab-content[pinned] {
   padding: 0 12px;
 }
 
+:root[sessionrestored] .tab-loading-burst {
+  position: relative;
+  overflow: hidden;
+}
+
+:root[sessionrestored] .tab-loading-burst::before {
+  position: absolute;
+  content: "";
+  /* We set the width to be a percentage of the tab's width so that we can use
+     the `scale` transform to scale it up to the full size of the tab when the
+     burst occurs. We also need to set the margin-inline-start so that the
+     center of the burst matches the center of the favicon. */
+  width: 5%;
+  height: 100%;
+  /* Center the burst over the .tab-loading-burst; it's 9px from the edge thanks
+     to .tab-content, plus 8px more since .tab-loading-burst is 16px wide. */
+  margin-inline-start: calc(17px - 2.5%);
+}
+
+:root[sessionrestored] .tab-loading-burst[pinned]::before {
+  /* This is like the margin-inline-start rule above, except that icons for
+     pinned tabs are 12px from the edge. */
+  margin-inline-start: calc(20px - 2.5%);
+}
+
+:root[sessionrestored] .tab-loading-burst[bursting]::before {
+  background-image: url("chrome://browser/skin/tabbrowser/loading-burst.svg");
+  background-position: center center;
+  background-size: 100% auto;
+  background-repeat: no-repeat;
+  animation: tab-burst-animation 375ms cubic-bezier(0,0,.58,1);
+  -moz-context-properties: fill;
+  fill: var(--tab-loading-fill);
+}
+
+@keyframes tab-burst-animation {
+  0% {
+    opacity: 0.4;
+    transform: scale(1);
+  }
+  100% {
+    opacity: 0;
+    transform: scale(40);
+  }
+}
+
 .tab-throbber,
 .tab-icon-image,
 .tab-sharing-icon-overlay,
 .tab-icon-sound,
 .tab-close-button {
   margin-top: 1px;
 }
 
--- a/browser/themes/shared/toolbarbutton-icons.inc.css
+++ b/browser/themes/shared/toolbarbutton-icons.inc.css
@@ -1,30 +1,33 @@
 :root {
   --toolbarbutton-icon-fill-attention: #0a84ff;
 }
 
 toolbar[brighttext] {
   --toolbarbutton-icon-fill-attention: #45a1ff;
 }
 
+.toolbarbutton-animatable-box,
 .toolbarbutton-1 {
   -moz-context-properties: fill;
   fill: #4c4c4c;
 }
 
+.toolbarbutton-animatable-box[brighttext],
 toolbar[brighttext] .toolbarbutton-1 {
   fill: #fff;
 }
 
 #back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
 #forward-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
 #reload-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
 #library-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
 #nav-bar-overflow-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
+#PlacesChevron:-moz-locale-dir(rtl) > .toolbarbutton-icon,
 #panic-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
   transform: scaleX(-1);
 }
 
 #back-button {
   list-style-image: url("chrome://browser/skin/back.svg");
 }
 
@@ -450,53 +453,54 @@ toolbar[brighttext] .toolbarbutton-1 {
   from {
     fill: var(--toolbarbutton-icon-fill-attention);
   }
   to {
     fill: inherit;
   }
 }
 
-#library-button[animate="bookmark"] {
-  position: relative;
-}
-
 #library-button[animate="bookmark"] > .toolbarbutton-icon {
   fill: transparent;
 }
 
-#library-button[animate="bookmark"] > .toolbarbutton-animatable-box {
+.toolbarbutton-animatable-box[animate="bookmark"] {
   position: absolute;
   overflow: hidden;
-  top: calc(50% - 27px); /* 27px is half the height of the sprite */
+  /* Position the sprite at the y-position of the library-button, then adjust
+     based on the size difference between half of the button height and half
+     of the sprite height. */
+  top: calc(var(--library-button-y) + var(--library-button-height) / 2 - 27px);
   /* Set a margin relative to the difference in widths of the .toolbarbutton-icon
-     and the .toolbar-animatable-box */
-  margin-inline-start: calc((16px + 2 * var(--toolbarbutton-inner-padding) - 22px) / 2);
+     and the .toolbar-animatable-box. This is correct even in RTL because the item
+     is positioned using `left`. */
+  left: calc(var(--library-icon-x) + (16px + 2 * var(--toolbarbutton-inner-padding) - 22px) / 2);
   /* Set the min- and max- width and height of the box equal to that
      of each frame of the SVG sprite (must use min- and max- due to bug 1379332). */
   min-width: 22px;
   max-width: 22px;
   min-height: 54px;
   max-height: 54px;
+  z-index: 2;
 }
 
-#library-button[animate="bookmark"] > .toolbarbutton-animatable-box > .toolbarbutton-animatable-image {
+.toolbarbutton-animatable-box[animate="bookmark"] > .toolbarbutton-animatable-image {
   height: var(--toolbarbutton-height);
   min-height: 54px; /* Minimum height must be equal to the height of the SVG sprite */
   background-image: url("chrome://browser/skin/library-bookmark-animation.svg");
   width: 1078px;
   animation-name: library-bookmark-animation;
   animation-duration: 800ms;
   animation-timing-function: steps(48);
   -moz-context-properties: fill, stroke;
   stroke: var(--toolbarbutton-icon-fill-attention);
 }
 
-#library-button[animate="bookmark"]:-moz-locale-dir(rtl) > .toolbarbutton-animatable-box > .toolbarbutton-animatable-image {
+.toolbarbutton-animatable-box[animate="bookmark"]:-moz-locale-dir(rtl) > .toolbarbutton-animatable-image {
   animation-name: library-bookmark-animation-rtl;
   transform: scaleX(-1);
 }
 
-#library-button[animate="bookmark"][fade] > .toolbarbutton-animatable-box > .toolbarbutton-animatable-image {
+.toolbarbutton-animatable-box[animate="bookmark"][fade] > .toolbarbutton-animatable-image {
   animation-name: library-bookmark-fade;
   animation-duration: 2s;
   animation-timing-function: ease-out;
 }
--- a/browser/themes/shared/toolbarbuttons.inc.css
+++ b/browser/themes/shared/toolbarbuttons.inc.css
@@ -262,20 +262,26 @@ toolbarbutton.bookmark-item:not(.subview
 :root[uidensity=compact] toolbarbutton.bookmark-item:not(.subviewbutton) {
   margin: 0 1px;
 }
 
 :root[uidensity=touch] toolbarbutton.bookmark-item:not(.subviewbutton) {
   padding: 4px;
 }
 
-toolbarbutton.bookmark-item:not(#bookmarks-toolbar-placeholder) {
+toolbarbutton.bookmark-item {
   max-width: 13em;
 }
 
+/* Not in a :not(...) clause in the rule above to avoid unduly increasing
+ * that rule's specificity. */
+#bookmarks-toolbar-placeholder {
+  max-width: unset;
+}
+
 .bookmark-item > .toolbarbutton-icon {
   width: 16px;
   height: 16px;
 }
 
 /* Force the display of the label for bookmarks */
 .bookmark-item > .toolbarbutton-text {
   display: -moz-box !important;
--- a/browser/themes/shared/urlbar-searchbar.inc.css
+++ b/browser/themes/shared/urlbar-searchbar.inc.css
@@ -18,26 +18,26 @@
 
 #urlbar:hover,
 .searchbar-textbox:hover {
   border-color: hsla(240,5%,5%,.35);
   box-shadow: 0 1px 6px rgba(0,0,0,.1), 0 0 1px rgba(0,0,0,.1);
 }
 
 #urlbar:-moz-lwtheme,
-.searchbar-textbox:-moz-lwtheme {
-  background-color: hsla(0,0%,100%,.8);
-  color: black;
+#navigator-toolbox .searchbar-textbox:-moz-lwtheme {
+  background-color: var(--url-and-searchbar-background-color, hsla(0,0%,100%,.8));
+  color: var(--url-and-searchbar-color, black);
 }
 
 #urlbar:-moz-lwtheme:hover,
 #urlbar:-moz-lwtheme[focused="true"],
-.searchbar-textbox:-moz-lwtheme:hover,
-.searchbar-textbox:-moz-lwtheme[focused="true"] {
-  background-color: white;
+#navigator-toolbox .searchbar-textbox:-moz-lwtheme:hover,
+#navigator-toolbox .searchbar-textbox:-moz-lwtheme[focused="true"] {
+  background-color: var(--url-and-searchbar-background-color, white);
 }
 
 :root[uidensity=compact] #urlbar,
 :root[uidensity=compact] .searchbar-textbox {
   min-height: 26px;
   margin-top: 3px;
   margin-bottom: 3px;
 }
@@ -106,53 +106,103 @@
 
 #pageAction-panel-sendToDevice-fxa,
 #pageAction-urlbar-sendToDevice-fxa {
   list-style-image: url("chrome://browser/skin/sync.svg");
 }
 
 /* URL bar and page action buttons */
 
-.urlbar-history-dropmarker {
-  -moz-appearance: none;
-  list-style-image: url(chrome://global/skin/icons/arrow-dropdown-16.svg);
-  transition: opacity 0.15s ease;
-}
-
-#urlbar[switchingtabs] > .urlbar-textbox-container > .urlbar-history-dropmarker {
-  transition: none;
-}
-
-#navigator-toolbox:not(:hover) > #nav-bar:not([customizing="true"]) > #nav-bar-customization-target > #urlbar-container > #urlbar:not([focused]) > .urlbar-textbox-container > .urlbar-history-dropmarker {
-  opacity: 0;
-}
-
-/* Page actions */
-
 #page-action-buttons {
   -moz-box-align: center;
   /* Add more space between the last icon and the urlbar's edge. */
   margin-inline-end: 3px;
 }
 
+#pageActionSeparator {
+  /* This draws the separator the same way that #urlbar-display-box draws its
+     left and right borders, which end up looking like separators.  It might not
+     be the best way in this case, but it makes sure that all these vertical
+     lines in the urlbar look the same: same height, vertical position, etc. */
+  border-inline-start: 1px solid var(--urlbar-separator-color);
+  border-image: linear-gradient(transparent 15%, var(--urlbar-separator-color) 15%, var(--urlbar-separator-color) 85%, transparent 85%);
+  border-image-slice: 1;
+  width: 1px;
+  height: 28px;
+  visibility: hidden;
+}
+
+:root[uidensity=compact] #pageActionSeparator {
+  height: 24px;
+}
+
+:root[uidensity=touch] #pageActionSeparator {
+  height: 30px;
+}
+
+:not(#pageActionSeparator):not([hidden]) ~ #pageActionSeparator {
+  /* Show the separator between the page actions and other elements when at
+     least of the latter is shown. */
+  visibility: visible;
+  margin-left: 6px;
+  margin-right: 6px;
+}
+
+#userContext-icons,
+#urlbar-zoom-button {
+  margin-left: 6px;
+  margin-right: 6px;
+}
+
 .urlbar-icon {
   padding: 0 6px;
   /* 16x16 icon with border-box sizing */
   width: 28px;
   height: 16px;
   -moz-context-properties: fill, fill-opacity;
   fill: currentColor;
   fill-opacity: 0.6;
   color: inherit;
 }
 
 .urlbar-icon:hover {
   fill-opacity: 0.8;
 }
 
+.urlbar-go-button,
+.search-go-button {
+  list-style-image: url("chrome://browser/skin/back.svg");
+  width: 26px;
+}
+
+.urlbar-go-button:-moz-locale-dir(ltr),
+.search-go-button:-moz-locale-dir(ltr) {
+  transform: scaleX(-1);
+}
+
+.urlbar-go-button:hover,
+.search-go-button:hover {
+  fill: #058b00;
+  fill-opacity: 1;
+}
+
+.urlbar-history-dropmarker {
+  -moz-appearance: none;
+  list-style-image: url(chrome://global/skin/icons/arrow-dropdown-16.svg);
+  transition: opacity 0.15s ease;
+}
+
+#urlbar[switchingtabs] > .urlbar-textbox-container > .urlbar-history-dropmarker {
+  transition: none;
+}
+
+#navigator-toolbox:not(:hover) > #nav-bar:not([customizing="true"]) > #nav-bar-customization-target > #urlbar-container > #urlbar:not([focused]) > .urlbar-textbox-container > .urlbar-history-dropmarker {
+  opacity: 0;
+}
+
 #pageActionButton {
   list-style-image: url("chrome://browser/skin/page-action.svg");
 }
 
 @keyframes bookmark-animation {
   from {
     transform: translateX(0);
   }
@@ -237,17 +287,16 @@
 
 #page-report-button {
   list-style-image: url(chrome://browser/skin/notification-icons/popup.svg);
 }
 
 /* Zoom button */
 
 #urlbar-zoom-button {
-  margin: 0 3px;
   font-size: .8em;
   padding: 0 8px;
   border-radius: 1em;
   background-color: hsla(0,0%,0%,.05);
   border: 1px solid ThreeDLightShadow;
 }
 
 #urlbar-zoom-button[animate="true"] {
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -23,20 +23,16 @@ browser.jar:
   skin/classic/browser/monitor_16-10.png
   skin/classic/browser/pageInfo.css
   skin/classic/browser/pageInfo.png
   skin/classic/browser/privatebrowsing-mask-tabstrip.png
   skin/classic/browser/privatebrowsing-mask-tabstrip-win7.png
   skin/classic/browser/privatebrowsing-mask-titlebar.png
   skin/classic/browser/privatebrowsing-mask-titlebar-win7.png
   skin/classic/browser/privatebrowsing-mask-titlebar-win7-tall.png
-  skin/classic/browser/reload-stop-go.png
-  skin/classic/browser/reload-stop-go@2x.png
-  skin/classic/browser/reload-stop-go-win7.png
-  skin/classic/browser/reload-stop-go-win7@2x.png
   skin/classic/browser/searchbar.css
   skin/classic/browser/setDesktopBackground.css
   skin/classic/browser/slowStartup-16.png
   skin/classic/browser/sync-desktopIcon.svg  (../shared/sync-desktopIcon.svg)
   skin/classic/browser/sync-mobileIcon.svg  (../shared/sync-mobileIcon.svg)
   skin/classic/browser/toolbarbutton-dropdown-arrow-win7.png
   skin/classic/browser/webRTC-indicator.css  (../shared/webRTC-indicator.css)
 * skin/classic/browser/controlcenter/panel.css                 (controlcenter/panel.css)
@@ -98,18 +94,16 @@ browser.jar:
 % override chrome://browser/skin/page-livemarks.png                   chrome://browser/skin/feeds/feedIcon16.png
 % override chrome://browser/skin/feeds/audioFeedIcon.png              chrome://browser/skin/feeds/feedIcon.png
 % override chrome://browser/skin/feeds/audioFeedIcon16.png            chrome://browser/skin/feeds/feedIcon16.png
 % override chrome://browser/skin/feeds/videoFeedIcon.png              chrome://browser/skin/feeds/feedIcon.png
 % override chrome://browser/skin/feeds/videoFeedIcon16.png            chrome://browser/skin/feeds/feedIcon16.png
 
 % override chrome://browser/skin/privatebrowsing-mask-tabstrip.png    chrome://browser/skin/privatebrowsing-mask-tabstrip-win7.png  os=WINNT osversion<=6.1
 % override chrome://browser/skin/privatebrowsing-mask-titlebar.png    chrome://browser/skin/privatebrowsing-mask-titlebar-win7.png  os=WINNT osversion<=6.1
-% override chrome://browser/skin/reload-stop-go.png                   chrome://browser/skin/reload-stop-go-win7.png                 os=WINNT osversion<=6.1
-% override chrome://browser/skin/reload-stop-go@2x.png                chrome://browser/skin/reload-stop-go-win7@2x.png              os=WINNT osversion<=6.1
 % override chrome://browser/skin/toolbarbutton-dropdown-arrow.png     chrome://browser/skin/toolbarbutton-dropdown-arrow-win7.png   os=WINNT osversion<=6.1
 
 % override chrome://browser/skin/tabbrowser/tab-background-start.png     chrome://browser/skin/tabbrowser/tab-background-start-preWin10.png     os=WINNT osversion<=6.3
 % override chrome://browser/skin/tabbrowser/tab-background-start@2x.png  chrome://browser/skin/tabbrowser/tab-background-start-preWin10@2x.png  os=WINNT osversion<=6.3
 % override chrome://browser/skin/tabbrowser/tab-background-middle.png    chrome://browser/skin/tabbrowser/tab-background-middle-preWin10.png    os=WINNT osversion<=6.3
 % override chrome://browser/skin/tabbrowser/tab-background-middle@2x.png chrome://browser/skin/tabbrowser/tab-background-middle-preWin10@2x.png os=WINNT osversion<=6.3
 % override chrome://browser/skin/tabbrowser/tab-background-end.png       chrome://browser/skin/tabbrowser/tab-background-end-preWin10.png       os=WINNT osversion<=6.3
 % override chrome://browser/skin/tabbrowser/tab-background-end@2x.png    chrome://browser/skin/tabbrowser/tab-background-end-preWin10@2x.png    os=WINNT osversion<=6.3
deleted file mode 100644
index 3ef32c3ce696eb2cc2d49b64b70d486fd837ffcb..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 38b27bf0cd4499bedab1ee01c8b1c38268955a0e..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 8b3f398c0d8509837330cc483d37b47695e2c463..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index f116eb98f26a1fc5ff9b69710b5aec464e0417db..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/themes/windows/searchbar.css
+++ b/browser/themes/windows/searchbar.css
@@ -5,51 +5,24 @@
 .autocomplete-textbox-container {
   -moz-box-align: stretch;
 }
 
 .textbox-input-box {
   margin: 0;
 }
 
-/* ::::: searchbar-engine-button ::::: */
-
 .searchbar-engine-image {
   height: 16px;
   width: 16px;
   list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.svg");
   margin-inline-start: -1px;
 }
 
-/* ::::: search-go-button ::::: */
-
-.search-go-container {
-  -moz-box-align: center;
-}
-
-.search-go-button {
-  padding: 1px;
-  list-style-image: url("chrome://browser/skin/reload-stop-go.png");
-  -moz-image-region: rect(0, 42px, 14px, 28px);
-  width: 14px;
-}
-
-.search-go-button:-moz-locale-dir(rtl) {
-  transform: scaleX(-1);
-}
-
-.search-go-button:hover {
-  -moz-image-region: rect(14px, 42px, 28px, 28px);
-}
-
-.search-go-button:hover:active {
-  -moz-image-region: rect(28px, 42px, 42px, 28px);
-}
-
-
+.search-go-container,
 .searchbar-search-button-container {
   -moz-box-align: center;
 }
 
 .searchbar-search-button {
   list-style-image: url("chrome://browser/skin/search-indicator.png");
   -moz-image-region: rect(0, 20px, 20px, 0);
   margin-top: 1px;
@@ -82,29 +55,16 @@
 
   .searchbar-search-button:hover {
     -moz-image-region: rect(0, 80px, 40px, 40px);
   }
 
   .searchbar-search-button:hover:active {
     -moz-image-region: rect(0, 120px, 40px, 80px);
   }
-
-  .search-go-button {
-    list-style-image: url("chrome://browser/skin/reload-stop-go@2x.png");
-    -moz-image-region: rect(0, 84px, 28px, 56px);
-  }
-
-  .search-go-button:hover {
-    -moz-image-region: rect(28px, 84px, 56px, 56px);
-  }
-
-  .search-go-button:hover:active {
-    -moz-image-region: rect(56px, 84px, 84px, 56px);
-  }
 }
 
 .search-panel-current-engine {
   -moz-box-align: center;
 }
 
 /**
  * The borders of the various elements are specified as follows.
old mode 100644
new mode 100755
--- a/build/moz.configure/warnings.configure
+++ b/build/moz.configure/warnings.configure
@@ -105,16 +105,21 @@ check_and_add_gcc_warning('-Wno-error=fr
 # catches format/argument mismatches with printf
 check_and_add_gcc_warning('-Wformat')
 
 # We use mix of both POSIX and Win32 printf format across the tree, so format
 # warnings are useless on mingw.
 check_and_add_gcc_warning('-Wno-format',
                           when=depends(target)(lambda t: t.kernel == 'WINNT'))
 
+# When compiling for Windows with gcc, we encounter lots of "#pragma warning"'s
+# which is an MSVC-only pragma that GCC does not recognize.
+check_and_add_gcc_warning('-Wno-unknown-pragmas',
+                          when=depends(target)(lambda t: t.kernel == 'WINNT'))
+
 # We hit this all over the place with the gtest INSTANTIATE_TEST_CASE_P macro
 check_and_add_gcc_warning('-Wno-gnu-zero-variadic-macro-arguments')
 
 # Add compile-time warnings for unprotected functions and format functions
 # that represent possible security problems
 check_and_add_gcc_warning('-Wformat-security')
 check_and_add_gcc_warning('-Wformat-overflow=2')
 
--- a/devtools/client/framework/ToolboxProcess.jsm
+++ b/devtools/client/framework/ToolboxProcess.jsm
@@ -140,27 +140,30 @@ BrowserToolboxProcess.prototype = {
     this.debuggerServer.init();
     // We mainly need a root actor and tab actors for opening a toolbox, even
     // against chrome/content/addon. But the "no auto hide" button uses the
     // preference actor, so also register the browser actors.
     this.debuggerServer.registerActors({ root: true, browser: true, tab: true });
     this.debuggerServer.allowChromeProcess = true;
     dumpn("initialized and added the browser actors for the DebuggerServer.");
 
-    let chromeDebuggingPort =
-      Services.prefs.getIntPref("devtools.debugger.chrome-debugging-port");
     let chromeDebuggingWebSocket =
       Services.prefs.getBoolPref("devtools.debugger.chrome-debugging-websocket");
     let listener = this.debuggerServer.createListener();
-    listener.portOrPath = chromeDebuggingPort;
+    listener.portOrPath = -1;
     listener.webSocket = chromeDebuggingWebSocket;
     listener.open();
+    this.port = listener.port;
+
+    if (!this.port) {
+      throw new Error("No debugger server port");
+    }
 
     dumpn("Finished initializing the chrome toolbox server.");
-    dumpn("Started listening on port: " + chromeDebuggingPort);
+    dump(`Debugger Server for Browser Toolbox listening on port: ${this.port}\n`);
   },
 
   /**
    * Initializes a profile for the remote debugger process.
    */
   _initProfile: function () {
     dumpn("Initializing the chrome toolbox user profile.");
 
@@ -237,20 +240,19 @@ BrowserToolboxProcess.prototype = {
   /**
    * Creates and initializes the profile & process for the remote debugger.
    */
   _create: function () {
     dumpn("Initializing chrome debugging process.");
 
     let command = Services.dirsvc.get("XREExeF", Ci.nsIFile).path;
 
-    let xulURI = DBG_XUL;
-
+    let xulURI = `${DBG_XUL}?port=${this.port}`;
     if (this._options.addonID) {
-      xulURI += "?addonID=" + this._options.addonID;
+      xulURI += `&addonID=${this._options.addonID}`;
     }
 
     dumpn("Running chrome debugging process.");
     let args = [
       "-no-remote",
       "-foreground",
       "-profile", this._dbgProfilePath,
       "-chrome", xulURI
--- a/devtools/client/framework/toolbox-process-window.js
+++ b/devtools/client/framework/toolbox-process-window.js
@@ -20,33 +20,41 @@ var { DebuggerClient } = require("devtoo
 var { PrefsHelper } = require("devtools/client/shared/prefs");
 var { Task } = require("devtools/shared/task");
 
 /**
  * Shortcuts for accessing various debugger preferences.
  */
 var Prefs = new PrefsHelper("devtools.debugger", {
   chromeDebuggingHost: ["Char", "chrome-debugging-host"],
-  chromeDebuggingPort: ["Int", "chrome-debugging-port"],
   chromeDebuggingWebSocket: ["Bool", "chrome-debugging-websocket"],
 });
 
 var gToolbox, gClient;
 
 var connect = Task.async(function* () {
   window.removeEventListener("load", connect);
+
   // Initiate the connection
+  const params = new URLSearchParams(window.location.search);
+
+  // A port needs to be passed in from the query string, for instance:
+  // `./mach run -chrome chrome://devtools/content/framework/toolbox-process-window.xul?port=6080`
+  if (!params.get("port")) {
+    throw new Error("Must specify a port on the query string");
+  }
+
   let transport = yield DebuggerClient.socketConnect({
     host: Prefs.chromeDebuggingHost,
-    port: Prefs.chromeDebuggingPort,
+    port: params.get("port"),
     webSocket: Prefs.chromeDebuggingWebSocket,
   });
   gClient = new DebuggerClient(transport);
   yield gClient.connect();
-  let addonID = getParameterByName("addonID");
+  let addonID = params.get("addonID");
 
   if (addonID) {
     let { addons } = yield gClient.listAddons();
     let addonActor = addons.filter(addon => addon.id === addonID).pop();
     let isTabActor = addonActor.isWebExtension;
     openToolbox({form: addonActor, chrome: true, isTabActor});
   } else {
     let response = yield gClient.getProcess();
@@ -211,15 +219,8 @@ function quitApp() {
              .createInstance(Ci.nsISupportsPRBool);
   Services.obs.notifyObservers(quit, "quit-application-requested");
 
   let shouldProceed = !quit.data;
   if (shouldProceed) {
     Services.startup.quit(Ci.nsIAppStartup.eForceQuit);
   }
 }
-
-function getParameterByName(name) {
-  name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
-  let regex = new RegExp("[\\?&]" + name + "=([^&#]*)");
-  let results = regex.exec(window.location.search);
-  return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
-}
--- a/devtools/client/netmonitor/src/assets/styles/netmonitor.css
+++ b/devtools/client/netmonitor/src/assets/styles/netmonitor.css
@@ -885,17 +885,18 @@ body,
   border-bottom: 1px solid var(--theme-splitter-color);
 }
 
 .tree-container .treeTable .tree-section > * {
   vertical-align: middle;
 }
 
 .tree-container .treeTable .treeRow.tree-section > .treeLabelCell > .treeLabel,
-.tree-container .treeTable .treeRow.tree-section > .treeLabelCell > .treeLabel:hover {
+.tree-container .treeTable .treeRow.tree-section > .treeLabelCell > .treeLabel:hover,
+.tree-container .treeTable .treeRow.tree-section > .treeValueCell:not(:hover) * {
   color: var(--theme-body-color-alt);
 }
 
 .tree-container .treeTable .treeValueCell {
   /* FIXME: Make value cell can be reduced to shorter width */
   max-width: 0;
   padding-inline-end: 5px;
 }
--- a/devtools/client/preferences/debugger.js
+++ b/devtools/client/preferences/debugger.js
@@ -1,17 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 pref("devtools.debugger.new-debugger-frontend", true);
 
 // Enable the Debugger
 pref("devtools.debugger.enabled", true);
 pref("devtools.debugger.chrome-debugging-host", "localhost");
-pref("devtools.debugger.chrome-debugging-port", 6080);
 pref("devtools.debugger.chrome-debugging-websocket", false);
 pref("devtools.debugger.remote-host", "localhost");
 pref("devtools.debugger.remote-timeout", 20000);
 pref("devtools.debugger.pause-on-exceptions", false);
 pref("devtools.debugger.ignore-caught-exceptions", false);
 pref("devtools.debugger.source-maps-enabled", true);
 pref("devtools.debugger.client-source-maps-enabled", true);
 pref("devtools.debugger.pretty-print-enabled", true);
--- a/devtools/client/webaudioeditor/includes.js
+++ b/devtools/client/webaudioeditor/includes.js
@@ -3,17 +3,16 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 const { loader, require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
 const { Task } = require("devtools/shared/task");
-const { Class } = require("sdk/core/heritage");
 const OldEventEmitter = require("devtools/shared/old-event-emitter");
 const EventEmitter = require("devtools/shared/event-emitter");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const Services = require("Services");
 const { gDevTools } = require("devtools/client/framework/devtools");
 const { LocalizationHelper } = require("devtools/shared/l10n");
 const { ViewHelpers } = require("devtools/client/shared/widgets/view-helpers");
 
--- a/devtools/client/webaudioeditor/models.js
+++ b/devtools/client/webaudioeditor/models.js
@@ -6,279 +6,282 @@
 /**
  * Representational wrapper around AudioNodeActors. Adding and destroying
  * AudioNodes should be performed through the AudioNodes collection.
  *
  * Events:
  * - `connect`: node, destinationNode, parameter
  * - `disconnect`: node
  */
-const AudioNodeModel = Class({
-  extends: EventEmitter,
+class AudioNodeModel extends EventEmitter {
+  constructor(actor) {
+    super();
 
-  // Will be added via AudioNodes `add`
-  collection: null,
+    // Will be added via AudioNodes `add`
+    this.collection = null;
 
-  initialize: function (actor) {
     this.actor = actor;
     this.id = actor.actorID;
     this.type = actor.type;
     this.bypassable = actor.bypassable;
     this._bypassed = false;
     this.connections = [];
-  },
+  }
 
   /**
    * Stores connection data inside this instance of this audio node connecting
    * to another node (destination). If connecting to another node's AudioParam,
    * the second argument (param) must be populated with a string.
    *
    * Connecting nodes is idempotent. Upon new connection, emits "connect" event.
    *
    * @param AudioNodeModel destination
    * @param String param
    */
-  connect: function (destination, param) {
+  connect(destination, param) {
     let edge = findWhere(this.connections, { destination: destination.id, param: param });
 
     if (!edge) {
       this.connections.push({ source: this.id, destination: destination.id, param: param });
       EventEmitter.emit(this, "connect", this, destination, param);
     }
-  },
+  }
 
   /**
    * Clears out all internal connection data. Emits "disconnect" event.
    */
-  disconnect: function () {
+  disconnect() {
     this.connections.length = 0;
     EventEmitter.emit(this, "disconnect", this);
-  },
+  }
 
   /**
    * Gets the bypass status of the audio node.
    *
    * @return Boolean
    */
-  isBypassed: function () {
+  isBypassed() {
     return this._bypassed;
-  },
+  }
 
   /**
    * Sets the bypass value of an AudioNode.
    *
    * @param Boolean enable
    * @return Promise
    */
-  bypass: function (enable) {
+  bypass(enable) {
     this._bypassed = enable;
     return this.actor.bypass(enable).then(() => EventEmitter.emit(this, "bypass", this, enable));
-  },
+  }
 
   /**
    * Returns a promise that resolves to an array of objects containing
    * both a `param` name property and a `value` property.
    *
    * @return Promise->Object
    */
-  getParams: function () {
+  getParams() {
     return this.actor.getParams();
-  },
+  }
 
   /**
    * Returns a promise that resolves to an object containing an
    * array of event information and an array of automation data.
    *
    * @param String paramName
    * @return Promise->Array
    */
-  getAutomationData: function (paramName) {
+  getAutomationData(paramName) {
     return this.actor.getAutomationData(paramName);
-  },
+  }
 
   /**
    * Takes a `dagreD3.Digraph` object and adds this node to
    * the graph to be rendered.
    *
    * @param dagreD3.Digraph
    */
-  addToGraph: function (graph) {
+  addToGraph(graph) {
     graph.addNode(this.id, {
       type: this.type,
       label: this.type.replace(/Node$/, ""),
       id: this.id,
       bypassed: this._bypassed
     });
-  },
+  }
 
   /**
    * Takes a `dagreD3.Digraph` object and adds edges to
    * the graph to be rendered. Separate from `addToGraph`,
    * as while we depend on D3/Dagre's constraints, we cannot
    * add edges for nodes that have not yet been added to the graph.
    *
    * @param dagreD3.Digraph
    */
-  addEdgesToGraph: function (graph) {
+  addEdgesToGraph(graph) {
     for (let edge of this.connections) {
       let options = {
         source: this.id,
         target: edge.destination
       };
 
       // Only add `label` if `param` specified, as this is an AudioParam
       // connection then. `label` adds the magic to render with dagre-d3,
       // and `param` is just more explicitly the param, ignoring
       // implementation details.
       if (edge.param) {
         options.label = options.param = edge.param;
       }
 
       graph.addEdge(null, this.id, edge.destination, options);
     }
-  },
+  }
 
-  toString: () => "[object AudioNodeModel]",
-});
+  toString() {
+    return "[object AudioNodeModel]";
+  }
+}
 
 
 /**
  * Constructor for a Collection of `AudioNodeModel` models.
  *
  * Events:
  * - `add`: node
  * - `remove`: node
  * - `connect`: node, destinationNode, parameter
  * - `disconnect`: node
  */
-const AudioNodesCollection = Class({
-  extends: EventEmitter,
+class AudioNodesCollection extends EventEmitter {
+  constructor() {
+    super();
 
-  model: AudioNodeModel,
-
-  initialize: function () {
+    this.model = AudioNodeModel;
     this.models = new Set();
     this._onModelEvent = this._onModelEvent.bind(this);
-  },
+  }
 
   /**
    * Iterates over all models within the collection, calling `fn` with the
    * model as the first argument.
    *
    * @param Function fn
    */
-  forEach: function (fn) {
+  forEach(fn) {
     this.models.forEach(fn);
-  },
+  }
 
   /**
    * Creates a new AudioNodeModel, passing through arguments into the AudioNodeModel
    * constructor, and adds the model to the internal collection store of this
    * instance.
    *
    * Emits "add" event on instance when completed.
    *
    * @param Object obj
    * @return AudioNodeModel
    */
-  add: function (obj) {
+  add(obj) {
     let node = new this.model(obj);
     node.collection = this;
 
     this.models.add(node);
 
     node.on("*", this._onModelEvent);
     EventEmitter.emit(this, "add", node);
     return node;
-  },
+  }
 
   /**
    * Removes an AudioNodeModel from the internal collection. Calls `delete` method
    * on the model, and emits "remove" on this instance.
    *
    * @param AudioNodeModel node
    */
-  remove: function (node) {
+  remove(node) {
     this.models.delete(node);
     EventEmitter.emit(this, "remove", node);
-  },
+  }
 
   /**
    * Empties out the internal collection of all AudioNodeModels.
    */
-  reset: function () {
+  reset() {
     this.models.clear();
-  },
+  }
 
   /**
    * Takes an `id` from an AudioNodeModel and returns the corresponding
    * AudioNodeModel within the collection that matches that id. Returns `null`
    * if not found.
    *
    * @param Number id
    * @return AudioNodeModel|null
    */
-  get: function (id) {
+  get(id) {
     return findWhere(this.models, { id: id });
-  },
+  }
 
   /**
    * Returns the count for how many models are a part of this collection.
    *
    * @return Number
    */
   get length() {
     return this.models.size;
-  },
+  }
 
   /**
    * Returns detailed information about the collection. used during tests
    * to query state. Returns an object with information on node count,
    * how many edges are within the data graph, as well as how many of those edges
    * are for AudioParams.
    *
    * @return Object
    */
-  getInfo: function () {
+  getInfo() {
     let info = {
       nodes: this.length,
       edges: 0,
       paramEdges: 0
     };
 
     this.models.forEach(node => {
       let paramEdgeCount = node.connections.filter(edge => edge.param).length;
       info.edges += node.connections.length - paramEdgeCount;
       info.paramEdges += paramEdgeCount;
     });
     return info;
-  },
+  }
 
   /**
    * Adds all nodes within the collection to the passed in graph,
    * as well as their corresponding edges.
    *
    * @param dagreD3.Digraph
    */
-  populateGraph: function (graph) {
+  populateGraph(graph) {
     this.models.forEach(node => node.addToGraph(graph));
     this.models.forEach(node => node.addEdgesToGraph(graph));
-  },
+  }
 
   /**
    * Called when a stored model emits any event. Used to manage
    * event propagation, or listening to model events to react, like
    * removing a model from the collection when it's destroyed.
    */
-  _onModelEvent: function (eventName, node, ...args) {
+  _onModelEvent(eventName, node, ...args) {
     if (eventName === "remove") {
       // If a `remove` event from the model, remove it
       // from the collection, and let the method handle the emitting on
       // the collection
       this.remove(node);
     } else {
       // Pipe the event to the collection
       EventEmitter.emit(this, eventName, node, ...args);
     }
-  },
+  }
 
-  toString: () => "[object AudioNodeCollection]",
-});
+  toString() {
+    return "[object AudioNodeCollection]";
+  }
+}
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -1797,17 +1797,16 @@ WebConsoleActor.prototype =
     let result = WebConsoleUtils.cloneObject(message);
 
     result.workerType = WebConsoleUtils.getWorkerType(result) || "none";
 
     delete result.wrappedJSObject;
     delete result.ID;
     delete result.innerID;
     delete result.consoleID;
-    delete result.originAttributes;
 
     result.arguments = Array.map(message.arguments || [], (obj) => {
       let dbgObj = this.makeDebuggeeValue(obj, useObjectGlobal);
       return this.createValueGrip(dbgObj);
     });
 
     result.styles = Array.map(message.styles || [], (string) => {
       return this.createValueGrip(string);
--- a/dom/cache/Connection.cpp
+++ b/dom/cache/Connection.cpp
@@ -156,16 +156,22 @@ Connection::Clone(bool aReadOnly, mozISt
 
   nsCOMPtr<mozIStorageConnection> wrapped = new Connection(conn);
   wrapped.forget(aConnectionOut);
 
   return rv;
 }
 
 NS_IMETHODIMP
+Connection::Interrupt()
+{
+  return mBase->Interrupt();
+}
+
+NS_IMETHODIMP
 Connection::GetDefaultPageSize(int32_t* aSizeOut)
 {
   return mBase->GetDefaultPageSize(aSizeOut);
 }
 
 NS_IMETHODIMP
 Connection::GetConnectionReady(bool* aReadyOut)
 {
--- a/dom/canvas/WebGL2ContextSamplers.cpp
+++ b/dom/canvas/WebGL2ContextSamplers.cpp
@@ -24,17 +24,17 @@ WebGL2Context::CreateSampler()
 }
 
 void
 WebGL2Context::DeleteSampler(WebGLSampler* sampler)
 {
     if (!ValidateDeleteObject("deleteSampler", sampler))
         return;
 
-    for (int n = 0; n < mGLMaxTextureUnits; n++) {
+    for (uint32_t n = 0; n < mGLMaxTextureUnits; n++) {
         if (mBoundSamplers[n] == sampler) {
             mBoundSamplers[n] = nullptr;
 
             InvalidateResolveCacheForTextureWithTexUnit(n);
         }
     }
 
     sampler->RequestDelete();
@@ -54,18 +54,18 @@ void
 WebGL2Context::BindSampler(GLuint unit, WebGLSampler* sampler)
 {
     if (IsContextLost())
         return;
 
     if (sampler && !ValidateObject("bindSampler", *sampler))
         return;
 
-    if (GLint(unit) >= mGLMaxTextureUnits)
-        return ErrorInvalidValue("bindSampler: unit must be < %d", mGLMaxTextureUnits);
+    if (unit >= mGLMaxTextureUnits)
+        return ErrorInvalidValue("bindSampler: unit must be < %u", mGLMaxTextureUnits);
 
     ////
 
     gl->MakeCurrent();
     gl->fBindSampler(unit, sampler ? sampler->mGLName : 0);
 
     InvalidateResolveCacheForTextureWithTexUnit(unit);
     mBoundSamplers[unit] = sampler;
--- a/dom/canvas/WebGL2ContextState.cpp
+++ b/dom/canvas/WebGL2ContextState.cpp
@@ -92,20 +92,20 @@ WebGL2Context::GetParameter(JSContext* c
 
     case LOCAL_GL_UNPACK_SKIP_PIXELS:
       return JS::Int32Value(mPixelStore_UnpackSkipPixels);
 
     case LOCAL_GL_UNPACK_SKIP_ROWS:
       return JS::Int32Value(mPixelStore_UnpackSkipRows);
 
     case LOCAL_GL_MAX_3D_TEXTURE_SIZE:
-      return JS::Int32Value(mImplMax3DTextureSize);
+      return JS::Int32Value(mGLMax3DTextureSize);
 
     case LOCAL_GL_MAX_ARRAY_TEXTURE_LAYERS:
-      return JS::Int32Value(mImplMaxArrayTextureLayers);
+      return JS::Int32Value(mGLMaxArrayTextureLayers);
 
     case LOCAL_GL_MAX_VARYING_COMPONENTS: {
       // On OS X Core Profile this is buggy.  The spec says that the
       // value is 4 * GL_MAX_VARYING_VECTORS
       GLint val;
       gl->fGetIntegerv(LOCAL_GL_MAX_VARYING_VECTORS, &val);
       return JS::Int32Value(4*val);
     }
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -99,20 +99,16 @@ WebGLContextOptions::WebGLContextOptions
     , preserveDrawingBuffer(false)
     , failIfMajorPerformanceCaveat(false)
 {
     // Set default alpha state based on preference.
     if (gfxPrefs::WebGLDefaultNoAlpha())
         alpha = false;
 }
 
-
-/*static*/ const uint32_t WebGLContext::kMinMaxColorAttachments = 4;
-/*static*/ const uint32_t WebGLContext::kMinMaxDrawBuffers = 4;
-
 WebGLContext::WebGLContext()
     : WebGLContextUnchecked(nullptr)
     , mMaxPerfWarnings(gfxPrefs::WebGLMaxPerfWarnings())
     , mNumPerfWarnings(0)
     , mMaxAcceptableFBStatusInvals(gfxPrefs::WebGLMaxAcceptableFBStatusInvals())
     , mDataAllocGLCallCount(0)
     , mBufferFetchingIsVerified(false)
     , mBufferFetchingHasPerVertex(false)
@@ -129,17 +125,16 @@ WebGLContext::WebGLContext()
     , mAllowFBInvalidation(gfxPrefs::WebGLFBInvalidation())
 {
     mGeneration = 0;
     mInvalidated = false;
     mCapturedFrameInvalidated = false;
     mShouldPresent = true;
     mResetLayer = true;
     mOptionsFrozen = false;
-    mMinCapability = false;
     mDisableExtensions = false;
     mIsMesa = false;
     mEmitContextLostErrorOnce = false;
     mWebGLError = 0;
     mUnderlyingGLError = 0;
 
     mActiveTexture = 0;
 
@@ -2456,16 +2451,28 @@ WebGLContext::ValidateArrayBufferView(co
     *out_byteLen = elemCount * elemSize;
     return true;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // XPCOM goop
 
 void
+WebGLContext::UpdateMaxDrawBuffers()
+{
+    mGLMaxColorAttachments = gl->GetIntAs<uint32_t>(LOCAL_GL_MAX_COLOR_ATTACHMENTS);
+    mGLMaxDrawBuffers = gl->GetIntAs<uint32_t>(LOCAL_GL_MAX_DRAW_BUFFERS);
+
+    // WEBGL_draw_buffers:
+    // "The value of the MAX_COLOR_ATTACHMENTS_WEBGL parameter must be greater than or
+    //  equal to that of the MAX_DRAW_BUFFERS_WEBGL parameter."
+    mGLMaxDrawBuffers = std::min(mGLMaxDrawBuffers, mGLMaxColorAttachments);
+}
+
+void
 ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
                             const std::vector<IndexedBufferBinding>& field,
                             const char* name, uint32_t flags)
 {
     for (const auto& cur : field) {
         ImplCycleCollectionTraverse(callback, cur.mBufferBinding, name, flags);
     }
 }
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -50,40 +50,16 @@
 #include "nsWrapperCache.h"
 #include "nsLayoutUtils.h"
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
 #include "mozilla/dom/WebGL2RenderingContextBinding.h"
 
 class nsIDocShell;
 
 /*
- * Minimum value constants defined in 6.2 State Tables of OpenGL ES - 2.0.25
- *   https://bugzilla.mozilla.org/show_bug.cgi?id=686732
- *
- * Exceptions: some of the following values are set to higher values than in the spec because
- * the values in the spec are ridiculously low. They are explicitly marked below
- */
-#define MINVALUE_GL_MAX_TEXTURE_SIZE                  1024  // Different from the spec, which sets it to 64 on page 162
-#define MINVALUE_GL_MAX_CUBE_MAP_TEXTURE_SIZE         512   // Different from the spec, which sets it to 16 on page 162
-#define MINVALUE_GL_MAX_VERTEX_ATTRIBS                8     // Page 164
-#define MINVALUE_GL_MAX_FRAGMENT_UNIFORM_VECTORS      16    // Page 164
-#define MINVALUE_GL_MAX_VERTEX_UNIFORM_VECTORS        128   // Page 164
-#define MINVALUE_GL_MAX_VARYING_VECTORS               8     // Page 164
-#define MINVALUE_GL_MAX_TEXTURE_IMAGE_UNITS           8     // Page 164
-#define MINVALUE_GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS    0     // Page 164
-#define MINVALUE_GL_MAX_RENDERBUFFER_SIZE             1024  // Different from the spec, which sets it to 1 on page 164
-#define MINVALUE_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS  8     // Page 164
-
-/*
- * Minimum value constants define in 6.2 State Tables of OpenGL ES - 3.0.4
- */
-#define MINVALUE_GL_MAX_3D_TEXTURE_SIZE             256
-#define MINVALUE_GL_MAX_ARRAY_TEXTURE_LAYERS        256
-
-/*
  * WebGL-only GLenums
  */
 #define LOCAL_GL_BROWSER_DEFAULT_WEBGL                       0x9244
 #define LOCAL_GL_CONTEXT_LOST_WEBGL                          0x9242
 #define LOCAL_GL_MAX_CLIENT_WAIT_TIMEOUT_WEBGL               0x9247
 #define LOCAL_GL_UNPACK_COLORSPACE_CONVERSION_WEBGL          0x9243
 #define LOCAL_GL_UNPACK_FLIP_Y_WEBGL                         0x9240
 #define LOCAL_GL_UNPACK_PREMULTIPLY_ALPHA_WEBGL              0x9241
@@ -327,19 +303,16 @@ class WebGLContext
         UNPACK_REQUIRE_FASTPATH = 0x10001,
         CONTEXT_LOST_WEBGL = 0x9242,
         UNPACK_COLORSPACE_CONVERSION_WEBGL = 0x9243,
         BROWSER_DEFAULT_WEBGL = 0x9244,
         UNMASKED_VENDOR_WEBGL = 0x9245,
         UNMASKED_RENDERER_WEBGL = 0x9246
     };
 
-    static const uint32_t kMinMaxColorAttachments;
-    static const uint32_t kMinMaxDrawBuffers;
-
     const uint32_t mMaxPerfWarnings;
     mutable uint64_t mNumPerfWarnings;
     const uint32_t mMaxAcceptableFBStatusInvals;
 
 public:
     WebGLContext();
 
 protected:
@@ -497,18 +470,16 @@ public:
     // It only clears the buffers we specify, and can reset its state without
     // first having to query anything, as WebGL knows its state at all times.
     void ForceClearFramebufferWithDefaultValues(GLbitfield bufferBits, bool fakeNoAlpha);
 
     // Calls ForceClearFramebufferWithDefaultValues() for the Context's 'screen'.
     void ClearScreen();
     void ClearBackbufferIfNeeded();
 
-    bool MinCapabilityMode() const { return mMinCapability; }
-
     void RunContextLossTimer();
     void UpdateContextLossStatus();
     void EnqueueUpdateContextLossStatus();
 
     bool TryToRestoreContext();
 
     void AssertCachedBindings();
     void AssertCachedGlobalState();
@@ -1440,17 +1411,16 @@ protected:
 
     WebGLContextOptions mOptions;
 
     bool mInvalidated;
     bool mCapturedFrameInvalidated;
     bool mResetLayer;
     bool mLayerIsMirror;
     bool mOptionsFrozen;
-    bool mMinCapability;
     bool mDisableExtensions;
     bool mIsMesa;
     bool mLoseContextOnMemoryPressure;
     bool mCanLoseContextInForeground;
     bool mRestoreWhenVisible;
     bool mShouldPresent;
     bool mBackbufferNeedsClear;
     bool mDisableFragHighP;
@@ -1467,61 +1437,64 @@ protected:
     GLenum mUnderlyingGLError;
     GLenum GetAndFlushUnderlyingGLErrors();
 
     bool mBypassShaderValidation;
 
     webgl::ShaderValidator* CreateShaderValidator(GLenum shaderType) const;
 
     // some GL constants
+    uint32_t mGLMaxTextureUnits;
+
     uint32_t mGLMaxVertexAttribs;
-    int32_t mGLMaxTextureUnits;
-    int32_t mGLMaxTextureImageUnits;
-    int32_t mGLMaxVertexTextureImageUnits;
-    int32_t mGLMaxVaryingVectors;
-    int32_t mGLMaxFragmentUniformVectors;
-    int32_t mGLMaxVertexUniformVectors;
-    uint32_t  mGLMaxTransformFeedbackSeparateAttribs;
-    GLuint  mGLMaxUniformBufferBindings;
+    uint32_t mGLMaxFragmentUniformVectors;
+    uint32_t mGLMaxVertexUniformVectors;
+    uint32_t mGLMaxVaryingVectors;
 
-    // What is supported:
+    uint32_t mGLMaxTransformFeedbackSeparateAttribs;
+    uint32_t mGLMaxUniformBufferBindings;
+
+    uint32_t mGLMaxVertexTextureImageUnits;
+    uint32_t mGLMaxFragmentTextureImageUnits;
+    uint32_t mGLMaxCombinedTextureImageUnits;
+
     uint32_t mGLMaxColorAttachments;
     uint32_t mGLMaxDrawBuffers;
-    // What we're allowing:
-    uint32_t mImplMaxColorAttachments;
-    uint32_t mImplMaxDrawBuffers;
 
-    uint32_t mImplMaxViewportDims[2];
+    uint32_t mGLMaxViewportDims[2];
 
 public:
     GLenum LastColorAttachmentEnum() const {
-        return LOCAL_GL_COLOR_ATTACHMENT0 + mImplMaxColorAttachments - 1;
+        return LOCAL_GL_COLOR_ATTACHMENT0 + mGLMaxColorAttachments - 1;
     }
 
     const decltype(mOptions)& Options() const { return mOptions; }
 
 protected:
 
     // Texture sizes are often not actually the GL values. Let's be explicit that these
     // are implementation limits.
-    uint32_t mImplMaxTextureSize;
-    uint32_t mImplMaxCubeMapTextureSize;
-    uint32_t mImplMax3DTextureSize;
-    uint32_t mImplMaxArrayTextureLayers;
-    uint32_t mImplMaxRenderbufferSize;
+    uint32_t mGLMaxTextureSize;
+    uint32_t mGLMaxCubeMapTextureSize;
+    uint32_t mGLMax3DTextureSize;
+    uint32_t mGLMaxArrayTextureLayers;
+    uint32_t mGLMaxRenderbufferSize;
 
 public:
     GLuint MaxVertexAttribs() const {
         return mGLMaxVertexAttribs;
     }
 
     GLuint GLMaxTextureUnits() const {
         return mGLMaxTextureUnits;
     }
 
+    float mGLAliasedLineWidthRange[2];
+    float mGLAliasedPointSizeRange[2];
+
     bool IsFormatValidForFB(TexInternalFormat format) const;
 
 protected:
     // Represents current status of the context with respect to context loss.
     // That is, whether the context is lost, and what part of the context loss
     // process we currently are at.
     // This is used to support the WebGL spec's asyncronous nature in handling
     // context loss.
@@ -2079,16 +2052,18 @@ public:
     UniquePtr<webgl::FormatUsageAuthority> mFormatUsage;
 
     virtual UniquePtr<webgl::FormatUsageAuthority>
     CreateFormatUsage(gl::GLContext* gl) const = 0;
 
 
     const decltype(mBound2DTextures)* TexListForElemType(GLenum elemType) const;
 
+    void UpdateMaxDrawBuffers();
+
     // Friend list
     friend class ScopedCopyTexImageSource;
     friend class ScopedResolveTexturesForDraw;
     friend class ScopedUnpackReset;
     friend class webgl::TexUnpackBlob;
     friend class webgl::TexUnpackBytes;
     friend class webgl::TexUnpackImage;
     friend class webgl::TexUnpackSurface;
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -79,17 +79,17 @@ using namespace mozilla::gl;
 
 void
 WebGLContext::ActiveTexture(GLenum texture)
 {
     if (IsContextLost())
         return;
 
     if (texture < LOCAL_GL_TEXTURE0 ||
-        texture >= LOCAL_GL_TEXTURE0 + uint32_t(mGLMaxTextureUnits))
+        texture >= LOCAL_GL_TEXTURE0 + mGLMaxTextureUnits)
     {
         return ErrorInvalidEnum(
             "ActiveTexture: texture unit %d out of range. "
             "Accepted values range from TEXTURE0 to TEXTURE0 + %d. "
             "Notice that TEXTURE0 != 0.",
             texture, mGLMaxTextureUnits);
     }
 
@@ -425,17 +425,17 @@ WebGLContext::DeleteTexture(WebGLTexture
 
     if (mBoundDrawFramebuffer)
         mBoundDrawFramebuffer->DetachTexture(funcName, tex);
 
     if (mBoundReadFramebuffer)
         mBoundReadFramebuffer->DetachTexture(funcName, tex);
 
     GLuint activeTexture = mActiveTexture;
-    for (int32_t i = 0; i < mGLMaxTextureUnits; i++) {
+    for (uint32_t i = 0; i < mGLMaxTextureUnits; i++) {
         if (mBound2DTextures[i] == tex ||
             mBoundCubeMapTextures[i] == tex ||
             mBound3DTextures[i] == tex ||
             mBound2DArrayTextures[i] == tex)
         {
             ActiveTexture(LOCAL_GL_TEXTURE0 + i);
             BindTexture(tex->Target().get(), nullptr);
         }
@@ -2252,18 +2252,18 @@ void
 WebGLContext::Viewport(GLint x, GLint y, GLsizei width, GLsizei height)
 {
     if (IsContextLost())
         return;
 
     if (width < 0 || height < 0)
         return ErrorInvalidValue("viewport: negative size");
 
-    width = std::min(width, (GLsizei)mImplMaxViewportDims[0]);
-    height = std::min(height, (GLsizei)mImplMaxViewportDims[1]);
+    width = std::min(width, (GLsizei)mGLMaxViewportDims[0]);
+    height = std::min(height, (GLsizei)mGLMaxViewportDims[1]);
 
     MakeContextCurrent();
     gl->fViewport(x, y, width, height);
 
     mViewportX = x;
     mViewportY = y;
     mViewportWidth = width;
     mViewportHeight = height;
--- a/dom/canvas/WebGLContextState.cpp
+++ b/dom/canvas/WebGLContextState.cpp
@@ -176,64 +176,25 @@ WebGLContext::GetParameter(JSContext* cx
 {
     const char funcName[] = "getParameter";
 
     if (IsContextLost())
         return JS::NullValue();
 
     MakeContextCurrent();
 
-    if (MinCapabilityMode()) {
-        switch(pname) {
-            ////////////////////////////
-            // Single-value params
-
-            // int
-            case LOCAL_GL_MAX_VERTEX_ATTRIBS:
-                return JS::Int32Value(MINVALUE_GL_MAX_VERTEX_ATTRIBS);
-
-            case LOCAL_GL_MAX_FRAGMENT_UNIFORM_VECTORS:
-                return JS::Int32Value(MINVALUE_GL_MAX_FRAGMENT_UNIFORM_VECTORS);
-
-            case LOCAL_GL_MAX_VERTEX_UNIFORM_VECTORS:
-                return JS::Int32Value(MINVALUE_GL_MAX_VERTEX_UNIFORM_VECTORS);
-
-            case LOCAL_GL_MAX_VARYING_VECTORS:
-                return JS::Int32Value(MINVALUE_GL_MAX_VARYING_VECTORS);
-
-            case LOCAL_GL_MAX_TEXTURE_SIZE:
-                return JS::Int32Value(MINVALUE_GL_MAX_TEXTURE_SIZE);
-
-            case LOCAL_GL_MAX_CUBE_MAP_TEXTURE_SIZE:
-                return JS::Int32Value(MINVALUE_GL_MAX_CUBE_MAP_TEXTURE_SIZE);
-
-            case LOCAL_GL_MAX_TEXTURE_IMAGE_UNITS:
-                return JS::Int32Value(MINVALUE_GL_MAX_TEXTURE_IMAGE_UNITS);
-
-            case LOCAL_GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS:
-                return JS::Int32Value(MINVALUE_GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS);
-
-            case LOCAL_GL_MAX_RENDERBUFFER_SIZE:
-                return JS::Int32Value(MINVALUE_GL_MAX_RENDERBUFFER_SIZE);
-
-            default:
-                // Return the real value; we're not overriding this one
-                break;
-        }
-    }
-
     if (IsWebGL2() || IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers)) {
         if (pname == LOCAL_GL_MAX_COLOR_ATTACHMENTS) {
-            return JS::Int32Value(mImplMaxColorAttachments);
+            return JS::Int32Value(mGLMaxColorAttachments);
 
         } else if (pname == LOCAL_GL_MAX_DRAW_BUFFERS) {
-            return JS::Int32Value(mImplMaxDrawBuffers);
+            return JS::Int32Value(mGLMaxDrawBuffers);
 
         } else if (pname >= LOCAL_GL_DRAW_BUFFER0 &&
-                   pname < GLenum(LOCAL_GL_DRAW_BUFFER0 + mImplMaxDrawBuffers))
+                   pname < GLenum(LOCAL_GL_DRAW_BUFFER0 + mGLMaxDrawBuffers))
         {
             GLint ret = LOCAL_GL_NONE;
             if (!mBoundDrawFramebuffer) {
                 if (pname == LOCAL_GL_DRAW_BUFFER0) {
                     ret = gl->Screen()->GetDrawBufferMode();
                 }
             } else {
                 gl->fGetIntegerv(pname, &ret);
@@ -415,21 +376,17 @@ WebGLContext::GetParameter(JSContext* cx
             return JS::Int32Value(refValue & stencilMask);
         }
 
         case LOCAL_GL_STENCIL_CLEAR_VALUE:
         case LOCAL_GL_UNPACK_ALIGNMENT:
         case LOCAL_GL_PACK_ALIGNMENT:
         case LOCAL_GL_SUBPIXEL_BITS:
         case LOCAL_GL_SAMPLE_BUFFERS:
-        case LOCAL_GL_SAMPLES:
-        case LOCAL_GL_MAX_VERTEX_ATTRIBS:
-        case LOCAL_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS:
-        case LOCAL_GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS:
-        case LOCAL_GL_MAX_TEXTURE_IMAGE_UNITS: {
+        case LOCAL_GL_SAMPLES: {
             GLint i = 0;
             gl->fGetIntegerv(pname, &i);
             return JS::Int32Value(i);
         }
 
         case LOCAL_GL_RED_BITS:
         case LOCAL_GL_GREEN_BITS:
         case LOCAL_GL_BLUE_BITS:
@@ -440,23 +397,35 @@ WebGLContext::GetParameter(JSContext* cx
             GLint val;
             if (!GetChannelBits(funcName, pname, &val))
                 return JS::NullValue();
 
             return JS::Int32Value(val);
         }
 
         case LOCAL_GL_MAX_TEXTURE_SIZE:
-            return JS::Int32Value(mImplMaxTextureSize);
+            return JS::Int32Value(mGLMaxTextureSize);
 
         case LOCAL_GL_MAX_CUBE_MAP_TEXTURE_SIZE:
-            return JS::Int32Value(mImplMaxCubeMapTextureSize);
+            return JS::Int32Value(mGLMaxCubeMapTextureSize);
 
         case LOCAL_GL_MAX_RENDERBUFFER_SIZE:
-            return JS::Int32Value(mImplMaxRenderbufferSize);
+            return JS::Int32Value(mGLMaxRenderbufferSize);
+
+        case LOCAL_GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS:
+            return JS::Int32Value(mGLMaxVertexTextureImageUnits);
+
+        case LOCAL_GL_MAX_TEXTURE_IMAGE_UNITS:
+            return JS::Int32Value(mGLMaxFragmentTextureImageUnits);
+
+        case LOCAL_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS:
+            return JS::Int32Value(mGLMaxCombinedTextureImageUnits);
+
+        case LOCAL_GL_MAX_VERTEX_ATTRIBS:
+            return JS::Int32Value(mGLMaxVertexAttribs);
 
         case LOCAL_GL_MAX_VERTEX_UNIFORM_VECTORS:
             return JS::Int32Value(mGLMaxVertexUniformVectors);
 
         case LOCAL_GL_MAX_FRAGMENT_UNIFORM_VECTORS:
             return JS::Int32Value(mGLMaxFragmentUniformVectors);
 
         case LOCAL_GL_MAX_VARYING_VECTORS:
@@ -528,25 +497,31 @@ WebGLContext::GetParameter(JSContext* cx
 
         ////////////////////////////////
         // Complex values
 
         // 2 floats
         case LOCAL_GL_DEPTH_RANGE:
         case LOCAL_GL_ALIASED_POINT_SIZE_RANGE:
         case LOCAL_GL_ALIASED_LINE_WIDTH_RANGE: {
-            GLenum driverPName = pname;
-            if (gl->IsCoreProfile() &&
-                driverPName == LOCAL_GL_ALIASED_POINT_SIZE_RANGE)
-            {
-                driverPName = LOCAL_GL_POINT_SIZE_RANGE;
+            GLfloat fv[2] = { 0 };
+            switch (pname) {
+            case LOCAL_GL_ALIASED_POINT_SIZE_RANGE:
+                fv[0] = mGLAliasedPointSizeRange[0];
+                fv[1] = mGLAliasedPointSizeRange[1];
+                break;
+            case LOCAL_GL_ALIASED_LINE_WIDTH_RANGE:
+                fv[0] = mGLAliasedLineWidthRange[0];
+                fv[1] = mGLAliasedLineWidthRange[1];
+                break;
+            // case LOCAL_GL_DEPTH_RANGE:
+            default:
+                gl->fGetFloatv(pname, fv);
+                break;
             }
-
-            GLfloat fv[2] = { 0 };
-            gl->fGetFloatv(driverPName, fv);
             JSObject* obj = dom::Float32Array::Create(cx, this, 2, fv);
             if (!obj) {
                 rv = NS_ERROR_OUT_OF_MEMORY;
             }
             return JS::ObjectOrNullValue(obj);
         }
 
         // 4 floats
@@ -558,18 +533,17 @@ WebGLContext::GetParameter(JSContext* cx
             if (!obj) {
                 rv = NS_ERROR_OUT_OF_MEMORY;
             }
             return JS::ObjectOrNullValue(obj);
         }
 
         // 2 ints
         case LOCAL_GL_MAX_VIEWPORT_DIMS: {
-            GLint iv[2] = { 0 };
-            gl->fGetIntegerv(pname, iv);
+            GLint iv[2] = { GLint(mGLMaxViewportDims[0]), GLint(mGLMaxViewportDims[1]) };
             JSObject* obj = dom::Int32Array::Create(cx, this, 2, iv);
             if (!obj) {
                 rv = NS_ERROR_OUT_OF_MEMORY;
             }
             return JS::ObjectOrNullValue(obj);
         }
 
         // 4 ints
--- a/dom/canvas/WebGLContextValidate.cpp
+++ b/dom/canvas/WebGLContextValidate.cpp
@@ -29,16 +29,76 @@
 #include "WebGLValidateStrings.h"
 #include "WebGLVertexArray.h"
 #include "WebGLVertexAttribData.h"
 
 #if defined(MOZ_WIDGET_COCOA)
 #include "nsCocoaFeatures.h"
 #endif
 
+////////////////////
+// Minimum value constants defined in GLES 2.0.25 $6.2 "State Tables":
+const uint32_t kMinMaxVertexAttribs          =   8; // Page 164
+const uint32_t kMinMaxVertexUniformVectors   = 128; // Page 164
+const uint32_t kMinMaxFragmentUniformVectors =  16; // Page 164
+const uint32_t kMinMaxVaryingVectors         =   8; // Page 164
+
+const uint32_t kMinMaxVertexTextureImageUnits   = 0; // Page 164
+const uint32_t kMinMaxFragmentTextureImageUnits = 8; // Page 164
+const uint32_t kMinMaxCombinedTextureImageUnits = 8; // Page 164
+
+const uint32_t kMinMaxColorAttachments = 4;
+const uint32_t kMinMaxDrawBuffers      = 4;
+
+// These few deviate from the spec: (The minimum values in the spec are ridiculously low)
+const uint32_t kMinMaxTextureSize        = 1024; // ES2 spec says `64` (p162)
+const uint32_t kMinMaxCubeMapTextureSize =  512; // ES2 spec says `16` (p162)
+const uint32_t kMinMaxRenderbufferSize   = 1024; // ES2 spec says `1` (p164)
+
+// Minimum value constants defined in GLES 3.0.4 $6.2 "State Tables":
+const uint32_t kMinMax3DTextureSize      = 256;
+const uint32_t kMinMaxArrayTextureLayers = 256;
+
+////////////////////
+// "Common" but usable values to avoid WebGL fingerprinting:
+const uint32_t kCommonMaxTextureSize        = 2048;
+const uint32_t kCommonMaxCubeMapTextureSize = 2048;
+const uint32_t kCommonMaxRenderbufferSize   = 2048;
+
+const uint32_t kCommonMaxVertexTextureImageUnits   =  8;
+const uint32_t kCommonMaxFragmentTextureImageUnits =  8;
+const uint32_t kCommonMaxCombinedTextureImageUnits = 16;
+
+const uint32_t kCommonMaxVertexAttribs          =  16;
+const uint32_t kCommonMaxVertexUniformVectors   = 256;
+const uint32_t kCommonMaxFragmentUniformVectors = 224;
+const uint32_t kCommonMaxVaryingVectors         =   8;
+
+const uint32_t kCommonMaxViewportDims = 4096;
+
+// The following ranges came from a 2013 Moto E and an old macbook.
+const float kCommonAliasedPointSizeRangeMin =  1;
+const float kCommonAliasedPointSizeRangeMax = 63;
+const float kCommonAliasedLineWidthRangeMin =  1;
+const float kCommonAliasedLineWidthRangeMax =  5;
+
+template<class T>
+static bool
+RestrictCap(T* const cap, const T restrictedVal)
+{
+    if (*cap < restrictedVal) {
+        return false; // already too low!
+    }
+
+    *cap = restrictedVal;
+    return true;
+}
+
+////////////////////
+
 namespace mozilla {
 
 bool
 WebGLContext::ValidateBlendEquationEnum(GLenum mode, const char* info)
 {
     switch (mode) {
     case LOCAL_GL_FUNC_ADD:
     case LOCAL_GL_FUNC_SUBTRACT:
@@ -361,25 +421,21 @@ WebGLContext::InitAndValidateGL(FailureR
     if (error != LOCAL_GL_NO_ERROR) {
         const nsPrintfCString reason("GL error 0x%x occurred during OpenGL context"
                                      " initialization, before WebGL initialization!",
                                      error);
         *out_failReason = { "FEATURE_FAILURE_WEBGL_GLERR_1", reason };
         return false;
     }
 
-    mMinCapability = gfxPrefs::WebGLMinCapabilityMode();
     mDisableExtensions = gfxPrefs::WebGLDisableExtensions();
     mLoseContextOnMemoryPressure = gfxPrefs::WebGLLoseContextOnMemoryPressure();
     mCanLoseContextInForeground = gfxPrefs::WebGLCanLoseContextInForeground();
     mRestoreWhenVisible = gfxPrefs::WebGLRestoreWhenVisible();
 
-    if (MinCapabilityMode())
-        mDisableFragHighP = true;
-
     // These are the default values, see 6.2 State tables in the
     // OpenGL ES 2.0.25 spec.
     mColorWriteMask[0] = 1;
     mColorWriteMask[1] = 1;
     mColorWriteMask[2] = 1;
     mColorWriteMask[3] = 1;
     mDepthWriteMask = 1;
     mColorClearValue[0] = 0.f;
@@ -440,145 +496,178 @@ WebGLContext::InitAndValidateGL(FailureR
     mCurrentProgram = nullptr;
 
     mBoundDrawFramebuffer = nullptr;
     mBoundReadFramebuffer = nullptr;
     mBoundRenderbuffer = nullptr;
 
     MakeContextCurrent();
 
-    if (MinCapabilityMode())
-        mGLMaxVertexAttribs = MINVALUE_GL_MAX_VERTEX_ATTRIBS;
-    else
-        gl->GetUIntegerv(LOCAL_GL_MAX_VERTEX_ATTRIBS, &mGLMaxVertexAttribs);
+    gl->GetUIntegerv(LOCAL_GL_MAX_VERTEX_ATTRIBS, &mGLMaxVertexAttribs);
 
     if (mGLMaxVertexAttribs < 8) {
         const nsPrintfCString reason("GL_MAX_VERTEX_ATTRIBS: %d is < 8!",
                                      mGLMaxVertexAttribs);
         *out_failReason = { "FEATURE_FAILURE_WEBGL_V_ATRB", reason };
         return false;
     }
 
     // Note: GL_MAX_TEXTURE_UNITS is fixed at 4 for most desktop hardware,
     // even though the hardware supports much more.  The
     // GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS value is the accurate value.
-    if (MinCapabilityMode())
-        mGLMaxTextureUnits = MINVALUE_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS;
-    else
-        gl->fGetIntegerv(LOCAL_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &mGLMaxTextureUnits);
+    gl->GetUIntegerv(LOCAL_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &mGLMaxTextureUnits);
+    mGLMaxCombinedTextureImageUnits = mGLMaxTextureUnits;
 
     if (mGLMaxTextureUnits < 8) {
-        const nsPrintfCString reason("GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS: %d is < 8!",
+        const nsPrintfCString reason("GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS: %u is < 8!",
                                      mGLMaxTextureUnits);
         *out_failReason = { "FEATURE_FAILURE_WEBGL_T_UNIT", reason };
         return false;
     }
 
     mBound2DTextures.SetLength(mGLMaxTextureUnits);
     mBoundCubeMapTextures.SetLength(mGLMaxTextureUnits);
     mBound3DTextures.SetLength(mGLMaxTextureUnits);
     mBound2DArrayTextures.SetLength(mGLMaxTextureUnits);
     mBoundSamplers.SetLength(mGLMaxTextureUnits);
 
-    gl->fGetIntegerv(LOCAL_GL_MAX_VIEWPORT_DIMS, (GLint*)mImplMaxViewportDims);
+    gl->fGetIntegerv(LOCAL_GL_MAX_VIEWPORT_DIMS, (GLint*)mGLMaxViewportDims);
 
     ////////////////
 
-    if (MinCapabilityMode()) {
-        mImplMaxTextureSize = MINVALUE_GL_MAX_TEXTURE_SIZE;
-        mImplMaxCubeMapTextureSize = MINVALUE_GL_MAX_CUBE_MAP_TEXTURE_SIZE;
-        mImplMaxRenderbufferSize = MINVALUE_GL_MAX_RENDERBUFFER_SIZE;
-
-        mImplMax3DTextureSize = MINVALUE_GL_MAX_3D_TEXTURE_SIZE;
-        mImplMaxArrayTextureLayers = MINVALUE_GL_MAX_ARRAY_TEXTURE_LAYERS;
+    gl->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_SIZE, (GLint*)&mGLMaxTextureSize);
+    gl->fGetIntegerv(LOCAL_GL_MAX_CUBE_MAP_TEXTURE_SIZE, (GLint*)&mGLMaxCubeMapTextureSize);
+    gl->fGetIntegerv(LOCAL_GL_MAX_RENDERBUFFER_SIZE, (GLint*)&mGLMaxRenderbufferSize);
 
-        mGLMaxTextureImageUnits = MINVALUE_GL_MAX_TEXTURE_IMAGE_UNITS;
-        mGLMaxVertexTextureImageUnits = MINVALUE_GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS;
-    } else {
-        gl->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_SIZE, (GLint*)&mImplMaxTextureSize);
-        gl->fGetIntegerv(LOCAL_GL_MAX_CUBE_MAP_TEXTURE_SIZE, (GLint*)&mImplMaxCubeMapTextureSize);
-        gl->fGetIntegerv(LOCAL_GL_MAX_RENDERBUFFER_SIZE, (GLint*)&mImplMaxRenderbufferSize);
+    if (!gl->GetPotentialInteger(LOCAL_GL_MAX_3D_TEXTURE_SIZE, (GLint*)&mGLMax3DTextureSize))
+        mGLMax3DTextureSize = 0;
+    if (!gl->GetPotentialInteger(LOCAL_GL_MAX_ARRAY_TEXTURE_LAYERS, (GLint*)&mGLMaxArrayTextureLayers))
+        mGLMaxArrayTextureLayers = 0;
 
-        if (!gl->GetPotentialInteger(LOCAL_GL_MAX_3D_TEXTURE_SIZE, (GLint*)&mImplMax3DTextureSize))
-            mImplMax3DTextureSize = 0;
-        if (!gl->GetPotentialInteger(LOCAL_GL_MAX_ARRAY_TEXTURE_LAYERS, (GLint*)&mImplMaxArrayTextureLayers))
-            mImplMaxArrayTextureLayers = 0;
-
-        gl->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_IMAGE_UNITS, &mGLMaxTextureImageUnits);
-        gl->fGetIntegerv(LOCAL_GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &mGLMaxVertexTextureImageUnits);
-    }
+    gl->GetUIntegerv(LOCAL_GL_MAX_TEXTURE_IMAGE_UNITS, &mGLMaxFragmentTextureImageUnits);
+    gl->GetUIntegerv(LOCAL_GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &mGLMaxVertexTextureImageUnits);
 
     ////////////////
 
     mGLMaxColorAttachments = 1;
     mGLMaxDrawBuffers = 1;
-    gl->GetPotentialInteger(LOCAL_GL_MAX_COLOR_ATTACHMENTS,
-                            (GLint*)&mGLMaxColorAttachments);
-    gl->GetPotentialInteger(LOCAL_GL_MAX_DRAW_BUFFERS, (GLint*)&mGLMaxDrawBuffers);
 
-    if (MinCapabilityMode()) {
-        mGLMaxColorAttachments = std::min(mGLMaxColorAttachments,
-                                          kMinMaxColorAttachments);
-        mGLMaxDrawBuffers = std::min(mGLMaxDrawBuffers, kMinMaxDrawBuffers);
+    if (IsWebGL2()) {
+        UpdateMaxDrawBuffers();
     }
 
-    if (IsWebGL2()) {
-        mImplMaxColorAttachments = mGLMaxColorAttachments;
-        mImplMaxDrawBuffers = std::min(mGLMaxDrawBuffers, mImplMaxColorAttachments);
+    ////////////////
+
+    if (gl->IsSupported(gl::GLFeature::ES2_compatibility)) {
+        gl->GetUIntegerv(LOCAL_GL_MAX_FRAGMENT_UNIFORM_VECTORS, &mGLMaxFragmentUniformVectors);
+        gl->GetUIntegerv(LOCAL_GL_MAX_VERTEX_UNIFORM_VECTORS, &mGLMaxVertexUniformVectors);
+        gl->GetUIntegerv(LOCAL_GL_MAX_VARYING_VECTORS, &mGLMaxVaryingVectors);
     } else {
-        mImplMaxColorAttachments = 1;
-        mImplMaxDrawBuffers = 1;
+        gl->GetUIntegerv(LOCAL_GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, &mGLMaxFragmentUniformVectors);
+        mGLMaxFragmentUniformVectors /= 4;
+        gl->GetUIntegerv(LOCAL_GL_MAX_VERTEX_UNIFORM_COMPONENTS, &mGLMaxVertexUniformVectors);
+        mGLMaxVertexUniformVectors /= 4;
+
+        /* We are now going to try to read GL_MAX_VERTEX_OUTPUT_COMPONENTS
+         * and GL_MAX_FRAGMENT_INPUT_COMPONENTS, however these constants
+         * only entered the OpenGL standard at OpenGL 3.2. So we will try
+         * reading, and check OpenGL error for INVALID_ENUM.
+         *
+         * On the public_webgl list, "problematic GetParameter pnames"
+         * thread, the following formula was given:
+         *   maxVaryingVectors = min(GL_MAX_VERTEX_OUTPUT_COMPONENTS,
+         *                           GL_MAX_FRAGMENT_INPUT_COMPONENTS) / 4
+         */
+        uint32_t maxVertexOutputComponents = 0;
+        uint32_t maxFragmentInputComponents = 0;
+        bool ok = true;
+        ok &= gl->GetPotentialInteger(LOCAL_GL_MAX_VERTEX_OUTPUT_COMPONENTS,
+                                      (GLint*)&maxVertexOutputComponents);
+        ok &= gl->GetPotentialInteger(LOCAL_GL_MAX_FRAGMENT_INPUT_COMPONENTS,
+                                      (GLint*)&maxFragmentInputComponents);
+        if (ok) {
+            mGLMaxVaryingVectors = std::min(maxVertexOutputComponents,
+                                            maxFragmentInputComponents) / 4;
+        } else {
+            mGLMaxVaryingVectors = 16;
+            // 16 = 64/4, and 64 is the min value for
+            // maxVertexOutputComponents in the OpenGL 3.2 spec.
+        }
     }
 
     ////////////////
 
-    if (MinCapabilityMode()) {
-        mGLMaxFragmentUniformVectors = MINVALUE_GL_MAX_FRAGMENT_UNIFORM_VECTORS;
-        mGLMaxVertexUniformVectors = MINVALUE_GL_MAX_VERTEX_UNIFORM_VECTORS;
-        mGLMaxVaryingVectors = MINVALUE_GL_MAX_VARYING_VECTORS;
-    } else {
-        if (gl->IsSupported(gl::GLFeature::ES2_compatibility)) {
-            gl->fGetIntegerv(LOCAL_GL_MAX_FRAGMENT_UNIFORM_VECTORS, &mGLMaxFragmentUniformVectors);
-            gl->fGetIntegerv(LOCAL_GL_MAX_VERTEX_UNIFORM_VECTORS, &mGLMaxVertexUniformVectors);
-            gl->fGetIntegerv(LOCAL_GL_MAX_VARYING_VECTORS, &mGLMaxVaryingVectors);
-        } else {
-            gl->fGetIntegerv(LOCAL_GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, &mGLMaxFragmentUniformVectors);
-            mGLMaxFragmentUniformVectors /= 4;
-            gl->fGetIntegerv(LOCAL_GL_MAX_VERTEX_UNIFORM_COMPONENTS, &mGLMaxVertexUniformVectors);
-            mGLMaxVertexUniformVectors /= 4;
+    gl->fGetFloatv(LOCAL_GL_ALIASED_LINE_WIDTH_RANGE, mGLAliasedLineWidthRange);
+
+    const GLenum driverPName = gl->IsCoreProfile() ? LOCAL_GL_POINT_SIZE_RANGE
+                                                   : LOCAL_GL_ALIASED_POINT_SIZE_RANGE;
+    gl->fGetFloatv(driverPName, mGLAliasedPointSizeRange);
+
+    ////////////////
+
+    if (gfxPrefs::WebGLMinCapabilityMode()) {
+        bool ok = true;
+
+        ok &= RestrictCap(&mGLMaxVertexTextureImageUnits  , kMinMaxVertexTextureImageUnits);
+        ok &= RestrictCap(&mGLMaxFragmentTextureImageUnits, kMinMaxFragmentTextureImageUnits);
+        ok &= RestrictCap(&mGLMaxCombinedTextureImageUnits, kMinMaxCombinedTextureImageUnits);
+
+        ok &= RestrictCap(&mGLMaxVertexAttribs         , kMinMaxVertexAttribs);
+        ok &= RestrictCap(&mGLMaxVertexUniformVectors  , kMinMaxVertexUniformVectors);
+        ok &= RestrictCap(&mGLMaxFragmentUniformVectors, kMinMaxFragmentUniformVectors);
+        ok &= RestrictCap(&mGLMaxVaryingVectors        , kMinMaxVaryingVectors);
+
+        ok &= RestrictCap(&mGLMaxColorAttachments, kMinMaxColorAttachments);
+        ok &= RestrictCap(&mGLMaxDrawBuffers     , kMinMaxDrawBuffers);
+
+        ok &= RestrictCap(&mGLMaxTextureSize       , kMinMaxTextureSize);
+        ok &= RestrictCap(&mGLMaxCubeMapTextureSize, kMinMaxCubeMapTextureSize);
+        ok &= RestrictCap(&mGLMax3DTextureSize     , kMinMax3DTextureSize);
+
+        ok &= RestrictCap(&mGLMaxArrayTextureLayers, kMinMaxArrayTextureLayers);
+        ok &= RestrictCap(&mGLMaxRenderbufferSize  , kMinMaxRenderbufferSize);
 
-            /* We are now going to try to read GL_MAX_VERTEX_OUTPUT_COMPONENTS
-             * and GL_MAX_FRAGMENT_INPUT_COMPONENTS, however these constants
-             * only entered the OpenGL standard at OpenGL 3.2. So we will try
-             * reading, and check OpenGL error for INVALID_ENUM.
-             *
-             * On the public_webgl list, "problematic GetParameter pnames"
-             * thread, the following formula was given:
-             *   maxVaryingVectors = min(GL_MAX_VERTEX_OUTPUT_COMPONENTS,
-             *                           GL_MAX_FRAGMENT_INPUT_COMPONENTS) / 4
-             */
-            GLint maxVertexOutputComponents = 0;
-            GLint maxFragmentInputComponents = 0;
+        if (!ok) {
+            GenerateWarning("Unable to restrict WebGL limits to minimums.");
+            return false;
+        }
+
+        mDisableFragHighP = true;
+    } else if (nsContentUtils::ShouldResistFingerprinting()) {
+        bool ok = true;
+
+        ok &= RestrictCap(&mGLMaxTextureSize       , kCommonMaxTextureSize);
+        ok &= RestrictCap(&mGLMaxCubeMapTextureSize, kCommonMaxCubeMapTextureSize);
+        ok &= RestrictCap(&mGLMaxRenderbufferSize  , kCommonMaxRenderbufferSize);
+
+        ok &= RestrictCap(&mGLMaxVertexTextureImageUnits  , kCommonMaxVertexTextureImageUnits);
+        ok &= RestrictCap(&mGLMaxFragmentTextureImageUnits, kCommonMaxFragmentTextureImageUnits);
+        ok &= RestrictCap(&mGLMaxCombinedTextureImageUnits, kCommonMaxCombinedTextureImageUnits);
 
-            const bool ok = (gl->GetPotentialInteger(LOCAL_GL_MAX_VERTEX_OUTPUT_COMPONENTS,
-                                                     &maxVertexOutputComponents) &&
-                             gl->GetPotentialInteger(LOCAL_GL_MAX_FRAGMENT_INPUT_COMPONENTS,
-                                                     &maxFragmentInputComponents));
+        ok &= RestrictCap(&mGLMaxVertexAttribs         , kCommonMaxVertexAttribs);
+        ok &= RestrictCap(&mGLMaxVertexUniformVectors  , kCommonMaxVertexUniformVectors);
+        ok &= RestrictCap(&mGLMaxFragmentUniformVectors, kCommonMaxFragmentUniformVectors);
+        ok &= RestrictCap(&mGLMaxVaryingVectors        , kCommonMaxVaryingVectors);
 
-            if (ok) {
-                mGLMaxVaryingVectors = std::min(maxVertexOutputComponents,
-                                                maxFragmentInputComponents) / 4;
-            } else {
-                mGLMaxVaryingVectors = 16;
-                // 16 = 64/4, and 64 is the min value for
-                // maxVertexOutputComponents in the OpenGL 3.2 spec.
-            }
+        ok &= RestrictCap(&mGLAliasedLineWidthRange[0], kCommonAliasedLineWidthRangeMin);
+        ok &= RestrictCap(&mGLAliasedLineWidthRange[1], kCommonAliasedLineWidthRangeMax);
+        ok &= RestrictCap(&mGLAliasedPointSizeRange[0], kCommonAliasedPointSizeRangeMin);
+        ok &= RestrictCap(&mGLAliasedPointSizeRange[1], kCommonAliasedPointSizeRangeMax);
+
+        ok &= RestrictCap(&mGLMaxViewportDims[0], kCommonMaxViewportDims);
+        ok &= RestrictCap(&mGLMaxViewportDims[1], kCommonMaxViewportDims);
+
+        if (!ok) {
+            GenerateWarning("Unable to restrict WebGL limits in order to resist fingerprinting");
+            return false;
         }
     }
 
+    ////////////////
+
     if (gl->IsCompatibilityProfile()) {
         gl->fEnable(LOCAL_GL_POINT_SPRITE);
     }
 
     if (!gl->IsGLES()) {
         gl->fEnable(LOCAL_GL_PROGRAM_POINT_SIZE);
     }
 
--- a/dom/canvas/WebGLExtensionDrawBuffers.cpp
+++ b/dom/canvas/WebGLExtensionDrawBuffers.cpp
@@ -15,22 +15,17 @@
 
 namespace mozilla {
 
 WebGLExtensionDrawBuffers::WebGLExtensionDrawBuffers(WebGLContext* webgl)
     : WebGLExtensionBase(webgl)
 {
     MOZ_ASSERT(IsSupported(webgl), "Don't construct extension if unsupported.");
 
-    // WEBGL_draw_buffers:
-    // "The value of the MAX_COLOR_ATTACHMENTS_WEBGL parameter must be greater than or
-    //  equal to that of the MAX_DRAW_BUFFERS_WEBGL parameter."
-    webgl->mImplMaxColorAttachments = webgl->mGLMaxColorAttachments;
-    webgl->mImplMaxDrawBuffers = std::min(webgl->mGLMaxDrawBuffers,
-                                          webgl->mImplMaxColorAttachments);
+    webgl->UpdateMaxDrawBuffers();
 }
 
 WebGLExtensionDrawBuffers::~WebGLExtensionDrawBuffers()
 {
 }
 
 void
 WebGLExtensionDrawBuffers::DrawBuffersWEBGL(const dom::Sequence<GLenum>& buffers)
@@ -43,24 +38,14 @@ WebGLExtensionDrawBuffers::DrawBuffersWE
     mContext->DrawBuffers(buffers);
 }
 
 bool
 WebGLExtensionDrawBuffers::IsSupported(const WebGLContext* webgl)
 {
     gl::GLContext* gl = webgl->GL();
 
-    if (!gl->IsSupported(gl::GLFeature::draw_buffers))
-        return false;
-
-    // WEBGL_draw_buffers requires at least 4 color attachments.
-    if (webgl->mGLMaxDrawBuffers < webgl->kMinMaxDrawBuffers ||
-        webgl->mGLMaxColorAttachments < webgl->kMinMaxColorAttachments)
-    {
-        return false;
-    }
-
-    return true;
+    return gl->IsSupported(gl::GLFeature::draw_buffers);
 }
 
 IMPL_WEBGL_EXTENSION_GOOP(WebGLExtensionDrawBuffers, WEBGL_draw_buffers)
 
 } // namespace mozilla
--- a/dom/canvas/WebGLFramebuffer.cpp
+++ b/dom/canvas/WebGLFramebuffer.cpp
@@ -673,18 +673,18 @@ WebGLFramebuffer::GetColorAttachPoint(GL
     if (attachPoint == LOCAL_GL_NONE)
         return Some<WebGLFBAttachPoint*>(nullptr);
 
     if (attachPoint < LOCAL_GL_COLOR_ATTACHMENT0)
         return Nothing();
 
     const size_t colorId = attachPoint - LOCAL_GL_COLOR_ATTACHMENT0;
 
-    MOZ_ASSERT(mContext->mImplMaxColorAttachments <= kMaxColorAttachments);
-    if (colorId >= mContext->mImplMaxColorAttachments)
+    MOZ_ASSERT(mContext->mGLMaxColorAttachments <= kMaxColorAttachments);
+    if (colorId >= mContext->mGLMaxColorAttachments)
         return Nothing();
 
     return Some(&mColorAttachments[colorId]);
 }
 
 Maybe<WebGLFBAttachPoint*>
 WebGLFramebuffer::GetAttachPoint(GLenum attachPoint)
 {
@@ -978,17 +978,17 @@ WebGLFramebuffer::ValidateForRead(const 
 void
 WebGLFramebuffer::ResolveAttachments() const
 {
     const auto& gl = mContext->gl;
 
     ////
     // Nuke attachment points.
 
-    for (uint32_t i = 0; i < mContext->mImplMaxColorAttachments; i++) {
+    for (uint32_t i = 0; i < mContext->mGLMaxColorAttachments; i++) {
         const GLenum attachEnum = LOCAL_GL_COLOR_ATTACHMENT0 + i;
         gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, attachEnum,
                                      LOCAL_GL_RENDERBUFFER, 0);
     }
 
     gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_ATTACHMENT,
                                  LOCAL_GL_RENDERBUFFER, 0);
     gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_STENCIL_ATTACHMENT,
@@ -1249,17 +1249,17 @@ WebGLFramebuffer::RefreshDrawBuffers() c
     const auto& gl = mContext->gl;
     if (!gl->IsSupported(gl::GLFeature::draw_buffers))
         return;
 
     // Prior to GL4.1, having a no-image FB attachment that's selected by DrawBuffers
     // yields a framebuffer status of FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER.
     // We could workaround this only on affected versions, but it's easier be
     // unconditional.
-    std::vector<GLenum> driverBuffers(mContext->mImplMaxDrawBuffers, LOCAL_GL_NONE);
+    std::vector<GLenum> driverBuffers(mContext->mGLMaxDrawBuffers, LOCAL_GL_NONE);
     for (const auto& attach : mColorDrawBuffers) {
         if (attach->HasImage()) {
             const uint32_t index = attach->mAttachmentPoint - LOCAL_GL_COLOR_ATTACHMENT0;
             driverBuffers[index] = attach->mAttachmentPoint;
         }
     }
 
     gl->fDrawBuffers(driverBuffers.size(), driverBuffers.data());
@@ -1284,17 +1284,17 @@ WebGLFramebuffer::RefreshReadBuffer() co
     gl->fReadBuffer(driverBuffer);
 }
 
 ////
 
 void
 WebGLFramebuffer::DrawBuffers(const char* funcName, const dom::Sequence<GLenum>& buffers)
 {
-    if (buffers.Length() > mContext->mImplMaxDrawBuffers) {
+    if (buffers.Length() > mContext->mGLMaxDrawBuffers) {
         // "An INVALID_VALUE error is generated if `n` is greater than MAX_DRAW_BUFFERS."
         mContext->ErrorInvalidValue("%s: `buffers` must have a length <="
                                     " MAX_DRAW_BUFFERS.", funcName);
         return;
     }
 
     std::vector<const WebGLFBAttachPoint*> newColorDrawBuffers;
     newColorDrawBuffers.reserve(buffers.Length());
@@ -1471,23 +1471,23 @@ WebGLFramebuffer::FramebufferTexture2D(c
          *   than or equal to zero and less than or equal to log2 of the
          *   value of MAX_CUBE_MAP_TEXTURE_SIZE. If textarget is TEXTURE_2D,
          *   level must be greater than or equal to zero and no larger than
          *   log2 of the value of MAX_TEXTURE_SIZE. Otherwise, an
          *   INVALID_VALUE error is generated.
          */
 
         if (texImageTarget == LOCAL_GL_TEXTURE_2D) {
-            if (uint32_t(level) > FloorLog2(mContext->mImplMaxTextureSize))
+            if (uint32_t(level) > FloorLog2(mContext->mGLMaxTextureSize))
                 return mContext->ErrorInvalidValue("%s: `level` is too large.", funcName);
         } else {
             MOZ_ASSERT(texImageTarget >= LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X &&
                        texImageTarget <= LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z);
 
-            if (uint32_t(level) > FloorLog2(mContext->mImplMaxCubeMapTextureSize))
+            if (uint32_t(level) > FloorLog2(mContext->mGLMaxCubeMapTextureSize))
                 return mContext->ErrorInvalidValue("%s: `level` is too large.", funcName);
         }
     } else if (level != 0) {
         return mContext->ErrorInvalidValue("%s: `level` must be 0.", funcName);
     }
 
     // End of validation.
 
@@ -1533,37 +1533,37 @@ WebGLFramebuffer::FramebufferTextureLaye
             mContext->ErrorInvalidOperation("%s: `texture` has never been bound.",
                                             funcName);
             return;
         }
 
         texImageTarget = tex->Target().get();
         switch (texImageTarget) {
         case LOCAL_GL_TEXTURE_3D:
-            if (uint32_t(layer) >= mContext->mImplMax3DTextureSize) {
+            if (uint32_t(layer) >= mContext->mGLMax3DTextureSize) {
                 mContext->ErrorInvalidValue("%s: `layer` must be < %s.", funcName,
                                             "MAX_3D_TEXTURE_SIZE");
                 return;
             }
 
-            if (uint32_t(level) > FloorLog2(mContext->mImplMax3DTextureSize)) {
+            if (uint32_t(level) > FloorLog2(mContext->mGLMax3DTextureSize)) {
                 mContext->ErrorInvalidValue("%s: `level` must be <= log2(%s).", funcName,
                                             "MAX_3D_TEXTURE_SIZE");
                 return;
             }
             break;
 
         case LOCAL_GL_TEXTURE_2D_ARRAY:
-            if (uint32_t(layer) >= mContext->mImplMaxArrayTextureLayers) {
+            if (uint32_t(layer) >= mContext->mGLMaxArrayTextureLayers) {
                 mContext->ErrorInvalidValue("%s: `layer` must be < %s.", funcName,
                                             "MAX_ARRAY_TEXTURE_LAYERS");
                 return;
             }
 
-            if (uint32_t(level) > FloorLog2(mContext->mImplMaxTextureSize)) {
+            if (uint32_t(level) > FloorLog2(mContext->mGLMaxTextureSize)) {
                 mContext->ErrorInvalidValue("%s: `level` must be <= log2(%s).", funcName,
                                             "MAX_TEXTURE_SIZE");
                 return;
             }
             break;
 
         default:
             mContext->ErrorInvalidOperation("%s: `texture` must be a TEXTURE_3D or"
--- a/dom/canvas/WebGLProgram.cpp
+++ b/dom/canvas/WebGLProgram.cpp
@@ -693,17 +693,17 @@ WebGLProgram::GetFragDataLocation(const 
         // OSX doesn't return locs for indexed names, just the base names.
         // Indicated by failure in: conformance2/programs/gl-get-frag-data-location.html
         bool isArray;
         size_t arrayIndex;
         nsCString baseUserName;
         if (!ParseName(userName, &baseUserName, &isArray, &arrayIndex))
             return -1;
 
-        if (arrayIndex >= mContext->mImplMaxDrawBuffers)
+        if (arrayIndex >= mContext->mGLMaxDrawBuffers)
             return -1;
 
         const auto baseLoc = GetFragDataByUserName(this, baseUserName);
         const auto loc = baseLoc + GLint(arrayIndex);
         return loc;
     }
 #endif
     return GetFragDataByUserName(this, userName);
--- a/dom/canvas/WebGLRenderbuffer.cpp
+++ b/dom/canvas/WebGLRenderbuffer.cpp
@@ -183,18 +183,18 @@ WebGLRenderbuffer::RenderbufferStorage(c
 {
     const auto usage = mContext->mFormatUsage->GetRBUsage(internalFormat);
     if (!usage) {
         mContext->ErrorInvalidEnum("%s: Invalid `internalFormat`: 0x%04x.", funcName,
                                    internalFormat);
         return;
     }
 
-    if (width > mContext->mImplMaxRenderbufferSize ||
-        height > mContext->mImplMaxRenderbufferSize)
+    if (width > mContext->mGLMaxRenderbufferSize ||
+        height > mContext->mGLMaxRenderbufferSize)
     {
         mContext->ErrorInvalidValue("%s: Width or height exceeds maximum renderbuffer"
                                     " size.",
                                     funcName);
         return;
     }
 
     mContext->MakeContextCurrent();
--- a/dom/canvas/WebGLShaderValidator.cpp
+++ b/dom/canvas/WebGLShaderValidator.cpp
@@ -140,18 +140,18 @@ WebGLContext::CreateShaderValidator(GLen
     ShInitBuiltInResources(&resources);
 
     resources.HashFunction = webgl::IdentifierHashFunc;
 
     resources.MaxVertexAttribs = mGLMaxVertexAttribs;
     resources.MaxVertexUniformVectors = mGLMaxVertexUniformVectors;
     resources.MaxVaryingVectors = mGLMaxVaryingVectors;
     resources.MaxVertexTextureImageUnits = mGLMaxVertexTextureImageUnits;
-    resources.MaxCombinedTextureImageUnits = mGLMaxTextureUnits;
-    resources.MaxTextureImageUnits = mGLMaxTextureImageUnits;
+    resources.MaxCombinedTextureImageUnits = mGLMaxCombinedTextureImageUnits;
+    resources.MaxTextureImageUnits = mGLMaxFragmentTextureImageUnits;
     resources.MaxFragmentUniformVectors = mGLMaxFragmentUniformVectors;
 
     const bool hasMRTs = (IsWebGL2() ||
                           IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers));
     resources.MaxDrawBuffers = (hasMRTs ? mGLMaxDrawBuffers : 1);
 
     if (IsExtensionEnabled(WebGLExtensionID::EXT_frag_depth))
         resources.EXT_frag_depth = 1;
--- a/dom/canvas/WebGLTextureUpload.cpp
+++ b/dom/canvas/WebGLTextureUpload.cpp
@@ -536,54 +536,54 @@ WebGLTexture::ValidateTexImageSpecificat
 
     /* GLES 3.0.4, p133-134:
      * GL_MAX_TEXTURE_SIZE is *not* the max allowed texture size. Rather, it is the
      * max (width/height) size guaranteed not to generate an INVALID_VALUE for too-large
      * dimensions. Sizes larger than GL_MAX_TEXTURE_SIZE *may or may not* result in an
      * INVALID_VALUE, or possibly GL_OOM.
      *
      * However, we have needed to set our maximums lower in the past to prevent resource
-     * corruption. Therefore we have mImplMaxTextureSize, which is neither necessarily
+     * corruption. Therefore we have mGLMaxTextureSize, which is neither necessarily
      * lower nor higher than MAX_TEXTURE_SIZE.
      *
-     * Note that mImplMaxTextureSize must be >= than the advertized MAX_TEXTURE_SIZE.
-     * For simplicity, we advertize MAX_TEXTURE_SIZE as mImplMaxTextureSize.
+     * Note that mGLMaxTextureSize must be >= than the advertized MAX_TEXTURE_SIZE.
+     * For simplicity, we advertize MAX_TEXTURE_SIZE as mGLMaxTextureSize.
      */
 
     uint32_t maxWidthHeight = 0;
     uint32_t maxDepth = 0;
     uint32_t maxLevel = 0;
 
     MOZ_ASSERT(level <= 31);
     switch (target.get()) {
     case LOCAL_GL_TEXTURE_2D:
-        maxWidthHeight = mContext->mImplMaxTextureSize >> level;
+        maxWidthHeight = mContext->mGLMaxTextureSize >> level;
         maxDepth = 1;
-        maxLevel = CeilingLog2(mContext->mImplMaxTextureSize);
+        maxLevel = CeilingLog2(mContext->mGLMaxTextureSize);
         break;
 
     case LOCAL_GL_TEXTURE_3D:
-        maxWidthHeight = mContext->mImplMax3DTextureSize >> level;
+        maxWidthHeight = mContext->mGLMax3DTextureSize >> level;
         maxDepth = maxWidthHeight;
-        maxLevel = CeilingLog2(mContext->mImplMax3DTextureSize);
+        maxLevel = CeilingLog2(mContext->mGLMax3DTextureSize);
         break;
 
     case LOCAL_GL_TEXTURE_2D_ARRAY:
-        maxWidthHeight = mContext->mImplMaxTextureSize >> level;
+        maxWidthHeight = mContext->mGLMaxTextureSize >> level;
         // "The maximum number of layers for two-dimensional array textures (depth)
         //  must be at least MAX_ARRAY_TEXTURE_LAYERS for all levels."
-        maxDepth = mContext->mImplMaxArrayTextureLayers;
-        maxLevel = CeilingLog2(mContext->mImplMaxTextureSize);
+        maxDepth = mContext->mGLMaxArrayTextureLayers;
+        maxLevel = CeilingLog2(mContext->mGLMaxTextureSize);
         break;
 
     default: // cube maps
         MOZ_ASSERT(IsCubeMap());
-        maxWidthHeight = mContext->mImplMaxCubeMapTextureSize >> level;
+        maxWidthHeight = mContext->mGLMaxCubeMapTextureSize >> level;
         maxDepth = 1;
-        maxLevel = CeilingLog2(mContext->mImplMaxCubeMapTextureSize);
+        maxLevel = CeilingLog2(mContext->mGLMaxCubeMapTextureSize);
         break;
     }
 
     if (level > maxLevel) {
         mContext->ErrorInvalidValue("%s: Requested level is not supported for target.",
                                     funcName);
         return false;
     }
--- a/dom/canvas/test/webgl-mochitest/mochitest.ini
+++ b/dom/canvas/test/webgl-mochitest/mochitest.ini
@@ -103,8 +103,10 @@ skip-if = toolkit == 'android' #bug 8654
 fail-if = (os == 'mac') || (os == 'win' && !(e10s && os_version == '6.1')) # no fast path on windows yet (bug 1373165 or 1373770), and mac (bug 1373669)
 [test_video_fastpath_theora.html]
 fail-if = (os == 'win' && os_version != '6.1') # no fast path on windows yet (bug 1373192), and mac (bug 1373702)
 [test_video_fastpath_vp8.html]
 fail-if = (os == 'win' && os_version != '6.1') # no fast path on windows yet (bug 1373192), and mac (bug 1373702)
 [test_video_fastpath_vp9.html]
 fail-if = (os == 'win' && os_version != '6.1') # no fast path on windows yet (bug 1373192), and mac (bug 1373702)
 [test_webglcontextcreationerror.html]
+[test_webgl_fingerprinting_resistance.html]
+fail-if = (os == 'mac') || (os == 'win') # on try server, LOCAL_GL_MAX_CUBE_MAP_TEXTURE_SIZE = 512 on mac and LOCAL_GL_ALIASED_LINE_WIDTH_RANGE = [1, 1] on win
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/test_webgl_fingerprinting_resistance.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+/* global SimpleTest SpecialPowers */
+SimpleTest.waitForExplicitFinish();
+document.addEventListener("DOMContentLoaded", async function() {
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      ["privacy.resistFingerprinting", true]
+    ]
+  });
+
+  let canvas = document.body.appendChild(document.createElement("canvas"));
+  if (!canvas) {
+    SimpleTest.ok(false, "Cannot create canvas");
+    SimpleTest.finish();
+  }
+
+  let gl = canvas.getContext("webgl");
+  if (!gl) {
+    SimpleTest.ok(false, "Cannot get WebGL context");
+    SimpleTest.finish();
+  }
+
+  SimpleTest.is(gl.getParameter(gl.MAX_TEXTURE_SIZE), 2048, "MAX_TEXTURE_SIZE");
+  SimpleTest.is(gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE),  2048, "MAX_CUBE_MAP_TEXTURE_SIZE");
+  SimpleTest.is(gl.getParameter(gl.MAX_RENDERBUFFER_SIZE),  2048, "MAX_RENDERBUFFER_SIZE");
+  SimpleTest.is(gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS),  8, "MAX_VERTEX_TEXTURE_IMAGE_UNITS");
+  SimpleTest.is(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS),  8, "MAX_TEXTURE_IMAGE_UNITS");
+  SimpleTest.is(gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS),  16, "MAX_COMBINED_TEXTURE_IMAGE_UNITS");
+  SimpleTest.is(gl.getParameter(gl.MAX_VERTEX_ATTRIBS),  16, "MAX_VERTEX_ATTRIBS");
+  SimpleTest.is(gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS),  256, "MAX_VERTEX_UNIFORM_VECTORS");
+  SimpleTest.is(gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS),  224, "MAX_FRAGMENT_UNIFORM_VECTORS");
+  SimpleTest.is(gl.getParameter(gl.MAX_VARYING_VECTORS),  8, "MAX_VARYING_VECTORS");
+  let viewportDims = gl.getParameter(gl.MAX_VIEWPORT_DIMS);
+  SimpleTest.is(viewportDims[0],  4096, "MAX_VIEWPORT_DIMS[0]");
+  SimpleTest.is(viewportDims[1],  4096, "MAX_VIEWPORT_DIMS[1]");
+  let aliasedPointSizeRange = gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE);
+  SimpleTest.is(aliasedPointSizeRange[0],  1, "ALIASED_POINT_SIZE_RANGE[0]");
+  SimpleTest.is(aliasedPointSizeRange[1],  63, "ALIASED_POINT_SIZE_RANGE[1]");
+  let aliasedLineWIdthRange = gl.getParameter(gl.ALIASED_LINE_WIDTH_RANGE);
+  SimpleTest.is(aliasedLineWIdthRange[0],  1, "ALIASED_LINE_WIDTH_RANGE[0]");
+  SimpleTest.is(aliasedLineWIdthRange[1],  5, "ALIASED_LINE_WIDTH_RANGE[1]");
+  SimpleTest.finish();
+});
+</script>
--- a/dom/console/Console.cpp
+++ b/dom/console/Console.cpp
@@ -1502,23 +1502,16 @@ Console::PopulateConsoleNotificationInTh
   ConsoleStackEntry frame;
   if (aData->mTopStackFrame) {
     frame = *aData->mTopStackFrame;
   }
 
   ClearException ce(aCx);
   RootedDictionary<ConsoleEvent> event(aCx);
 
-  // Save the principal's OriginAttributes in the console event data
-  // so that we will be able to filter messages by origin attributes.
-  JS::Rooted<JS::Value> originAttributesValue(aCx);
-  if (ToJSValue(aCx, aData->mOriginAttributes, &originAttributesValue)) {
-    event.mOriginAttributes = originAttributesValue;
-  }
-
   event.mAddonId = aData->mAddonId;
 
   event.mID.Construct();
   event.mInnerID.Construct();
 
   if (aData->mIDType == ConsoleCallData::eString) {
     event.mID.Value().SetAsString() = aData->mOuterIDString;
     event.mInnerID.Value().SetAsString() = aData->mInnerIDString;
--- a/dom/console/ConsoleAPIStorage.js
+++ b/dom/console/ConsoleAPIStorage.js
@@ -124,21 +124,16 @@ ConsoleAPIStorageService.prototype = {
   recordEvent: function CS_recordEvent(aId, aOuterId, aEvent)
   {
     if (!_consoleStorage.has(aId)) {
       _consoleStorage.set(aId, []);
     }
 
     let storage = _consoleStorage.get(aId);
 
-    // Clone originAttributes to prevent "TypeError: can't access dead object"
-    // exceptions when cached console messages are retrieved/filtered
-    // by the devtools webconsole actor.
-    aEvent.originAttributes = Cu.cloneInto(aEvent.originAttributes, {});
-
     storage.push(aEvent);
 
     // truncate
     if (storage.length > STORAGE_MAX_EVENTS) {
       storage.shift();
     }
 
     Services.obs.notifyObservers(aEvent, "console-api-log-event", aOuterId);
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -6663,24 +6663,26 @@ HTMLMediaElement::MozFragmentEnd()
 NS_IMETHODIMP HTMLMediaElement::GetMozFragmentEnd(double* aTime)
 {
   *aTime = MozFragmentEnd();
   return NS_OK;
 }
 
 static double ClampPlaybackRate(double aPlaybackRate)
 {
+  MOZ_ASSERT(aPlaybackRate >= 0.0);
+
   if (aPlaybackRate == 0.0) {
     return aPlaybackRate;
   }
-  if (Abs(aPlaybackRate) < MIN_PLAYBACKRATE) {
-    return aPlaybackRate < 0 ? -MIN_PLAYBACKRATE : MIN_PLAYBACKRATE;
-  }
-  if (Abs(aPlaybackRate) > MAX_PLAYBACKRATE) {
-    return aPlaybackRate < 0 ? -MAX_PLAYBACKRATE : MAX_PLAYBACKRATE;
+  if (aPlaybackRate < MIN_PLAYBACKRATE) {
+    return MIN_PLAYBACKRATE;
+  }
+  if (aPlaybackRate > MAX_PLAYBACKRATE) {
+    return MAX_PLAYBACKRATE;
   }
   return aPlaybackRate;
 }
 
 NS_IMETHODIMP HTMLMediaElement::GetDefaultPlaybackRate(double* aDefaultPlaybackRate)
 {
   *aDefaultPlaybackRate = DefaultPlaybackRate();
   return NS_OK;
@@ -6712,32 +6714,36 @@ NS_IMETHODIMP HTMLMediaElement::GetPlayb
 }
 
 void
 HTMLMediaElement::SetPlaybackRate(double aPlaybackRate, ErrorResult& aRv)
 {
   // Changing the playback rate of a media that has more than two channels is
   // not supported.
   if (aPlaybackRate < 0) {
-    aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     return;
   }
 
-  mPlaybackRate = ClampPlaybackRate(aPlaybackRate);
+  if (mPlaybackRate == aPlaybackRate) {
+    return;
+  }
+
+  mPlaybackRate = aPlaybackRate;
 
   if (mPlaybackRate != 0.0 &&
-      (mPlaybackRate < 0 || mPlaybackRate > THRESHOLD_HIGH_PLAYBACKRATE_AUDIO ||
+      (mPlaybackRate > THRESHOLD_HIGH_PLAYBACKRATE_AUDIO ||
        mPlaybackRate < THRESHOLD_LOW_PLAYBACKRATE_AUDIO)) {
     SetMutedInternal(mMuted | MUTED_BY_INVALID_PLAYBACK_RATE);
   } else {
     SetMutedInternal(mMuted & ~MUTED_BY_INVALID_PLAYBACK_RATE);
   }
 
   if (mDecoder) {
-    mDecoder->SetPlaybackRate(mPlaybackRate);
+    mDecoder->SetPlaybackRate(ClampPlaybackRate(mPlaybackRate));
   }
   DispatchAsyncEvent(NS_LITERAL_STRING("ratechange"));
 }
 
 NS_IMETHODIMP HTMLMediaElement::SetPlaybackRate(double aPlaybackRate)
 {
   ErrorResult rv;
   SetPlaybackRate(aPlaybackRate, rv);
--- a/dom/media/test/test_info_leak.html
+++ b/dom/media/test/test_info_leak.html
@@ -24,17 +24,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 /** Test for Bug 478957 **/
 
 // Tests whether we leak events and state change info when loading stuff from local files from a webserver.
 
 var manager = new MediaTestManager;
 
 var gEventTypes = [ 'loadstart', 'progress', 'suspend', 'abort', 'error', 'emptied', 'stalled', 'play', 'pause', 'loadedmetadata', 'loadeddata', 'waiting', 'playing', 'canplay', 'canplaythrough', 'seeking', 'seeked', 'timeupdate', 'ended', 'ratechange', 'durationchange', 'volumechange' ];
 
-var gExpectedEvents = ['ratechange', 'loadstart', 'error'];
+var gExpectedEvents = ['loadstart', 'error'];
 
 function createTestArray() {
   var tests = [];
   var tmpVid = document.createElement("video");
 
   for (var testNum=0; testNum<gInfoLeakTests.length; testNum++) {
     var test = gInfoLeakTests[testNum];
     if (!tmpVid.canPlayType(test.type)) {
--- a/dom/media/test/test_load.html
+++ b/dom/media/test/test_load.html
@@ -89,17 +89,17 @@ function prependSource(src, type) {
 var gTests = [
   {
     // Test 0: adding video to doc, then setting src should load implicitly.
     create:
       function(src, type) {
         document.body.appendChild(gMedia);
         gMedia.src = src;
       },
-    expectedEvents: ['ratechange',  'loadstart', 'durationchange', 'loadedmetadata', 'loadeddata']
+    expectedEvents: ['loadstart', 'durationchange', 'loadedmetadata', 'loadeddata']
   }, {
     // Test 1: adding video to doc, then adding source.
     create:
       function(src, type) {
         document.body.appendChild(gMedia);
         addSource(src, type);
       },
     expectedEvents: ['loadstart', 'durationchange', 'loadedmetadata', 'loadeddata']
@@ -117,17 +117,17 @@ var gTests = [
   }, {
     // Test 3:  video with bad src, good <source>, ensure that <source> aren't used.
     create:
       function(src, type) {
         gMedia.src = "404a";
         addSource(src, type);
         document.body.appendChild(gMedia);
       },
-    expectedEvents: ['ratechange', 'loadstart', 'error']
+    expectedEvents: ['loadstart', 'error']
   }, {
     // Test 4:  video with only bad source, loading, then adding a good source
     // -  should resume load.
     create:
       function(src, type) {
         addSource("404a", type);
         var s2 = addSource("404b", type);
         s2.addEventListener("error",
@@ -165,17 +165,17 @@ var gTests = [
     create:
       function(src, type) {
         gMedia.preload = "none";
         gMedia.src = src;
         document.body.appendChild(gMedia);
         addSource(src, type);
         gMedia.load();
       },
-    expectedEvents: ['emptied', 'ratechange',  'loadstart', 'durationchange', 'loadedmetadata', 'loadeddata']
+    expectedEvents: ['emptied',  'loadstart', 'durationchange', 'loadedmetadata', 'loadeddata']
   }
 ];
 
 function nextTest() {
   if (gMedia) {
     for (var i=0; i<gEventTypes.length; i++) {
       gMedia.removeEventListener(gEventTypes[i], listener);
     }
--- a/dom/media/test/test_playback_rate.html
+++ b/dom/media/test/test_playback_rate.html
@@ -81,17 +81,17 @@ function afterNullPlaybackRate(e) {
 
   ok(t.currentTime == t.oldCurrentTime, t.name + ": Current time should not change when playbackRate is null (" + t.currentTime + " " + t.oldCurrentTime + ").");
   ok(!t.paused, t.name + ": The element should not be in paused state.");
   t.removeEventListener("paused", onpaused);
   is(t.pausedReceived, undefined, t.name + ": Paused event should not have been received.");
   t.timestamp = Date.now();
   t.oldCurrentTime = t.currentTime;
   t.playbackRate = VERY_FAST_RATE;
-  is(t.playbackRate, FAST_RATE, t.name + ": Playback rate should be clamped to " + FAST_RATE + ".");
+  is(t.playbackRate, VERY_FAST_RATE, t.name + ": Playback rate should be clamped to " + VERY_FAST_RATE + ".");
 }
 
 function onended(e) {
   var t = e.target;
   t.gotEnded = true;
 
   t.bufferingTime = 0;
   // If we got "ended" too early, skip these tests.
@@ -154,17 +154,17 @@ function startTest(test, token) {
   element.addEventListener("volumechange", onvolumechange);
   manager.started(token);
   element.startTimestamp = Date.now();
   is(element.mozPreservesPitch, true, test.name + ": Pitch preservation should be enabled by default.");
   element.addEventListener("loadedmetadata", function() {
     is(element.playbackRate, 1.0, test.name + ": playbackRate should be initially 1.0");
     is(element.defaultPlaybackRate, 1.0, test.name + ": defaultPlaybackRate should be initially 1.0");
     element.playbackRate = VERY_SLOW_RATE;
-    is(element.playbackRate, SLOW_RATE, test.name + ": PlaybackRate should be clamped to " + SLOW_RATE + ".");
+    is(element.playbackRate, VERY_SLOW_RATE, test.name + ": PlaybackRate should be " + VERY_SLOW_RATE + ".");
     element.play();
     element.playbackRate = SLOW_RATE;
   });
 }
 
 manager.runTests(gPlayedTests, startTest);
 
 </script>
--- a/dom/tests/mochitest/localstorage/test_localStorageQuotaPrivateBrowsing_perwindowpb.html
+++ b/dom/tests/mochitest/localstorage/test_localStorageQuotaPrivateBrowsing_perwindowpb.html
@@ -146,40 +146,34 @@ function onMessageReceived(event, aWindo
       break;
     // Any other message indicates error or succes message of a test
     default:
       SimpleTest.ok(!event.data.match(failureRegExp), event.data);
       break;
   }
 }
 
-function whenDelayedStartupFinished(aWindow, aCallback) {
+function whenDelayedStartupFinished(aCallback) {
   Services.obs.addObserver(function observer(aSubject, aTopic) {
-    if (aWindow == aSubject) {
-      Services.obs.removeObserver(observer, aTopic);
-    }
+    Services.obs.removeObserver(observer, aTopic);
 
-    if (aWindow.content == null || aWindow.content.location.href != CONTENT_PAGE ) {
-      aWindow.addEventListener("DOMContentLoaded", function() {
-        SimpleTest.executeSoon(function() { aCallback(aWindow); });
-      }, {capture: true, once: true});
-
-      aWindow.gBrowser.loadURI(CONTENT_PAGE);
-    }
+    aSubject.addEventListener("DOMContentLoaded", function() {
+      SimpleTest.executeSoon(function() { aCallback(aSubject); });
+    }, {capture: true, once: true});
   }, "browser-delayed-startup-finished");
 }
 
 function testOnWindow(aIsPrivate, callback) {
   var mainWindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
                          .getInterface(Ci.nsIWebNavigation)
                          .QueryInterface(Ci.nsIDocShellTreeItem)
                          .rootTreeItem
                          .QueryInterface(Ci.nsIInterfaceRequestor)
                          .getInterface(Ci.nsIDOMWindow);
 
-  var win = mainWindow.OpenBrowserWindow({private: aIsPrivate});
-  whenDelayedStartupFinished(win, function() { callback(win); });
+  mainWindow.openUILinkIn(CONTENT_PAGE, "window", {private: aIsPrivate});
+  whenDelayedStartupFinished(callback);
 };
 </script>
 </head>
 <body onload="startTest();">
 </body>
 </html>
--- a/dom/webidl/Console.webidl
+++ b/dom/webidl/Console.webidl
@@ -46,17 +46,16 @@ namespace console {
   [ChromeOnly]
   const boolean IS_NATIVE_CONSOLE = true;
 };
 
 // This is used to propagate console events to the observers.
 dictionary ConsoleEvent {
   (unsigned long long or DOMString) ID;
   (unsigned long long or DOMString) innerID;
-  any originAttributes = null;
   DOMString addonId = "";
   DOMString level = "";
   DOMString filename = "";
   unsigned long lineNumber = 0;
   unsigned long columnNumber = 0;
   DOMString functionName = "";
   double timeStamp = 0;
   sequence<any> arguments;
--- a/gfx/doc/README.webrender
+++ b/gfx/doc/README.webrender
@@ -74,9 +74,9 @@ there is another crate in m-c called moz
 the same folder to store its rust dependencies. If one of the libraries that is
 required by both mozjs_sys and webrender is updated without updating the other
 project's Cargo.lock file, that results in build bustage.
 This means that any time you do this sort of manual update of packages, you need
 to make sure that mozjs_sys also has its Cargo.lock file updated if needed, hence
 the need to run the cargo update command in js/src as well. Hopefully this will
 be resolved soon.
 
-Latest Commit: 1007a65c6dd1fdfb8b39d57d7faff3cae7b32e0c
+Latest Commit: 310af2613e7508b22cad11e734b8c47e66447cc7
--- a/gfx/layers/wr/ScrollingLayersHelper.cpp
+++ b/gfx/layers/wr/ScrollingLayersHelper.cpp
@@ -122,17 +122,17 @@ ScrollingLayersHelper::ScrollingLayersHe
   // Finally, if clip chain's ASR was the leafmost ASR, then the top of the
   // scroll id stack right now will point to that, rather than the item's ASR
   // which is what we want. So we override that by doing a PushClipAndScrollInfo
   // call. This should generally only happen for fixed-pos type items, but we
   // use code generic enough to handle other cases.
   FrameMetrics::ViewID scrollId = aItem->GetActiveScrolledRoot()
       ? nsLayoutUtils::ViewIDForASR(aItem->GetActiveScrolledRoot())
       : FrameMetrics::NULL_SCROLL_ID;
-  if (aBuilder.TopmostScrollId() != Some(scrollId)) {
+  if (aBuilder.TopmostScrollId() != scrollId) {
     Maybe<wr::WrClipId> clipId = mBuilder->TopmostClipId();
     mBuilder->PushClipAndScrollInfo(scrollId, clipId.ptrOr(nullptr));
     mPushedClipAndScroll = true;
   }
 }
 
 void
 ScrollingLayersHelper::DefineAndPushScrollLayers(nsDisplayItem* aItem,
@@ -145,17 +145,17 @@ ScrollingLayersHelper::DefineAndPushScro
 {
   if (!aAsr) {
     return;
   }
   Maybe<ScrollMetadata> metadata = aAsr->mScrollableFrame->ComputeScrollMetadata(
       nullptr, aItem->ReferenceFrame(), ContainerLayerParameters(), nullptr);
   MOZ_ASSERT(metadata);
   FrameMetrics::ViewID scrollId = metadata->GetMetrics().GetScrollId();
-  if (aBuilder.TopmostScrollId() == Some(scrollId)) {
+  if (aBuilder.TopmostScrollId() == scrollId) {
     // it's already been pushed, so we don't need to recurse any further.
     return;
   }
 
   // Find the first clip up the chain that's "outside" aAsr. Any clips
   // that are "inside" aAsr (i.e. that are scrolled by aAsr) will need to be
   // pushed onto the stack after aAsr has been pushed. On the recursive call
   // we need to skip up the clip chain past these clips.
--- a/gfx/webrender/res/clip_shared.glsl
+++ b/gfx/webrender/res/clip_shared.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 #ifdef WR_VERTEX_SHADER
 
 #define SEGMENT_ALL         0
 #define SEGMENT_CORNER_TL   1
--- a/gfx/webrender/res/cs_blur.fs.glsl
+++ b/gfx/webrender/res/cs_blur.fs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 // TODO(gw): Write a fast path blur that handles smaller blur radii
 //           with a offset / weight uniform table and a constant
 //           loop iteration count!
 
--- a/gfx/webrender/res/cs_blur.glsl
+++ b/gfx/webrender/res/cs_blur.glsl
@@ -1,10 +1,11 @@
-#line 1
 /* 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,prim_shared
+
 varying vec3 vUv;
 flat varying vec4 vUvRect;
 flat varying vec2 vOffsetScale;
 flat varying float vSigma;
 flat varying int vBlurRadius;
--- a/gfx/webrender/res/cs_blur.vs.glsl
+++ b/gfx/webrender/res/cs_blur.vs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 // Applies a separable gaussian blur in one direction, as specified
 // by the dir field in the blur command.
 
 #define DIR_HORIZONTAL  0
--- a/gfx/webrender/res/cs_box_shadow.fs.glsl
+++ b/gfx/webrender/res/cs_box_shadow.fs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 // See http://asciimath.org to render the equations here.
 
 // The Gaussian function used for blurring:
 //
--- a/gfx/webrender/res/cs_box_shadow.glsl
+++ b/gfx/webrender/res/cs_box_shadow.glsl
@@ -1,10 +1,11 @@
-#line 1
 /* 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,prim_shared
+
 varying vec2 vPos;
 flat varying vec2 vBorderRadii;
 flat varying float vBlurRadius;
 flat varying vec4 vBoxShadowRect;
 flat varying float vInverted;
--- a/gfx/webrender/res/cs_box_shadow.vs.glsl
+++ b/gfx/webrender/res/cs_box_shadow.vs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 void main(void) {
     PrimitiveInstance pi = fetch_prim_instance();
     RenderTaskData task = fetch_render_task(pi.render_task_index);
     BoxShadow bs = fetch_boxshadow(pi.specific_prim_address);
--- a/gfx/webrender/res/cs_clip_border.fs.glsl
+++ b/gfx/webrender/res/cs_clip_border.fs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 void main(void) {
     vec2 local_pos = vPos.xy / vPos.z;
 
     // Get local space position relative to the clip center.
--- a/gfx/webrender/res/cs_clip_border.glsl
+++ b/gfx/webrender/res/cs_clip_border.glsl
@@ -1,14 +1,14 @@
-#line 1
-
 /* 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,prim_shared,clip_shared
+
 varying vec3 vPos;
 
 flat varying vec2 vClipCenter;
 
 flat varying vec4 vPoint_Tangent0;
 flat varying vec4 vPoint_Tangent1;
 flat varying vec3 vDotParams;
 flat varying vec2 vAlphaMask;
--- a/gfx/webrender/res/cs_clip_border.vs.glsl
+++ b/gfx/webrender/res/cs_clip_border.vs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 // Matches BorderCorner enum in border.rs
 #define CORNER_TOP_LEFT     0
 #define CORNER_TOP_RIGHT    1
 #define CORNER_BOTTOM_LEFT  2
--- a/gfx/webrender/res/cs_clip_image.glsl
+++ b/gfx/webrender/res/cs_clip_image.glsl
@@ -1,10 +1,10 @@
-#line 1
-
 /* 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,prim_shared,clip_shared
+
 varying vec3 vPos;
 flat varying vec4 vClipMaskUvRect;
 flat varying vec4 vClipMaskUvInnerRect;
 flat varying float vLayer;
--- a/gfx/webrender/res/cs_clip_image.vs.glsl
+++ b/gfx/webrender/res/cs_clip_image.vs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 struct ImageMaskData {
     RectWithSize local_rect;
 };
 
--- a/gfx/webrender/res/cs_clip_rectangle.glsl
+++ b/gfx/webrender/res/cs_clip_rectangle.glsl
@@ -1,12 +1,12 @@
-#line 1
-
 /* 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,prim_shared,clip_shared
+
 varying vec3 vPos;
 flat varying float vClipMode;
 flat varying vec4 vClipCenter_Radius_TL;
 flat varying vec4 vClipCenter_Radius_TR;
 flat varying vec4 vClipCenter_Radius_BL;
 flat varying vec4 vClipCenter_Radius_BR;
--- a/gfx/webrender/res/cs_clip_rectangle.vs.glsl
+++ b/gfx/webrender/res/cs_clip_rectangle.vs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 struct ClipRect {
     RectWithSize rect;
     vec4 mode;
 };
--- a/gfx/webrender/res/cs_text_run.fs.glsl
+++ b/gfx/webrender/res/cs_text_run.fs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 void main(void) {
     float a = texture(sColor0, vUv).a;
     oFragColor = vec4(vColor.rgb, vColor.a * a);
 }
--- a/gfx/webrender/res/cs_text_run.glsl
+++ b/gfx/webrender/res/cs_text_run.glsl
@@ -1,7 +1,8 @@
-#line 1
 /* 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,prim_shared
+
 varying vec3 vUv;
 flat varying vec4 vColor;
--- a/gfx/webrender/res/cs_text_run.vs.glsl
+++ b/gfx/webrender/res/cs_text_run.vs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 // Draw a text run to a cache target. These are always
 // drawn un-transformed. These are used for effects such
 // as text-shadow.
 
deleted file mode 100644
--- a/gfx/webrender/res/debug_color.fs.glsl
+++ /dev/null
@@ -1,10 +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/. */
-
-varying vec4 vColor;
-
-void main(void)
-{
-    oFragColor = vColor;
-}
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/debug_color.glsl
@@ -0,0 +1,24 @@
+/* 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,shared_other
+
+varying vec4 vColor;
+
+#ifdef WR_VERTEX_SHADER
+in vec4 aColor;
+
+void main(void) {
+    vColor = aColor;
+    vec4 pos = vec4(aPosition, 1.0);
+    pos.xy = floor(pos.xy * uDevicePixelRatio + 0.5) / uDevicePixelRatio;
+    gl_Position = uTransform * pos;
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+void main(void) {
+    oFragColor = vColor;
+}
+#endif
deleted file mode 100644
--- a/gfx/webrender/res/debug_color.vs.glsl
+++ /dev/null
@@ -1,14 +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/. */
-
-in vec4 aColor;
-varying vec4 vColor;
-
-void main(void)
-{
-    vColor = aColor;
-    vec4 pos = vec4(aPosition, 1.0);
-    pos.xy = floor(pos.xy * uDevicePixelRatio + 0.5) / uDevicePixelRatio;
-    gl_Position = uTransform * pos;
-}
deleted file mode 100644
--- a/gfx/webrender/res/debug_font.fs.glsl
+++ /dev/null
@@ -1,12 +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/. */
-
-varying vec2 vColorTexCoord;
-varying vec4 vColor;
-
-void main(void)
-{
-    float alpha = texture(sColor0, vec3(vColorTexCoord.xy, 0.0)).r;
-    oFragColor = vec4(vColor.xyz, vColor.w * alpha);
-}
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/debug_font.glsl
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include shared,shared_other
+
+varying vec2 vColorTexCoord;
+varying vec4 vColor;
+
+#ifdef WR_VERTEX_SHADER
+in vec4 aColor;
+in vec4 aColorTexCoord;
+
+void main(void) {
+    vColor = aColor;
+    vColorTexCoord = aColorTexCoord.xy;
+    vec4 pos = vec4(aPosition, 1.0);
+    pos.xy = floor(pos.xy * uDevicePixelRatio + 0.5) / uDevicePixelRatio;
+    gl_Position = uTransform * pos;
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+void main(void) {
+    float alpha = texture(sColor0, vec3(vColorTexCoord.xy, 0.0)).r;
+    oFragColor = vec4(vColor.xyz, vColor.w * alpha);
+}
+#endif
deleted file mode 100644
--- a/gfx/webrender/res/debug_font.vs.glsl
+++ /dev/null
@@ -1,18 +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/. */
-
-in vec4 aColor;
-in vec4 aColorTexCoord;
-
-varying vec2 vColorTexCoord;
-varying vec4 vColor;
-
-void main(void)
-{
-    vColor = aColor;
-    vColorTexCoord = aColorTexCoord.xy;
-    vec4 pos = vec4(aPosition, 1.0);
-    pos.xy = floor(pos.xy * uDevicePixelRatio + 0.5) / uDevicePixelRatio;
-    gl_Position = uTransform * pos;
-}
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -1,42 +1,12 @@
-#line 1
 /* 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/. */
 
-#define PST_TOP_LEFT     0
-#define PST_TOP          1
-#define PST_TOP_RIGHT    2
-#define PST_RIGHT        3
-#define PST_BOTTOM_RIGHT 4
-#define PST_BOTTOM       5
-#define PST_BOTTOM_LEFT  6
-#define PST_LEFT         7
-
-#define BORDER_LEFT      0
-#define BORDER_TOP       1
-#define BORDER_RIGHT     2
-#define BORDER_BOTTOM    3
-
-// Border styles as defined in webrender_api/types.rs
-#define BORDER_STYLE_NONE         0
-#define BORDER_STYLE_SOLID        1
-#define BORDER_STYLE_DOUBLE       2
-#define BORDER_STYLE_DOTTED       3
-#define BORDER_STYLE_DASHED       4
-#define BORDER_STYLE_HIDDEN       5
-#define BORDER_STYLE_GROOVE       6
-#define BORDER_STYLE_RIDGE        7
-#define BORDER_STYLE_INSET        8
-#define BORDER_STYLE_OUTSET       9
-
-#define UV_NORMALIZED    uint(0)
-#define UV_PIXEL         uint(1)
-
 #define EXTEND_MODE_CLAMP  0
 #define EXTEND_MODE_REPEAT 1
 
 #define LINE_STYLE_SOLID        0
 #define LINE_STYLE_DOTTED       1
 #define LINE_STYLE_DASHED       2
 #define LINE_STYLE_WAVY         3
 
@@ -327,95 +297,16 @@ struct RadialGradient {
     vec4 tile_size_repeat;
 };
 
 RadialGradient fetch_radial_gradient(int address) {
     vec4 data[3] = fetch_from_resource_cache_3(address);
     return RadialGradient(data[0], data[1], data[2]);
 }
 
-struct Border {
-    vec4 style;
-    vec4 widths;
-    vec4 colors[4];
-    vec4 radii[2];
-};
-
-vec4 get_effective_border_widths(Border border, int style) {
-    switch (style) {
-        case BORDER_STYLE_DOUBLE:
-            // Calculate the width of a border segment in a style: double
-            // border. Round to the nearest CSS pixel.
-
-            // The CSS spec doesn't define what width each of the segments
-            // in a style: double border should be. It only says that the
-            // sum of the segments should be equal to the total border
-            // width. We pick to make the segments (almost) equal thirds
-            // for now - we can adjust this if we find other browsers pick
-            // different values in some cases.
-            // SEE: https://drafts.csswg.org/css-backgrounds-3/#double
-            return floor(0.5 + border.widths / 3.0);
-        case BORDER_STYLE_GROOVE:
-        case BORDER_STYLE_RIDGE:
-            return floor(0.5 + border.widths * 0.5);
-        default:
-            return border.widths;
-    }
-}
-
-Border fetch_border(int address) {
-    vec4 data[8] = fetch_from_resource_cache_8(address);
-    return Border(data[0], data[1],
-                  vec4[4](data[2], data[3], data[4], data[5]),
-                  vec4[2](data[6], data[7]));
-}
-
-struct BorderCorners {
-    vec2 tl_outer;
-    vec2 tl_inner;
-    vec2 tr_outer;
-    vec2 tr_inner;
-    vec2 br_outer;
-    vec2 br_inner;
-    vec2 bl_outer;
-    vec2 bl_inner;
-};
-
-BorderCorners get_border_corners(Border border, RectWithSize local_rect) {
-    vec2 tl_outer = local_rect.p0;
-    vec2 tl_inner = tl_outer + vec2(max(border.radii[0].x, border.widths.x),
-                                    max(border.radii[0].y, border.widths.y));
-
-    vec2 tr_outer = vec2(local_rect.p0.x + local_rect.size.x,
-                         local_rect.p0.y);
-    vec2 tr_inner = tr_outer + vec2(-max(border.radii[0].z, border.widths.z),
-                                    max(border.radii[0].w, border.widths.y));
-
-    vec2 br_outer = vec2(local_rect.p0.x + local_rect.size.x,
-                         local_rect.p0.y + local_rect.size.y);
-    vec2 br_inner = br_outer - vec2(max(border.radii[1].x, border.widths.z),
-                                    max(border.radii[1].y, border.widths.w));
-
-    vec2 bl_outer = vec2(local_rect.p0.x,
-                         local_rect.p0.y + local_rect.size.y);
-    vec2 bl_inner = bl_outer + vec2(max(border.radii[1].z, border.widths.x),
-                                    -max(border.radii[1].w, border.widths.w));
-
-    return BorderCorners(
-        tl_outer,
-        tl_inner,
-        tr_outer,
-        tr_inner,
-        br_outer,
-        br_inner,
-        bl_outer,
-        bl_inner
-    );
-}
-
 struct Glyph {
     vec2 offset;
 };
 
 Glyph fetch_glyph(int specific_prim_address,
                   int glyph_index,
                   int subpx_dir) {
     // Two glyphs are packed in each texel in the GPU cache.
--- a/gfx/webrender/res/ps_angle_gradient.fs.glsl
+++ b/gfx/webrender/res/ps_angle_gradient.fs.glsl
@@ -1,10 +1,8 @@
-#line 1
-
 /* 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/. */
 
 void main(void) {
     vec2 pos = mod(vPos, vTileRepeat);
 
     if (pos.x >= vTileSize.x ||
--- a/gfx/webrender/res/ps_angle_gradient.glsl
+++ b/gfx/webrender/res/ps_angle_gradient.glsl
@@ -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,prim_shared
+
 flat varying int vGradientAddress;
 flat varying float vGradientRepeat;
 
 flat varying vec2 vScaledDir;
 flat varying vec2 vStartPoint;
 
 flat varying vec2 vTileSize;
 flat varying vec2 vTileRepeat;
--- a/gfx/webrender/res/ps_angle_gradient.vs.glsl
+++ b/gfx/webrender/res/ps_angle_gradient.vs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 void main(void) {
     Primitive prim = load_primitive();
     Gradient gradient = fetch_gradient(prim.specific_prim_address);
 
--- a/gfx/webrender/res/ps_blend.glsl
+++ b/gfx/webrender/res/ps_blend.glsl
@@ -1,8 +1,10 @@
 /* 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,prim_shared
+
 varying vec3 vUv;
 flat varying vec4 vUvBounds;
 flat varying float vAmount;
 flat varying int vOp;
--- a/gfx/webrender/res/ps_blend.vs.glsl
+++ b/gfx/webrender/res/ps_blend.vs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 void main(void) {
     CompositeInstance ci = fetch_composite_instance();
     AlphaBatchTask dest_task = fetch_alpha_batch_task(ci.render_task_index);
     AlphaBatchTask src_task = fetch_alpha_batch_task(ci.src_task_index);
--- a/gfx/webrender/res/ps_border_corner.fs.glsl
+++ b/gfx/webrender/res/ps_border_corner.fs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 void main(void) {
     float alpha = 1.0;
 #ifdef WR_FEATURE_TRANSFORM
     alpha = 0.0;
--- a/gfx/webrender/res/ps_border_corner.glsl
+++ b/gfx/webrender/res/ps_border_corner.glsl
@@ -1,13 +1,14 @@
-#line 1
 /* 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,prim_shared,shared_border
+
 // Edge color transition
 flat varying vec4 vColor00;
 flat varying vec4 vColor01;
 flat varying vec4 vColor10;
 flat varying vec4 vColor11;
 flat varying vec4 vColorEdgeLine;
 
 // Border radius
--- a/gfx/webrender/res/ps_border_corner.vs.glsl
+++ b/gfx/webrender/res/ps_border_corner.vs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 // Matches BorderCornerSide enum in border.rs
 #define SIDE_BOTH       0
 #define SIDE_FIRST      1
 #define SIDE_SECOND     2
--- a/gfx/webrender/res/ps_border_edge.fs.glsl
+++ b/gfx/webrender/res/ps_border_edge.fs.glsl
@@ -1,10 +1,8 @@
-#line 1
-
 /* 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/. */
 
 void main(void) {
     float alpha = 1.0;
 #ifdef WR_FEATURE_TRANSFORM
     alpha = 0.0;
--- a/gfx/webrender/res/ps_border_edge.glsl
+++ b/gfx/webrender/res/ps_border_edge.glsl
@@ -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,prim_shared,shared_border
+
 flat varying vec4 vColor0;
 flat varying vec4 vColor1;
 flat varying vec2 vEdgeDistance;
 flat varying float vAxisSelect;
 flat varying float vAlphaSelect;
 flat varying vec4 vClipParams;
 flat varying float vClipSelect;
 
--- a/gfx/webrender/res/ps_border_edge.vs.glsl
+++ b/gfx/webrender/res/ps_border_edge.vs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 void write_edge_distance(float p0,
                          float original_width,
                          float adjusted_width,
                          float style,
--- a/gfx/webrender/res/ps_box_shadow.glsl
+++ b/gfx/webrender/res/ps_box_shadow.glsl
@@ -1,9 +1,11 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include shared,prim_shared
+
 flat varying vec4 vColor;
 
 varying vec3 vUv;
 flat varying vec2 vMirrorPoint;
 flat varying vec4 vCacheUvRectCoords;
--- a/gfx/webrender/res/ps_box_shadow.vs.glsl
+++ b/gfx/webrender/res/ps_box_shadow.vs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 #define BS_HEADER_VECS 4
 
 void main(void) {
     Primitive prim = load_primitive();
--- a/gfx/webrender/res/ps_cache_image.fs.glsl
+++ b/gfx/webrender/res/ps_cache_image.fs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 void main(void) {
     vec2 uv = clamp(vUv.xy, vUvBounds.xy, vUvBounds.zw);
     oFragColor = texture(sColor0, vec3(uv, vUv.z));
 }
--- a/gfx/webrender/res/ps_cache_image.glsl
+++ b/gfx/webrender/res/ps_cache_image.glsl
@@ -1,6 +1,8 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include shared,prim_shared
+
 varying vec3 vUv;
 flat varying vec4 vUvBounds;
--- a/gfx/webrender/res/ps_cache_image.vs.glsl
+++ b/gfx/webrender/res/ps_cache_image.vs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 // Draw a cached primitive (e.g. a blurred text run) from the
 // target cache to the framebuffer, applying tile clip boundaries.
 
 void main(void) {
--- a/gfx/webrender/res/ps_clear.vs.glsl
+++ b/gfx/webrender/res/ps_clear.vs.glsl
@@ -1,10 +1,8 @@
-#line 1
-
 /* 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/. */
 
 in ivec4 aClearRectangle;
 
 void main() {
     vec4 rect = vec4(aClearRectangle);
--- a/gfx/webrender/res/ps_composite.fs.glsl
+++ b/gfx/webrender/res/ps_composite.fs.glsl
@@ -1,10 +1,8 @@
-#line 1
-
 /* 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/. */
 
 float gauss(float x, float sigma) {
     if (sigma == 0.0)
         return 1.0;
     return (1.0 / sqrt(6.283185307179586 * sigma * sigma)) * exp(-(x * x) / (2.0 * sigma * sigma));
--- a/gfx/webrender/res/ps_composite.glsl
+++ b/gfx/webrender/res/ps_composite.glsl
@@ -1,7 +1,9 @@
 /* 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,prim_shared
+
 varying vec3 vUv0;
 varying vec3 vUv1;
 flat varying int vOp;
--- a/gfx/webrender/res/ps_composite.vs.glsl
+++ b/gfx/webrender/res/ps_composite.vs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 void main(void) {
     CompositeInstance ci = fetch_composite_instance();
     AlphaBatchTask dest_task = fetch_alpha_batch_task(ci.render_task_index);
     ReadbackTask backdrop_task = fetch_readback_task(ci.backdrop_task_index);
--- a/gfx/webrender/res/ps_gradient.glsl
+++ b/gfx/webrender/res/ps_gradient.glsl
@@ -1,11 +1,13 @@
 /* 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,prim_shared
+
 varying vec4 vColor;
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
 #else
 varying vec2 vPos;
 #endif
--- a/gfx/webrender/res/ps_gradient.vs.glsl
+++ b/gfx/webrender/res/ps_gradient.vs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 void main(void) {
     Primitive prim = load_primitive();
     Gradient gradient = fetch_gradient(prim.specific_prim_address);
 
--- a/gfx/webrender/res/ps_hardware_composite.glsl
+++ b/gfx/webrender/res/ps_hardware_composite.glsl
@@ -1,5 +1,7 @@
 /* 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,prim_shared
+
 varying vec3 vUv;
--- a/gfx/webrender/res/ps_hardware_composite.vs.glsl
+++ b/gfx/webrender/res/ps_hardware_composite.vs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 void main(void) {
     CompositeInstance ci = fetch_composite_instance();
     AlphaBatchTask dest_task = fetch_alpha_batch_task(ci.render_task_index);
     AlphaBatchTask src_task = fetch_alpha_batch_task(ci.src_task_index);
--- a/gfx/webrender/res/ps_image.fs.glsl
+++ b/gfx/webrender/res/ps_image.fs.glsl
@@ -1,10 +1,8 @@
-#line 1
-
 /* 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/. */
 
 void main(void) {
 #ifdef WR_FEATURE_TRANSFORM
     float alpha = 0.0;
     vec2 pos = init_transform_fs(vLocalPos, alpha);
--- a/gfx/webrender/res/ps_image.glsl
+++ b/gfx/webrender/res/ps_image.glsl
@@ -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,prim_shared
+
 // If this is in WR_FEATURE_TEXTURE_RECT mode, the rect and size use non-normalized
 // texture coordinates. Otherwise, it uses normalized texture coordinates. Please
 // check GL_TEXTURE_RECTANGLE.
 flat varying vec2 vTextureOffset; // Offset of this image into the texture atlas.
 flat varying vec2 vTextureSize;   // Size of the image in the texture atlas.
 flat varying vec2 vTileSpacing;   // Amount of space between tiled instances of this image.
 flat varying vec4 vStRect;        // Rectangle of valid texture rect.
 flat varying float vLayer;
--- a/gfx/webrender/res/ps_image.vs.glsl
+++ b/gfx/webrender/res/ps_image.vs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 void main(void) {
     Primitive prim = load_primitive();
     Image image = fetch_image(prim.specific_prim_address);
     ImageResource res = fetch_image_resource(prim.user_data0);
--- a/gfx/webrender/res/ps_line.fs.glsl
+++ b/gfx/webrender/res/ps_line.fs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 float det(vec2 a, vec2 b) {
     return a.x * b.y - b.x * a.y;
 }
 
--- a/gfx/webrender/res/ps_line.glsl
+++ b/gfx/webrender/res/ps_line.glsl
@@ -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,prim_shared
+
 varying vec4 vColor;
 flat varying int vStyle;
 flat varying float vAxisSelect;
 flat varying vec4 vParams;
 flat varying vec2 vLocalOrigin;
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
--- a/gfx/webrender/res/ps_line.vs.glsl
+++ b/gfx/webrender/res/ps_line.vs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 #define LINE_ORIENTATION_VERTICAL       0
 #define LINE_ORIENTATION_HORIZONTAL     1
 
 void main(void) {
--- a/gfx/webrender/res/ps_radial_gradient.glsl
+++ b/gfx/webrender/res/ps_radial_gradient.glsl
@@ -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,prim_shared
+
 flat varying int vGradientAddress;
 flat varying float vGradientRepeat;
 
 flat varying vec2 vStartCenter;
 flat varying vec2 vEndCenter;
 flat varying float vStartRadius;
 flat varying float vEndRadius;
 
--- a/gfx/webrender/res/ps_radial_gradient.vs.glsl
+++ b/gfx/webrender/res/ps_radial_gradient.vs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 void main(void) {
     Primitive prim = load_primitive();
     RadialGradient gradient = fetch_radial_gradient(prim.specific_prim_address);
 
--- a/gfx/webrender/res/ps_rectangle.glsl
+++ b/gfx/webrender/res/ps_rectangle.glsl
@@ -1,9 +1,11 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include shared,prim_shared
+
 varying vec4 vColor;
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
 #endif
--- a/gfx/webrender/res/ps_rectangle.vs.glsl
+++ b/gfx/webrender/res/ps_rectangle.vs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 void main(void) {
     Primitive prim = load_primitive();
     Rectangle rect = fetch_rectangle(prim.specific_prim_address);
     vColor = rect.color;
--- a/gfx/webrender/res/ps_split_composite.fs.glsl
+++ b/gfx/webrender/res/ps_split_composite.fs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 void main(void) {
     bvec4 inside = lessThanEqual(vec4(vUvTaskBounds.xy, vUv.xy),
                                  vec4(vUv.xy, vUvTaskBounds.zw));
     if (all(inside)) {
--- a/gfx/webrender/res/ps_split_composite.glsl
+++ b/gfx/webrender/res/ps_split_composite.glsl
@@ -1,8 +1,9 @@
-#line 1
 /* 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,prim_shared
+
 varying vec3 vUv;
 flat varying vec4 vUvTaskBounds;
 flat varying vec4 vUvSampleBounds;
--- a/gfx/webrender/res/ps_split_composite.vs.glsl
+++ b/gfx/webrender/res/ps_split_composite.vs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 struct SplitGeometry {
     vec3 points[4];
 };
 
--- a/gfx/webrender/res/ps_text_run.glsl
+++ b/gfx/webrender/res/ps_text_run.glsl
@@ -1,11 +1,13 @@
 /* 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,prim_shared
+
 flat varying vec4 vColor;
 varying vec3 vUv;
 flat varying vec4 vUvBorder;
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
 #endif
--- a/gfx/webrender/res/ps_text_run.vs.glsl
+++ b/gfx/webrender/res/ps_text_run.vs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 void main(void) {
     Primitive prim = load_primitive();
     TextRun text = fetch_text_run(prim.specific_prim_address);
 
--- a/gfx/webrender/res/ps_yuv_image.fs.glsl
+++ b/gfx/webrender/res/ps_yuv_image.fs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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(WR_FEATURE_YUV_REC601) && !defined(WR_FEATURE_YUV_REC709)
 #define WR_FEATURE_YUV_REC601
 #endif
 
--- a/gfx/webrender/res/ps_yuv_image.glsl
+++ b/gfx/webrender/res/ps_yuv_image.glsl
@@ -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,prim_shared
+
 // If this is in WR_FEATURE_TEXTURE_RECT mode, the rect and size use non-normalized
 // texture coordinates. Otherwise, it uses normalized texture coordinates. Please
 // check GL_TEXTURE_RECTANGLE.
 flat varying vec2 vTextureOffsetY; // Offset of the y plane into the texture atlas.
 flat varying vec2 vTextureOffsetU; // Offset of the u plane into the texture atlas.
 flat varying vec2 vTextureOffsetV; // Offset of the v plane into the texture atlas.
 flat varying vec2 vTextureSizeY;   // Size of the y plane in the texture atlas.
 flat varying vec2 vTextureSizeUv;  // Size of the u and v planes in the texture atlas.
--- a/gfx/webrender/res/ps_yuv_image.vs.glsl
+++ b/gfx/webrender/res/ps_yuv_image.vs.glsl
@@ -1,9 +1,8 @@
-#line 1
 /* 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/. */
 
 void main(void) {
     Primitive prim = load_primitive();
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(prim.local_rect,
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/shared_border.glsl
@@ -0,0 +1,98 @@
+/* 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/. */
+
+#ifdef WR_VERTEX_SHADER
+
+// Border styles as defined in webrender_api/types.rs
+#define BORDER_STYLE_NONE         0
+#define BORDER_STYLE_SOLID        1
+#define BORDER_STYLE_DOUBLE       2
+#define BORDER_STYLE_DOTTED       3
+#define BORDER_STYLE_DASHED       4
+#define BORDER_STYLE_HIDDEN       5
+#define BORDER_STYLE_GROOVE       6
+#define BORDER_STYLE_RIDGE        7
+#define BORDER_STYLE_INSET        8
+#define BORDER_STYLE_OUTSET       9
+
+struct Border {
+    vec4 style;
+    vec4 widths;
+    vec4 colors[4];
+    vec4 radii[2];
+};
+
+struct BorderCorners {
+    vec2 tl_outer;
+    vec2 tl_inner;
+    vec2 tr_outer;
+    vec2 tr_inner;
+    vec2 br_outer;
+    vec2 br_inner;
+    vec2 bl_outer;
+    vec2 bl_inner;
+};
+
+vec4 get_effective_border_widths(Border border, int style) {
+    switch (style) {
+        case BORDER_STYLE_DOUBLE:
+            // Calculate the width of a border segment in a style: double
+            // border. Round to the nearest CSS pixel.
+
+            // The CSS spec doesn't define what width each of the segments
+            // in a style: double border should be. It only says that the
+            // sum of the segments should be equal to the total border
+            // width. We pick to make the segments (almost) equal thirds
+            // for now - we can adjust this if we find other browsers pick
+            // different values in some cases.
+            // SEE: https://drafts.csswg.org/css-backgrounds-3/#double
+            return floor(0.5 + border.widths / 3.0);
+        case BORDER_STYLE_GROOVE:
+        case BORDER_STYLE_RIDGE:
+            return floor(0.5 + border.widths * 0.5);
+        default:
+            return border.widths;
+    }
+}
+
+Border fetch_border(int address) {
+    vec4 data[8] = fetch_from_resource_cache_8(address);
+    return Border(data[0], data[1],
+                  vec4[4](data[2], data[3], data[4], data[5]),
+                  vec4[2](data[6], data[7]));
+}
+
+BorderCorners get_border_corners(Border border, RectWithSize local_rect) {
+    vec2 tl_outer = local_rect.p0;
+    vec2 tl_inner = tl_outer + vec2(max(border.radii[0].x, border.widths.x),
+                                    max(border.radii[0].y, border.widths.y));
+
+    vec2 tr_outer = vec2(local_rect.p0.x + local_rect.size.x,
+                         local_rect.p0.y);
+    vec2 tr_inner = tr_outer + vec2(-max(border.radii[0].z, border.widths.z),
+                                    max(border.radii[0].w, border.widths.y));
+
+    vec2 br_outer = vec2(local_rect.p0.x + local_rect.size.x,
+                         local_rect.p0.y + local_rect.size.y);
+    vec2 br_inner = br_outer - vec2(max(border.radii[1].x, border.widths.z),
+                                    max(border.radii[1].y, border.widths.w));
+
+    vec2 bl_outer = vec2(local_rect.p0.x,
+                         local_rect.p0.y + local_rect.size.y);
+    vec2 bl_inner = bl_outer + vec2(max(border.radii[1].z, border.widths.x),
+                                    -max(border.radii[1].w, border.widths.w));
+
+    return BorderCorners(
+        tl_outer,
+        tl_inner,
+        tr_outer,
+        tr_inner,
+        br_outer,
+        br_inner,
+        bl_outer,
+        bl_inner
+    );
+}
+
+#endif
--- a/gfx/webrender/src/debug_render.rs
+++ b/gfx/webrender/src/debug_render.rs
@@ -78,18 +78,22 @@ pub struct DebugRenderer {
     tri_vao: VAO,
     line_vertices: Vec<DebugColorVertex>,
     line_vao: VAO,
     color_program: Program,
 }
 
 impl DebugRenderer {
     pub fn new(device: &mut Device) -> DebugRenderer {
-        let font_program = device.create_program("debug_font", "shared_other", &DESC_FONT).unwrap();
-        let color_program = device.create_program("debug_color", "shared_other", &DESC_COLOR).unwrap();
+        let font_program = device.create_program("debug_font",
+                                                 "",
+                                                 &DESC_FONT).unwrap();
+        let color_program = device.create_program("debug_color",
+                                                  "",
+                                                  &DESC_COLOR).unwrap();
 
         let font_vao = device.create_vao(&DESC_FONT, 32);
         let line_vao = device.create_vao(&DESC_COLOR, 32);
         let tri_vao = device.create_vao(&DESC_COLOR, 32);
 
         let font_texture_id = device.create_texture_ids(1, TextureTarget::Array)[0];
         device.init_texture(font_texture_id,
                             debug_font_data::BMP_WIDTH,
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -41,20 +41,22 @@ const GL_FORMAT_A: gl::GLuint = gl::RED;
 #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
 const GL_FORMAT_A: gl::GLuint = gl::ALPHA;
 
 const GL_FORMAT_BGRA_GL: gl::GLuint = gl::BGRA;
 
 const GL_FORMAT_BGRA_GLES: gl::GLuint = gl::BGRA_EXT;
 
 const SHADER_VERSION_GL: &str = "#version 150\n";
-
 const SHADER_VERSION_GLES: &str = "#version 300 es\n";
 
-static SHADER_PREAMBLE: &str = "shared";
+const SHADER_KIND_VERTEX: &str = "#define WR_VERTEX_SHADER\n";
+const SHADER_KIND_FRAGMENT: &str = "#define WR_FRAGMENT_SHADER\n";
+const SHADER_IMPORT: &str = "#include ";
+const SHADER_LINE_MARKER: &str = "#line 1\n";
 
 #[repr(u32)]
 pub enum DepthFunction {
     Less = gl::LESS,
     LessEqual = gl::LEQUAL,
 }
 
 #[derive(Copy, Clone, Debug, PartialEq)]
@@ -125,32 +127,101 @@ fn get_shader_version(gl: &gl::Gl) -> &'
             SHADER_VERSION_GL
         }
         gl::GlType::Gles => {
             SHADER_VERSION_GLES
         }
     }
 }
 
-fn get_optional_shader_source(shader_name: &str, base_path: &Option<PathBuf>) -> Option<String> {
+// Get a shader string by name, from the built in resources or
+// an override path, if supplied.
+fn get_shader_source(shader_name: &str, base_path: &Option<PathBuf>) -> Option<String> {
     if let Some(ref base) = *base_path {
         let shader_path = base.join(&format!("{}.glsl", shader_name));
         if shader_path.exists() {
             let mut source = String::new();
             File::open(&shader_path).unwrap().read_to_string(&mut source).unwrap();
             return Some(source);
         }
     }
 
-    shader_source::SHADERS.get(shader_name).and_then(|s| Some((*s).to_owned()))
+    shader_source::SHADERS.get(shader_name).map(|s| s.to_string())
+}
+
+// Parse a shader string for imports. Imports are recursively processed, and
+// prepended to the list of outputs.
+fn parse_shader_source(source: String, base_path: &Option<PathBuf>, output: &mut String) {
+    for line in source.lines() {
+        if line.starts_with(SHADER_IMPORT) {
+            let imports = line[SHADER_IMPORT.len()..].split(",");
+
+            // For each import, get the source, and recurse.
+            for import in imports {
+                if let Some(include) = get_shader_source(import, base_path) {
+                    parse_shader_source(include, base_path, output);
+                }
+            }
+        } else {
+            output.push_str(line);
+            output.push_str("\n");
+        }
+    }
 }
 
-fn get_shader_source(shader_name: &str, base_path: &Option<PathBuf>) -> String {
-    get_optional_shader_source(shader_name, base_path)
-        .expect(&format!("Couldn't get required shader: {}", shader_name))
+pub fn build_shader_strings(gl_version_string: &str,
+                            features: &str,
+                            base_filename: &str,
+                            override_path: &Option<PathBuf>) -> (String, String) {
+    // Construct a list of strings to be passed to the shader compiler.
+    let mut vs_source = String::new();
+    let mut fs_source = String::new();
+
+    // GLSL requires that the version number comes first.
+    vs_source.push_str(gl_version_string);
+    fs_source.push_str(gl_version_string);
+
+    // Define a constant depending on whether we are compiling VS or FS.
+    vs_source.push_str(SHADER_KIND_VERTEX);
+    fs_source.push_str(SHADER_KIND_FRAGMENT);
+
+    // Add any defines that were passed by the caller.
+    vs_source.push_str(features);
+    fs_source.push_str(features);
+
+    // Parse the main .glsl file, including any imports
+    // and append them to the list of sources.
+    let mut shared_result = String::new();
+    if let Some(shared_source) = get_shader_source(base_filename, override_path) {
+        parse_shader_source(shared_source,
+            override_path,
+            &mut shared_result);
+    }
+
+    vs_source.push_str(SHADER_LINE_MARKER);
+    vs_source.push_str(&shared_result);
+    fs_source.push_str(SHADER_LINE_MARKER);
+    fs_source.push_str(&shared_result);
+
+    // Append legacy (.vs and .fs) files if they exist.
+    // TODO(gw): Once all shaders are ported to just use the
+    //           .glsl file, we can remove this code.
+    let vs_name = format!("{}.vs", base_filename);
+    if let Some(old_vs_source) = get_shader_source(&vs_name, override_path) {
+        vs_source.push_str(SHADER_LINE_MARKER);
+        vs_source.push_str(&old_vs_source);
+    }
+
+    let fs_name = format!("{}.fs", base_filename);
+    if let Some(old_fs_source) = get_shader_source(&fs_name, override_path) {
+        fs_source.push_str(SHADER_LINE_MARKER);
+        fs_source.push_str(&old_fs_source);
+    }
+
+    (vs_source, fs_source)
 }
 
 pub trait FileWatcherHandler : Send {
     fn file_changed(&self, path: PathBuf);
 }
 
 impl VertexAttributeKind {
     fn size_in_bytes(&self) -> u32 {
@@ -323,53 +394,16 @@ impl Drop for Texture {
         self.gl.delete_textures(&[self.id]);
     }
 }
 
 pub struct Program {
     id: gl::GLuint,
     u_transform: gl::GLint,
     u_device_pixel_ratio: gl::GLint,
-    name: String,
-    vs_source: String,
-    fs_source: String,
-    prefix: Option<String>,
-    vs_id: Option<gl::GLuint>,
-    fs_id: Option<gl::GLuint>,
-}
-
-impl Program {
-    fn attach_and_bind_shaders(&mut self,
-                               vs_id: gl::GLuint,
-                               fs_id: gl::GLuint,
-                               descriptor: &VertexDescriptor,
-                               gl: &gl::Gl) -> Result<(), ShaderError> {
-        gl.attach_shader(self.id, vs_id);
-        gl.attach_shader(self.id, fs_id);
-
-        for (i, attr) in descriptor.vertex_attributes
-                                   .iter()
-                                   .chain(descriptor.instance_attributes.iter())
-                                   .enumerate() {
-            gl.bind_attrib_location(self.id,
-                                    i as gl::GLuint,
-                                    attr.name);
-        }
-
-        gl.link_program(self.id);
-        if gl.get_program_iv(self.id, gl::LINK_STATUS) == (0 as gl::GLint) {
-            let error_log = gl.get_program_info_log(self.id);
-            println!("Failed to link shader program: {:?}\n{}", self.name, error_log);
-            gl.detach_shader(self.id, vs_id);
-            gl.detach_shader(self.id, fs_id);
-            return Err(ShaderError::Link(self.name.clone(), error_log));
-        }
-
-        Ok(())
-    }
 }
 
 impl Drop for Program {
     fn drop(&mut self) {
         debug_assert!(thread::panicking() || self.id == 0, "renderer::deinit not called");
     }
 }
 
@@ -709,31 +743,27 @@ pub struct Device {
 
     // debug
     inside_frame: bool,
 
     // resources
     resource_override_path: Option<PathBuf>,
     textures: FastHashMap<TextureId, Texture>,
 
-    // misc.
-    shader_preamble: String,
-
     max_texture_size: u32,
 
     // Frame counter. This is used to map between CPU
     // frames and GPU frames.
     frame_id: FrameId,
 }
 
 impl Device {
     pub fn new(gl: Rc<gl::Gl>,
                resource_override_path: Option<PathBuf>,
                _file_changed_handler: Box<FileWatcherHandler>) -> Device {
-        let shader_preamble = get_shader_source(SHADER_PREAMBLE, &resource_override_path);
         let max_texture_size = gl.get_integer_v(gl::MAX_TEXTURE_SIZE) as u32;
 
         Device {
             gl,
             resource_override_path,
             // This is initialized to 1 by default, but it is set
             // every frame by the call to begin_frame().
             device_pixel_ratio: 1.0,
@@ -749,18 +779,16 @@ impl Device {
             bound_pbo: PBOId(0),
             bound_read_fbo: FBOId(0),
             bound_draw_fbo: FBOId(0),
             default_read_fbo: 0,
             default_draw_fbo: 0,
 
             textures: FastHashMap::default(),
 
-            shader_preamble,
-
             max_texture_size,
             frame_id: FrameId(0),
         }
     }
 
     pub fn gl(&self) -> &gl::Gl {
         &*self.gl
     }
@@ -774,33 +802,22 @@ impl Device {
     }
 
     pub fn get_capabilities(&self) -> &Capabilities {
         &self.capabilities
     }
 
     pub fn compile_shader(gl: &gl::Gl,
                           name: &str,
-                          source_str: &str,
                           shader_type: gl::GLenum,
-                          shader_preamble: &[String])
+                          source: String)
                           -> Result<gl::GLuint, ShaderError> {
         debug!("compile {:?}", name);
-
-        let mut s = String::new();
-        s.push_str(get_shader_version(gl));
-        for prefix in shader_preamble {
-            s.push_str(prefix);
-        }
-        s.push_str(source_str);
-
         let id = gl.create_shader(shader_type);
-        let mut source = Vec::new();
-        source.extend_from_slice(s.as_bytes());
-        gl.shader_source(id, &[&source[..]]);
+        gl.shader_source(id, &[source.as_bytes()]);
         gl.compile_shader(id);
         let log = gl.get_shader_info_log(id);
         if gl.get_shader_iv(id, gl::COMPILE_STATUS) == (0 as gl::GLint) {
             println!("Failed to compile shader: {:?}\n{}", name, log);
             Err(ShaderError::Compilation(name.to_string(), log))
         } else {
             if !log.is_empty() {
                 println!("Warnings detected on shader: {:?}\n{}", name, log);
@@ -1194,138 +1211,100 @@ impl Device {
         }
 
         texture.format = ImageFormat::Invalid;
         texture.width = 0;
         texture.height = 0;
         texture.layer_count = 0;
     }
 
-    pub fn create_program(&mut self,
-                          base_filename: &str,
-                          include_filename: &str,
-                          descriptor: &VertexDescriptor) -> Result<Program, ShaderError> {
-        self.create_program_with_prefix(base_filename,
-                                        &[include_filename],
-                                        None,
-                                        descriptor)
-    }
-
     pub fn delete_program(&mut self, mut program: Program) {
         self.gl.delete_program(program.id);
         program.id = 0;
     }
 
-    pub fn create_program_with_prefix(&mut self,
-                                      base_filename: &str,
-                                      include_filenames: &[&str],
-                                      prefix: Option<String>,
-                                      descriptor: &VertexDescriptor) -> Result<Program, ShaderError> {
-        debug_assert!(self.inside_frame);
-
-        let pid = self.gl.create_program();
-
-        let mut vs_name = String::from(base_filename);
-        vs_name.push_str(".vs");
-        let mut fs_name = String::from(base_filename);
-        fs_name.push_str(".fs");
-
-        let mut include = format!("// Base shader: {}\n", base_filename);
-        for inc_filename in include_filenames {
-            let src = get_shader_source(inc_filename, &self.resource_override_path);
-            include.push_str(&src);
-        }
-
-        if let Some(shared_src) = get_optional_shader_source(base_filename, &self.resource_override_path) {
-            include.push_str(&shared_src);
-        }
-
-        let mut program = Program {
-            name: base_filename.to_owned(),
-            id: pid,
-            u_transform: -1,
-            u_device_pixel_ratio: -1,
-            vs_source: get_shader_source(&vs_name, &self.resource_override_path),
-            fs_source: get_shader_source(&fs_name, &self.resource_override_path),
-            prefix,
-            vs_id: None,
-            fs_id: None,
-        };
-
-        try!{ self.load_program(&mut program, include, descriptor) };
-
-        Ok(program)
-    }
-
-    fn load_program(&mut self,
-                    program: &mut Program,
-                    include: String,
-                    descriptor: &VertexDescriptor) -> Result<(), ShaderError> {
+    pub fn create_program(&mut self,
+                          base_filename: &str,
+                          features: &str,
+                          descriptor: &VertexDescriptor) -> Result<Program, ShaderError> {
         debug_assert!(self.inside_frame);
 
-        let mut vs_preamble = Vec::new();
-        let mut fs_preamble = Vec::new();
+        let gl_version_string = get_shader_version(&*self.gl);
+
+        let (vs_source, fs_source) = build_shader_strings(gl_version_string,
+                                                          features,
+                                                          base_filename,
+                                                          &self.resource_override_path);
+
+        // Compile the vertex shader
+        let vs_id = match Device::compile_shader(&*self.gl,
+                                                 base_filename,
+                                                 gl::VERTEX_SHADER,
+                                                 vs_source) {
+            Ok(vs_id) => vs_id,
+            Err(err) => return Err(err),
+        };
 
-        vs_preamble.push("#define WR_VERTEX_SHADER\n".to_owned());
-        fs_preamble.push("#define WR_FRAGMENT_SHADER\n".to_owned());
+        // Compiler the fragment shader
+        let fs_id = match Device::compile_shader(&*self.gl,
+                                                 base_filename,
+                                                 gl::FRAGMENT_SHADER,
+                                                 fs_source) {
+            Ok(fs_id) => fs_id,
+            Err(err) => {
+                self.gl.delete_shader(vs_id);
+                return Err(err);
+            }
+        };
 
-        if let Some(ref prefix) = program.prefix {
-            vs_preamble.push(prefix.clone());
-            fs_preamble.push(prefix.clone());
+        // Create program and attach shaders
+        let pid = self.gl.create_program();
+        self.gl.attach_shader(pid, vs_id);
+        self.gl.attach_shader(pid, fs_id);
+
+        // Bind vertex attributes
+        for (i, attr) in descriptor.vertex_attributes
+                                   .iter()
+                                   .chain(descriptor.instance_attributes.iter())
+                                   .enumerate() {
+            self.gl.bind_attrib_location(pid,
+                                         i as gl::GLuint,
+                                         attr.name);
         }
 
-        vs_preamble.push(self.shader_preamble.to_owned());
-        fs_preamble.push(self.shader_preamble.to_owned());
-
-        vs_preamble.push(include.clone());
-        fs_preamble.push(include);
+        // Link!
+        self.gl.link_program(pid);
 
-        // todo(gw): store shader ids so they can be freed!
-        let vs_id = try!{ Device::compile_shader(&*self.gl,
-                                                 &program.name,
-                                                 &program.vs_source,
-                                                 gl::VERTEX_SHADER,
-                                                 &vs_preamble) };
-        let fs_id = try!{ Device::compile_shader(&*self.gl,
-                                                 &program.name,
-                                                 &program.fs_source,
-                                                 gl::FRAGMENT_SHADER,
-                                                 &fs_preamble) };
+        // GL recommends detaching and deleting shaders once the link
+        // is complete (whether successful or not). This allows the driver
+        // to free any memory associated with the parsing and compilation.
+        self.gl.detach_shader(pid, vs_id);
+        self.gl.detach_shader(pid, fs_id);
+        self.gl.delete_shader(vs_id);
+        self.gl.delete_shader(fs_id);
 
-        if let Some(vs_id) = program.vs_id {
-            self.gl.detach_shader(program.id, vs_id);
+        if self.gl.get_program_iv(pid, gl::LINK_STATUS) == (0 as gl::GLint) {
+            let error_log = self.gl.get_program_info_log(pid);
+            println!("Failed to link shader program: {:?}\n{}", base_filename, error_log);
+            self.gl.delete_program(pid);
+            return Err(ShaderError::Link(base_filename.to_string(), error_log));
         }
 
-        if let Some(fs_id) = program.fs_id {
-            self.gl.detach_shader(program.id, fs_id);
-        }
+        let u_transform = self.gl.get_uniform_location(pid, "uTransform");
+        let u_device_pixel_ratio = self.gl.get_uniform_location(pid, "uDevicePixelRatio");
 
-        if let Err(bind_error) = program.attach_and_bind_shaders(vs_id, fs_id, descriptor, &*self.gl) {
-            if let (Some(vs_id), Some(fs_id)) = (program.vs_id, program.fs_id) {
-                try! { program.attach_and_bind_shaders(vs_id, fs_id, descriptor, &*self.gl) };
-            } else {
-               return Err(bind_error);
-            }
-        } else {
-            if let Some(vs_id) = program.vs_id {
-                self.gl.delete_shader(vs_id);
-            }
+        let program = Program {
+            id: pid,
+            u_transform,
+            u_device_pixel_ratio,
+        };
 
-            if let Some(fs_id) = program.fs_id {
-                self.gl.delete_shader(fs_id);
-            }
+        self.bind_program(&program);
 
-            program.vs_id = Some(vs_id);
-            program.fs_id = Some(fs_id);
-        }
-
-        program.u_transform = self.gl.get_uniform_location(program.id, "uTransform");
-        program.u_device_pixel_ratio = self.gl.get_uniform_location(program.id, "uDevicePixelRatio");
-
-        self.bind_program(program);
+        // TODO(gw): Abstract these to not be part of the device code!
         let u_color_0 = self.gl.get_uniform_location(program.id, "sColor0");
         if u_color_0 != -1 {
             self.gl.uniform_1i(u_color_0, TextureSampler::Color0 as i32);
         }
         let u_color1 = self.gl.get_uniform_location(program.id, "sColor1");
         if u_color1 != -1 {
             self.gl.uniform_1i(u_color1, TextureSampler::Color1 as i32);
         }
@@ -1356,17 +1335,17 @@ impl Device {
             self.gl.uniform_1i(u_tasks, TextureSampler::RenderTasks as i32);
         }
 
         let u_resource_cache = self.gl.get_uniform_location(program.id, "sResourceCache");
         if u_resource_cache != -1 {
             self.gl.uniform_1i(u_resource_cache, TextureSampler::ResourceCache as i32);
         }
 
-        Ok(())
+        Ok(program)
     }
 
     pub fn get_uniform_location(&self, program: &Program, name: &str) -> UniformLocation {
         UniformLocation(self.gl.get_uniform_location(program.id, name))
     }
 
     pub fn set_uniform_2f(&self, uniform: UniformLocation, x: f32, y: f32) {
         debug_assert!(self.inside_frame);
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -16,27 +16,27 @@ use internal_types::{FastHashMap, Hardwa
 use mask_cache::{ClipMode, ClipRegion, ClipSource, MaskCacheInfo};
 use plane_split::{BspSplitter, Polygon, Splitter};
 use prim_store::{GradientPrimitiveCpu, ImagePrimitiveCpu, LinePrimitive, PrimitiveKind};
 use prim_store::{PrimitiveContainer, PrimitiveIndex};
 use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu, TextRunMode};
 use prim_store::{RectanglePrimitive, TextRunPrimitiveCpu, TextShadowPrimitiveCpu};
 use prim_store::{BoxShadowPrimitiveCpu, TexelRect, YuvImagePrimitiveCpu};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
-use render_task::{AlphaRenderItem, ClipWorkItem, MaskCacheKey, RenderTask, RenderTaskIndex};
-use render_task::{RenderTaskId, RenderTaskLocation};
+use render_task::{AlphaRenderItem, ClipWorkItem, RenderTask};
+use render_task::{RenderTaskTree, RenderTaskId, RenderTaskLocation};
 use resource_cache::ResourceCache;
 use clip_scroll_node::{ClipInfo, ClipScrollNode, NodeType};
 use clip_scroll_tree::ClipScrollTree;
 use std::{cmp, f32, i32, mem, usize};
 use euclid::{SideOffsets2D, vec2, vec3};
 use tiling::{ContextIsolation, StackingContextIndex};
 use tiling::{ClipScrollGroup, ClipScrollGroupIndex, CompositeOps, DisplayListMap, Frame};
 use tiling::{PackedLayer, PackedLayerIndex, PrimitiveFlags, PrimitiveRunCmd, RenderPass};
-use tiling::{RenderTargetContext, RenderTaskCollection, ScrollbarPrimitive, StackingContext};
+use tiling::{RenderTargetContext, ScrollbarPrimitive, StackingContext};
 use util::{self, pack_as_float, subtract_rect, recycle_vec};
 use util::{MatrixHelpers, RectHelpers};
 
 #[derive(Debug, Clone)]
 struct ImageBorderSegment {
     geom_rect: LayerRect,
     sub_rect: TexelRect,
     stretch_size: LayerSize,
@@ -1271,25 +1271,27 @@ impl FrameBuilder {
     /// Compute the contribution (bounding rectangles, and resources) of layers and their
     /// primitives in screen space.
     fn build_layer_screen_rects_and_cull_layers(&mut self,
                                                 screen_rect: &DeviceIntRect,
                                                 clip_scroll_tree: &mut ClipScrollTree,
                                                 display_lists: &DisplayListMap,
                                                 resource_cache: &mut ResourceCache,
                                                 gpu_cache: &mut GpuCache,
+                                                render_tasks: &mut RenderTaskTree,
                                                 profile_counters: &mut FrameProfileCounters,
                                                 device_pixel_ratio: f32) {
         profile_scope!("cull");
         LayerRectCalculationAndCullingPass::create_and_run(self,
                                                            screen_rect,
                                                            clip_scroll_tree,
                                                            display_lists,
                                                            resource_cache,
                                                            gpu_cache,
+                                                           render_tasks,
                                                            profile_counters,
                                                            device_pixel_ratio);
     }
 
     fn update_scroll_bars(&mut self,
                           clip_scroll_tree: &ClipScrollTree,
                           gpu_cache: &mut GpuCache) {
         let distance_from_edge = 8.0;
@@ -1333,37 +1335,34 @@ impl FrameBuilder {
             //           has been broken for a long time, so I've removed it
             //           for now. We can re-add that code once the clips
             //           data is moved over to the GPU cache!
         }
     }
 
     fn build_render_task(&mut self,
                          clip_scroll_tree: &ClipScrollTree,
-                         gpu_cache: &mut GpuCache)
-                         -> (RenderTask, usize) {
+                         gpu_cache: &mut GpuCache,
+                         render_tasks: &mut RenderTaskTree)
+                         -> RenderTaskId {
         profile_scope!("build_render_task");
 
         let mut next_z = 0;
-        let mut next_task_index = RenderTaskIndex(0);
-
         let mut sc_stack: Vec<StackingContextIndex> = Vec::new();
-        let mut current_task = RenderTask::new_alpha_batch(next_task_index,
-                                                           DeviceIntPoint::zero(),
+        let mut current_task = RenderTask::new_alpha_batch(DeviceIntPoint::zero(),
                                                            RenderTaskLocation::Fixed);
-        next_task_index.0 += 1;
         // A stack of the alpha batcher tasks. We create them on the way down,
         // and then actually populate with items and dependencies on the way up.
         let mut alpha_task_stack = Vec::new();
         // A map of "preserve-3d" contexts. We are baking these into render targets
         // and only compositing once we are out of "preserve-3d" hierarchy.
         // The stacking contexts that fall into this category are
         //  - ones with `ContextIsolation::Items`, for their actual items to be backed
         //  - immediate children of `ContextIsolation::Items`
-        let mut preserve_3d_map: FastHashMap<StackingContextIndex, RenderTask> = FastHashMap::default();
+        let mut preserve_3d_map: FastHashMap<StackingContextIndex, RenderTaskId> = FastHashMap::default();
         // The plane splitter stack, using a simple BSP tree.
         let mut splitter_stack = Vec::new();
 
         debug!("build_render_task()");
 
         for cmd in &self.cmds {
             match *cmd {
                 PrimitiveRunCmd::PushStackingContext(stacking_context_index) => {
@@ -1378,44 +1377,41 @@ impl FrameBuilder {
 
                     debug!("\tpush {:?} {:?}", stacking_context_index, stacking_context.isolation);
 
                     let stacking_context_rect = &stacking_context.screen_bounds;
                     let composite_count = stacking_context.composite_ops.count();
 
                     if stacking_context.isolation == ContextIsolation::Full && composite_count == 0 {
                         alpha_task_stack.push(current_task);
-                        current_task = RenderTask::new_dynamic_alpha_batch(next_task_index, stacking_context_rect);
-                        next_task_index.0 += 1;
+                        current_task = RenderTask::new_dynamic_alpha_batch(stacking_context_rect);
                     }
 
                     if parent_isolation == Some(ContextIsolation::Items) ||
                        stacking_context.isolation == ContextIsolation::Items {
                         if parent_isolation != Some(ContextIsolation::Items) {
                             splitter_stack.push(BspSplitter::new());
                         }
                         alpha_task_stack.push(current_task);
-                        current_task = RenderTask::new_dynamic_alpha_batch(next_task_index, stacking_context_rect);
-                        next_task_index.0 += 1;
+                        current_task = RenderTask::new_dynamic_alpha_batch(stacking_context_rect);
                         //Note: technically, we shouldn't make a new alpha task for "preserve-3d" contexts
                         // that have no child items (only other stacking contexts). However, we don't know if
                         // there are any items at this time (in `PushStackingContex