Merge m-c to graphics
authorKartikaya Gupta <kgupta@mozilla.com>
Tue, 07 Mar 2017 10:30:42 -0500
changeset 397753 49be4b9eca3aba79de74cd2346ddc922e4ce7d97
parent 397752 48f2b03ac2fcc48b2c086458e68fe4c3946ce54e (current diff)
parent 396786 575e82f15c295cd56746fe26847ba95eae197954 (diff)
child 397754 6374644ff317f40c9ddb43483fc70c45a121021b
push id1490
push usermtabara@mozilla.com
push dateMon, 31 Jul 2017 14:08:16 +0000
treeherdermozilla-release@70e32e6bf15e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to graphics MozReview-Commit-ID: DbHqbxaKedC
browser/components/extensions/test/browser/browser_ext_url_overrides_all.js
browser/components/extensions/test/browser/browser_ext_url_overrides_home.js
browser/components/sessionstore/SessionHistory.jsm
dom/indexedDB/test/service_worker.js
dom/indexedDB/test/service_worker_client.html
dom/indexedDB/test/test_serviceworker.html
gfx/layers/ipc/CompositorBridgeParent.cpp
toolkit/components/extensions/test/mochitest/test_ext_contentscript.html
toolkit/components/extensions/test/mochitest/test_ext_i18n.html
toolkit/components/extensions/test/mochitest/test_ext_i18n_css.html
xpcom/build/nsXPCOMStrings.cpp
xpcom/string/nsXPCOMStrings.h
--- a/.hgtags
+++ b/.hgtags
@@ -126,8 +126,9 @@ 99137d6d4061f408ae0869122649d8bdf489cc30
 67c66c2878aed17ae3096d7db483ddbb2293c503 FIREFOX_AURORA_46_BASE
 68d3781deda0d4d58ec9877862830db89669b3a5 FIREFOX_AURORA_47_BASE
 1c6385ae1fe7e37d8f23f958ce14582f07af729e FIREFOX_AURORA_48_BASE
 d98f20c25feeac4dd7ebbd1c022957df1ef58af4 FIREFOX_AURORA_49_BASE
 465d150bc8be5bbf9f02a8607d4552b6a5e1697c FIREFOX_AURORA_50_BASE
 fc69febcbf6c0dcc4b3dfc7a346d8d348798a65f FIREFOX_AURORA_51_BASE
 1196bf3032e1bce1fb07a01fd9082a767426c5fb FIREFOX_AURORA_52_BASE
 f80dc9fc34680105b714a49b4704bb843f5f7004 FIREFOX_AURORA_53_BASE
+6583496f169cd8a13c531ed16e98e8bf313eda8e FIREFOX_AURORA_54_BASE
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 1343432 - jemalloc update needs a clobber
+Merge day clobber
\ No newline at end of file
--- a/accessible/atk/AccessibleWrap.cpp
+++ b/accessible/atk/AccessibleWrap.cpp
@@ -24,17 +24,16 @@
 #include "mozilla/a11y/Platform.h"
 #include "Relation.h"
 #include "RootAccessible.h"
 #include "States.h"
 #include "nsISimpleEnumerator.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Sprintf.h"
-#include "nsXPCOMStrings.h"
 #include "nsComponentManagerUtils.h"
 #include "nsIPersistentProperties2.h"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 
 MaiAtkObject::EAvailableAtkSignals MaiAtkObject::gAvailableAtkSignals =
   eUnknown;
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -164,16 +164,18 @@ pref("extensions.update.interval", 86400
 // Non-symmetric (not shared by extensions) extension-specific [update] preferences
 pref("extensions.dss.enabled", false);          // Dynamic Skin Switching
 pref("extensions.dss.switchPending", false);    // Non-dynamic switch pending after next
                                                 // restart.
 
 pref("extensions.{972ce4c6-7e08-4474-a285-3208198ce6fd}.name", "chrome://browser/locale/browser.properties");
 pref("extensions.{972ce4c6-7e08-4474-a285-3208198ce6fd}.description", "chrome://browser/locale/browser.properties");
 
+pref("extensions.webextensions.themes.icons.buttons", "back,forward,reload,stop,bookmark_star,bookmark_menu,downloads,home,app_menu,cut,copy,paste,new_window,new_private_window,save_page,print,history,full_screen,find,options,addons,developer,synced_tabs,open_file,sidebars,share_page,subscribe,text_encoding,email_link,forget,pocket");
+
 pref("lightweightThemes.update.enabled", true);
 pref("lightweightThemes.getMoreURL", "https://addons.mozilla.org/%LOCALE%/firefox/themes");
 pref("lightweightThemes.recommendedThemes", "[{\"id\":\"recommended-1\",\"homepageURL\":\"https://addons.mozilla.org/firefox/addon/a-web-browser-renaissance/\",\"headerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/1.header.jpg\",\"footerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/1.footer.jpg\",\"textcolor\":\"#000000\",\"accentcolor\":\"#f2d9b1\",\"iconURL\":\"resource:///chrome/browser/content/browser/defaultthemes/1.icon.jpg\",\"previewURL\":\"resource:///chrome/browser/content/browser/defaultthemes/1.preview.jpg\",\"author\":\"Sean.Martell\",\"version\":\"0\"},{\"id\":\"recommended-2\",\"homepageURL\":\"https://addons.mozilla.org/firefox/addon/space-fantasy/\",\"headerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/2.header.jpg\",\"footerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/2.footer.jpg\",\"textcolor\":\"#ffffff\",\"accentcolor\":\"#d9d9d9\",\"iconURL\":\"resource:///chrome/browser/content/browser/defaultthemes/2.icon.jpg\",\"previewURL\":\"resource:///chrome/browser/content/browser/defaultthemes/2.preview.jpg\",\"author\":\"fx5800p\",\"version\":\"1.0\"},{\"id\":\"recommended-4\",\"homepageURL\":\"https://addons.mozilla.org/firefox/addon/pastel-gradient/\",\"headerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/4.header.png\",\"footerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/4.footer.png\",\"textcolor\":\"#000000\",\"accentcolor\":\"#000000\",\"iconURL\":\"resource:///chrome/browser/content/browser/defaultthemes/4.icon.png\",\"previewURL\":\"resource:///chrome/browser/content/browser/defaultthemes/4.preview.png\",\"author\":\"darrinhenein\",\"version\":\"1.0\"}]");
 
 #if defined(MOZ_WIDEVINE_EME)
 pref("browser.eme.ui.enabled", true);
 #else
 pref("browser.eme.ui.enabled", false);
@@ -1242,43 +1244,54 @@ pref("security.cert_pinning.enforcement_
 pref("plain_text.wrap_long_lines", true);
 
 // If this turns true, Moz*Gesture events are not called stopPropagation()
 // before content.
 pref("dom.debug.propagate_gesture_events_through_content", false);
 
 // All the Geolocation preferences are here.
 //
-// The request URL of the GeoLocation backend.
-#ifdef RELEASE_OR_BETA
+
+// Geolocation preferences for the RELEASE channel.
+// Some of these prefs are specified even though they are redundant; they are
+// here for clarity and end-user experiments.
+#ifdef RELEASE
 pref("geo.wifi.uri", "https://www.googleapis.com/geolocation/v1/geolocate?key=%GOOGLE_API_KEY%");
-#else
-pref("geo.wifi.uri", "https://location.services.mozilla.com/v1/geolocate?key=%MOZILLA_API_KEY%");
-#endif
 
 #ifdef XP_MACOSX
-#ifdef RELEASE_OR_BETA
 pref("geo.provider.use_corelocation", false);
-#else
-pref("geo.provider.use_corelocation", true);
-#endif
 #endif
 
 #ifdef XP_WIN
 pref("geo.provider.ms-windows-location", false);
 #endif
 
 #ifdef MOZ_WIDGET_GTK
-#ifdef MOZ_GPSD
-#ifdef RELEASE_OR_BETA
 pref("geo.provider.use_gpsd", false);
+#endif
+
 #else
+
+// Geolocation preferences for Nightly/Aurora/Beta.
+pref("geo.wifi.uri", "https://location.services.mozilla.com/v1/geolocate?key=%MOZILLA_API_KEY%");
+
+#ifdef XP_MACOSX
+pref("geo.provider.use_corelocation", true);
+#endif
+
+// The native Windows location provider is only enabled in Nightly and likely to
+// be unstable. Set to false if things are really broken.
+#if defined(XP_WIN) && defined(NIGHTLY_BUILD)
+pref("geo.provider.ms-windows-location", true);
+#endif
+
+#if defined(MOZ_WIDGET_GTK) && defined(MOZ_GPSD)
 pref("geo.provider.use_gpsd", true);
 #endif
-#endif
+
 #endif
 
 // We keep allowing non-HTTPS geo requests on all the release
 // channels, for now.
 // TODO: default to false (or remove altogether) for #1072859.
 pref("geo.security.allowinsecure", true);
 
 // Necko IPC security checks only needed for app isolation for cookies/cache/etc:
--- a/browser/base/content/browser-ctrlTab.js
+++ b/browser/base/content/browser-ctrlTab.js
@@ -1,8 +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/. */
 
 /**
  * Tab previews utility, produces thumbnails
  */
 var tabPreviews = {
@@ -468,21 +469,25 @@ var ctrlTab = {
   },
 
   handleEvent: function ctrlTab_handleEvent(event) {
     switch (event.type) {
       case "SSWindowRestored":
         this._initRecentlyUsedTabs();
         break;
       case "TabAttrModified":
-        // tab attribute modified (e.g. label, busy, image, selected)
-        for (let i = this.previews.length - 1; i >= 0; i--) {
-          if (this.previews[i]._tab && this.previews[i]._tab == event.target) {
-            this.updatePreview(this.previews[i], event.target);
-            break;
+        // tab attribute modified (i.e. label, busy, image, selected)
+        // update preview only if tab attribute modified in the list
+        if (event.detail.changed.some(
+          (elem, ind, arr) => ["label", "busy", "image", "selected"].includes(elem))) {
+          for (let i = this.previews.length - 1; i >= 0; i--) {
+            if (this.previews[i]._tab && this.previews[i]._tab == event.target) {
+              this.updatePreview(this.previews[i], event.target);
+              break;
+            }
           }
         }
         break;
       case "TabSelect":
         this.detachTab(event.target);
         this.attachTab(event.target, 0);
         break;
       case "TabOpen":
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -1259,8 +1259,10 @@ toolbarpaletteitem[place="palette"][hidd
   box-shadow: 0 0 2px 2px rgba(255,0,0,0.4);
 }
 
 .dragfeedback-tab {
   -moz-appearance: none;
   opacity: 0.65;
   -moz-window-shadow: none;
 }
+
+%include theme-vars.inc.css
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -824,16 +824,18 @@ function gKeywordURIFixup({ target: brow
   // We get called irrespective of whether we did a keyword search, or
   // whether the original input would be vaguely interpretable as a URL,
   // so figure that out first.
   let alternativeURI = deserializeURI(fixupInfo.fixedURI);
   if (!fixupInfo.keywordProviderName || !alternativeURI || !alternativeURI.host) {
     return;
   }
 
+  let contentPrincipal = browser.contentPrincipal;
+
   // At this point we're still only just about to load this URI.
   // When the async DNS lookup comes back, we may be in any of these states:
   // 1) still on the previous URI, waiting for the preferredURI (keyword
   //    search) to respond;
   // 2) at the keyword search URI (preferredURI)
   // 3) at some other page because the user stopped navigation.
   // We keep track of the currentURI to detect case (1) in the DNS lookup
   // callback.
@@ -926,17 +928,18 @@ function gKeywordURIFixup({ target: brow
     let notification =
       notificationBox.appendNotification(message, "keyword-uri-fixup", null,
                                          notificationBox.PRIORITY_INFO_HIGH,
                                          buttons);
     notification.persistence = 1;
   };
 
   try {
-    gDNSService.asyncResolve(hostName, 0, onLookupComplete, Services.tm.mainThread);
+    gDNSService.asyncResolve(hostName, 0, onLookupComplete, Services.tm.mainThread,
+                             contentPrincipal.originAttributes);
   } catch (ex) {
     // Do nothing if the URL is invalid (we don't want to show a notification in that case).
     if (ex.result != Cr.NS_ERROR_UNKNOWN_HOST) {
       // ... otherwise, report:
       Cu.reportError(ex);
     }
   }
 }
@@ -6716,16 +6719,22 @@ var gIdentityHandler = {
    */
   _sslStatus: null,
 
   /**
    * Bitmask provided by nsIWebProgressListener.onSecurityChange.
    */
   _state: 0,
 
+  /**
+   * This flag gets set if the identity popup was opened by a keypress,
+   * to be able to focus it on the popupshown event.
+   */
+  _popupTriggeredByKeyboard: false,
+
   get _isBroken() {
     return this._state & Ci.nsIWebProgressListener.STATE_IS_BROKEN;
   },
 
   get _isSecure() {
     // If a <browser> is included within a chrome document, then this._state
     // will refer to the security state for the <browser> and not the top level
     // document. In this case, don't upgrade the security state in the UI
@@ -6850,16 +6859,20 @@ var gIdentityHandler = {
   get _permissionEmptyHint() {
     delete this._permissionEmptyHint;
     return this._permissionEmptyHint = document.getElementById("identity-popup-permission-empty-hint");
   },
   get _permissionReloadHint() {
     delete this._permissionReloadHint;
     return this._permissionReloadHint = document.getElementById("identity-popup-permission-reload-hint");
   },
+  get _popupExpander() {
+    delete this._popupExpander;
+    return this._popupExpander = document.getElementById("identity-popup-security-expander");
+  },
   get _permissionAnchors() {
     delete this._permissionAnchors;
     let permissionAnchors = {};
     for (let anchor of document.getElementById("blocked-permissions-container").children) {
       permissionAnchors[anchor.getAttribute("data-permission-id")] = anchor;
     }
     return this._permissionAnchors = permissionAnchors;
   },
@@ -6871,20 +6884,28 @@ var gIdentityHandler = {
   handleMoreInfoClick(event) {
     displaySecurityInfo();
     event.stopPropagation();
     this._identityPopup.hidePopup();
   },
 
   toggleSubView(name, anchor) {
     let view = this._identityPopupMultiView;
+    let id = `identity-popup-${name}View`;
+    let subView = document.getElementById(id);
+
+    // Listen for the subview showing or hiding to change
+    // the tooltip on the expander button.
+    subView.addEventListener("ViewShowing", this);
+    subView.addEventListener("ViewHiding", this);
+
     if (view.showingSubView) {
       view.showMainView();
     } else {
-      view.showSubView(`identity-popup-${name}View`, anchor);
+      view.showSubView(id, anchor);
     }
 
     // If an element is focused that's not the anchor, clear the focus.
     // Elements of hidden views have -moz-user-focus:ignore but setting that
     // per CSS selector doesn't blur a focused element in those hidden views.
     if (Services.focus.focusedElement != anchor) {
       Services.focus.clearFocus(window);
     }
@@ -7183,16 +7204,19 @@ var gIdentityHandler = {
   refreshIdentityPopup() {
     // Update "Learn More" for Mixed Content Blocking and Insecure Login Forms.
     let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
     this._identityPopupMixedContentLearnMore
         .setAttribute("href", baseURL + "mixed-content");
     this._identityPopupInsecureLoginFormsLearnMore
         .setAttribute("href", baseURL + "insecure-password");
 
+    // The expander switches its tooltip when toggled, change it to the default.
+    this._popupExpander.tooltipText = gNavigatorBundle.getString("identity.showDetails.tooltip");
+
     // Determine connection security information.
     let connection = "not-secure";
     if (this._isSecureInternalUI) {
       connection = "chrome";
     } else if (this._isURILoadedFromFile) {
       connection = "file";
     } else if (this._isEV) {
       connection = "secure-ev";
@@ -7356,16 +7380,18 @@ var gIdentityHandler = {
       return; // Left click, space or enter only
     }
 
     // Don't allow left click, space or enter if the location has been modified.
     if (gURLBar.getAttribute("pageproxystate") != "valid") {
       return;
     }
 
+    this._popupTriggeredByKeyboard = event.type == "keypress";
+
     // Make sure that the display:none style we set in xul is removed now that
     // the popup is actually needed
     this._identityPopup.hidden = false;
 
     // Remove the reload hint that we show after a user has cleared a permission.
     this._permissionReloadHint.setAttribute("hidden", "true");
 
     // Update the popup strings
@@ -7375,33 +7401,45 @@ var gIdentityHandler = {
     this._identityBox.setAttribute("open", "true");
 
     // Now open the popup, anchored off the primary chrome element
     this._identityPopup.openPopup(this._identityIcon, "bottomcenter topleft");
   },
 
   onPopupShown(event) {
     if (event.target == this._identityPopup) {
-      // Move focus to the next available element in the identity popup.
-      // This is required by role=alertdialog and fixes an issue where
-      // an already open panel would steal focus from the identity popup.
-      document.commandDispatcher.advanceFocusIntoSubtree(this._identityPopup);
+      if (this._popupTriggeredByKeyboard) {
+        // Move focus to the next available element in the identity popup.
+        // This is required by role=alertdialog and fixes an issue where
+        // an already open panel would steal focus from the identity popup.
+        document.commandDispatcher.advanceFocusIntoSubtree(this._identityPopup);
+      }
 
       window.addEventListener("focus", this, true);
     }
   },
 
   onPopupHidden(event) {
     if (event.target == this._identityPopup) {
       window.removeEventListener("focus", this, true);
       this._identityBox.removeAttribute("open");
     }
   },
 
   handleEvent(event) {
+    // If the subview is shown or hidden, change the tooltip on the expander button.
+    if (event.type == "ViewShowing") {
+      this._popupExpander.tooltipText = gNavigatorBundle.getString("identity.hideDetails.tooltip");
+      return;
+    }
+    if (event.type == "ViewHiding") {
+      this._popupExpander.tooltipText = gNavigatorBundle.getString("identity.showDetails.tooltip");
+      return;
+    }
+
     let elem = document.activeElement;
     let position = elem.compareDocumentPosition(this._identityPopup);
 
     if (!(position & (Node.DOCUMENT_POSITION_CONTAINS |
                       Node.DOCUMENT_POSITION_CONTAINED_BY)) &&
         !this._identityPopup.hasAttribute("noautohide")) {
       // Hide the panel when focusing an element that is
       // neither an ancestor nor descendant unless the panel has
--- a/browser/base/content/tabbrowser.css
+++ b/browser/base/content/tabbrowser.css
@@ -92,19 +92,24 @@ tabpanels {
  * memory that to-be-restored tabs would otherwise consume simply by setting
  * their browsers to 'display: none' as that will prevent them from having to
  * create a presentation and the like.
  */
 browser[pending] {
   display: none;
 }
 
+browser[pendingtabchild],
 browser[pendingpaint] {
   opacity: 0;
 }
 
+tabbrowser[pendingtabchild] {
+  background-color: #ffffff !important;
+}
+
 tabbrowser[pendingpaint] {
   background-image: url(chrome://browser/skin/tabbrowser/pendingpaint.png);
   background-repeat: no-repeat;
   background-position: center center;
   background-color: #f9f9f9 !important;
   background-size: 30px;
 }
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -3718,25 +3718,32 @@
 
             // We show this tab in case the requestedTab hasn't loaded yet.
             lastVisibleTab: this.selectedTab,
 
             // Auxilliary state variables:
 
             visibleTab: this.selectedTab,   // Tab that's on screen.
             spinnerTab: null,               // Tab showing a spinner.
+            blankTab: null,                 // Tab showing blank.
             originalTab: this.selectedTab,  // Tab that we started on.
 
             tabbrowser: this,  // Reference to gBrowser.
             loadTimer: null,   // TAB_SWITCH_TIMEOUT nsITimer instance.
             unloadTimer: null, // UNLOAD_DELAY nsITimer instance.
 
             // Map from tabs to STATE_* (below).
             tabState: new Map(),
 
+            // Holds a collection of <xul:browser>'s for this tabbrowser
+            // that we cannot force paint since their TabChild's haven't
+            // been constructed yet. Instead, we show blank tabs for them
+            // when attempting to switch to them.
+            pendingTabChild: new WeakSet(),
+
             // True if we're in the midst of switching tabs.
             switchInProgress: false,
 
             // Keep an exact list of content processes (tabParent) in which
             // we're actively suppressing the display port. This gives a robust
             // way to make sure we don't forget to un-suppress.
             activeSuppressDisplayport: new Set(),
 
@@ -3822,16 +3829,17 @@
 
               window.addEventListener("MozAfterPaint", this);
               window.addEventListener("MozLayerTreeReady", this);
               window.addEventListener("MozLayerTreeCleared", this);
               window.addEventListener("TabRemotenessChange", this);
               window.addEventListener("sizemodechange", this);
               window.addEventListener("SwapDocShells", this, true);
               window.addEventListener("EndSwapDocShells", this, true);
+              window.addEventListener("MozTabChildNotReady", this, true);
               if (!this.minimized) {
                 this.setTabState(this.requestedTab, this.STATE_LOADED);
               }
             },
 
             destroy() {
               if (this.unloadTimer) {
                 this.clearTimer(this.unloadTimer);
@@ -3844,31 +3852,33 @@
 
               window.removeEventListener("MozAfterPaint", this);
               window.removeEventListener("MozLayerTreeReady", this);
               window.removeEventListener("MozLayerTreeCleared", this);
               window.removeEventListener("TabRemotenessChange", this);
               window.removeEventListener("sizemodechange", this);
               window.removeEventListener("SwapDocShells", this, true);
               window.removeEventListener("EndSwapDocShells", this, true);
+              window.removeEventListener("MozTabChildNotReady", this, true);
 
               this.tabbrowser._switcher = null;
 
               this.activeSuppressDisplayport.forEach(function(tabParent) {
                 tabParent.suppressDisplayport(false);
               });
               this.activeSuppressDisplayport.clear();
             },
 
             finish() {
               this.log("FINISH");
 
               this.assert(this.tabbrowser._switcher);
               this.assert(this.tabbrowser._switcher === this);
               this.assert(!this.spinnerTab);
+              this.assert(!this.blankTab);
               this.assert(!this.loadTimer);
               this.assert(!this.loadingTab);
               this.assert(this.lastVisibleTab === this.requestedTab);
               this.assert(this.minimized || this.getTabState(this.requestedTab) == this.STATE_LOADED);
 
               this.destroy();
 
               let toBrowser = this.requestedTab.linkedBrowser;
@@ -3889,30 +3899,55 @@
                 cancelable: true
               });
               this.tabbrowser.dispatchEvent(event);
             },
 
             // This function is called after all the main state changes to
             // make sure we display the right tab.
             updateDisplay() {
+              let requestedTabState = this.getTabState(this.requestedTab);
+
+              let shouldBeBlank =
+                this.pendingTabChild.has(this.requestedTab.linkedBrowser) &&
+                requestedTabState == this.STATE_LOADING;
+
               // Figure out which tab we actually want visible right now.
               let showTab = null;
-              if (this.getTabState(this.requestedTab) != this.STATE_LOADED &&
-                  this.lastVisibleTab && this.loadTimer) {
+              if (requestedTabState != this.STATE_LOADED &&
+                  this.lastVisibleTab && this.loadTimer && !shouldBeBlank) {
                 // If we can't show the requestedTab, and lastVisibleTab is
                 // available, show it.
                 showTab = this.lastVisibleTab;
               } else {
-                // Show the requested tab. If it's not available, we'll show the spinner.
+                // Show the requested tab. If it's not available, we'll show the spinner or a blank tab.
                 showTab = this.requestedTab;
               }
 
+              // First, let's deal with blank tabs, which we show instead
+              // of the spinner when the tab is not currently set up
+              // properly in the content process.
+              if (!shouldBeBlank && this.blankTab) {
+                this.tabbrowser.removeAttribute("pendingtabchild");
+                this.blankTab.linkedBrowser.removeAttribute("pendingtabchild");
+                this.blankTab = null;
+              } else if (shouldBeBlank && this.blankTab !== showTab) {
+                if (this.blankTab) {
+                  this.blankTab.linkedBrowser.removeAttribute("pendingtabchild");
+                }
+                this.blankTab = showTab;
+                this.tabbrowser.setAttribute("pendingtabchild", "true");
+                this.blankTab.linkedBrowser.setAttribute("pendingtabchild", "true");
+              }
+
               // Show or hide the spinner as needed.
-              let needSpinner = this.getTabState(showTab) != this.STATE_LOADED && !this.minimized;
+              let needSpinner = this.getTabState(showTab) != this.STATE_LOADED &&
+                                !this.minimized &&
+                                !shouldBeBlank;
+
               if (!needSpinner && this.spinnerTab) {
                 this.spinnerHidden();
                 this.tabbrowser.removeAttribute("pendingpaint");
                 this.spinnerTab.linkedBrowser.removeAttribute("pendingpaint");
                 this.spinnerTab = null;
               } else if (needSpinner && this.spinnerTab !== showTab) {
                 if (this.spinnerTab) {
                   this.spinnerTab.linkedBrowser.removeAttribute("pendingpaint");
@@ -3987,16 +4022,19 @@
                 if (!tab.linkedBrowser) {
                   this.tabState.delete(tab);
                 }
               }
 
               if (this.lastVisibleTab && !this.lastVisibleTab.linkedBrowser) {
                 this.lastVisibleTab = null;
               }
+              if (this.blankTab && !this.blankTab.linkedBrowser) {
+                this.blankTab = null;
+              }
               if (this.spinnerTab && !this.spinnerTab.linkedBrowser) {
                 this.spinnerHidden();
                 this.spinnerTab = null;
               }
               if (this.loadingTab && !this.loadingTab.linkedBrowser) {
                 this.loadingTab = null;
                 this.clearTimer(this.loadTimer);
                 this.loadTimer = null;
@@ -4099,18 +4137,19 @@
               this.preActions();
               this.loadTimer = null;
               this.loadingTab = null;
               this.postActions();
             },
 
             // Fires when the layers become available for a tab.
             onLayersReady(browser) {
+              this.pendingTabChild.delete(browser);
               let tab = this.tabbrowser.getTabForBrowser(browser);
-              this.logState(`onLayersReady(${tab._tPos})`);
+              this.logState(`onLayersReady(${tab._tPos}, ${browser.isRemoteBrowser})`);
 
               this.assert(this.getTabState(tab) == this.STATE_LOADING ||
                           this.getTabState(tab) == this.STATE_LOADED);
               this.setTabState(tab, this.STATE_LOADED);
 
               this.maybeFinishTabSwitch();
 
               if (this.loadingTab === tab) {
@@ -4125,16 +4164,17 @@
             // around.
             onPaint() {
               this.maybeVisibleTabs.clear();
               this.maybeFinishTabSwitch();
             },
 
             // Called when we're done clearing the layers for a tab.
             onLayersCleared(browser) {
+              this.pendingTabChild.delete(browser);
               let tab = this.tabbrowser.getTabForBrowser(browser);
               if (tab) {
                 this.logState(`onLayersCleared(${tab._tPos})`);
                 this.assert(this.getTabState(tab) == this.STATE_UNLOADING ||
                             this.getTabState(tab) == this.STATE_UNLOADED);
                 this.setTabState(tab, this.STATE_UNLOADED);
               }
             },
@@ -4145,16 +4185,25 @@
             onRemotenessChange(tab) {
               this.logState(`onRemotenessChange(${tab._tPos}, ${tab.linkedBrowser.isRemoteBrowser})`);
               if (!tab.linkedBrowser.isRemoteBrowser) {
                 if (this.getTabState(tab) == this.STATE_LOADING) {
                   this.onLayersReady(tab.linkedBrowser);
                 } else if (this.getTabState(tab) == this.STATE_UNLOADING) {
                   this.onLayersCleared(tab.linkedBrowser);
                 }
+              } else if (this.getTabState(tab) == this.STATE_LOADED) {
+                // A tab just changed from non-remote to remote, which means
+                // that it's gone back into the STATE_LOADING state until
+                // it sends up a layer tree. We also add the browser to
+                // the pendingTabChild set since this browser is unlikely
+                // to have its TabChild set up right away, and we want to
+                // make it appear "blank" instead of showing a spinner for it.
+                this.pendingTabChild.add(tab.linkedBrowser);
+                this.setTabState(tab, this.STATE_LOADING);
               }
             },
 
             // Called when a tab has been removed, and the browser node is
             // about to be removed from the DOM.
             onTabRemoved(tab) {
               if (this.lastVisibleTab == tab) {
                 // The browser that was being presented to the user is
@@ -4191,29 +4240,41 @@
 
             onSwapDocShells(ourBrowser, otherBrowser) {
               // This event fires before the swap. ourBrowser is from
               // our window. We save the state of otherBrowser since ourBrowser
               // needs to take on that state at the end of the swap.
 
               let otherTabbrowser = otherBrowser.ownerGlobal.gBrowser;
               let otherState;
+              let pendingTabChild = false;
               if (otherTabbrowser && otherTabbrowser._switcher) {
                 let otherTab = otherTabbrowser.getTabForBrowser(otherBrowser);
-                otherState = otherTabbrowser._switcher.getTabState(otherTab);
+                let otherSwitcher = otherTabbrowser._switcher;
+                otherState = otherSwitcher.getTabState(otherTab);
+                pendingTabChild = otherSwitcher.pendingTabChild.has(otherBrowser);
+
+                if (pendingTabChild) {
+                  this.assert(otherState == this.STATE_LOADING);
+                }
+
+                otherSwitcher.pendingTabChild.delete(otherBrowser);
               } else {
                 otherState = (otherBrowser.docShellIsActive
                               ? this.STATE_LOADED
                               : this.STATE_UNLOADED);
               }
 
               if (!this.swapMap) {
                 this.swapMap = new WeakMap();
               }
-              this.swapMap.set(otherBrowser, otherState);
+              this.swapMap.set(otherBrowser, {
+                state: otherState,
+                pendingTabChild,
+              });
             },
 
             onEndSwapDocShells(ourBrowser, otherBrowser) {
               // The swap has happened. We reset the loadingTab in
               // case it has been swapped. We also set ourBrowser's state
               // to whatever otherBrowser's state was before the swap.
 
               if (this.loadTimer) {
@@ -4222,36 +4283,63 @@
                 // ready yet. Typically it will already be ready
                 // though. If it's not, we're probably in a new window,
                 // in which case we have no other tabs to display anyway.
                 this.clearTimer(this.loadTimer);
                 this.loadTimer = null;
               }
               this.loadingTab = null;
 
-              let otherState = this.swapMap.get(otherBrowser);
+              let { state: otherState, pendingTabChild } =
+                this.swapMap.get(otherBrowser);
+
               this.swapMap.delete(otherBrowser);
 
               let ourTab = this.tabbrowser.getTabForBrowser(ourBrowser);
               if (ourTab) {
                 this.setTabStateNoAction(ourTab, otherState);
+                if (pendingTabChild) {
+                  this.pendingTabChild.add(ourTab.linkedBrowser);
+                }
               }
             },
 
             shouldActivateDocShell(browser) {
               let tab = this.tabbrowser.getTabForBrowser(browser);
               let state = this.getTabState(tab);
               return state == this.STATE_LOADING || state == this.STATE_LOADED;
             },
 
             activateBrowserForPrintPreview(browser) {
               let tab = this.tabbrowser.getTabForBrowser(browser);
               this.setTabState(tab, this.STATE_LOADING);
             },
 
+            // The tab for this browser isn't currently set
+            // up in the content process, so we have no chance
+            // of painting it right away. We'll paint a blank
+            // tab instead.
+            onTabChildNotReady(browser) {
+              this.assert(browser.isRemoteBrowser);
+
+              let tab = this.tabbrowser.getTabForBrowser(browser);
+
+              this.assert(this.getTabState(tab) == this.STATE_LOADING);
+
+              this.logState(`onTabChildNotReady(${tab._tPos})`);
+              this.pendingTabChild.add(browser);
+              this.maybeFinishTabSwitch();
+
+              if (this.loadingTab === tab) {
+                this.clearTimer(this.loadTimer);
+                this.loadTimer = null;
+                this.loadingTab = null;
+              }
+            },
+
             // Called when the user asks to switch to a given tab.
             requestTab(tab) {
               if (tab === this.requestedTab) {
                 return;
               }
 
               this.logState("requestTab " + this.tinfo(tab));
               this.startTabSwitch();
@@ -4297,16 +4385,18 @@
               } else if (event.type == "TabRemotenessChange") {
                 this.onRemotenessChange(event.target);
               } else if (event.type == "sizemodechange") {
                 this.onSizeModeChange();
               } else if (event.type == "SwapDocShells") {
                 this.onSwapDocShells(event.originalTarget, event.detail);
               } else if (event.type == "EndSwapDocShells") {
                 this.onEndSwapDocShells(event.originalTarget, event.detail);
+              } else if (event.type == "MozTabChildNotReady") {
+                this.onTabChildNotReady(event.originalTarget);
               }
 
               this.postActions();
               this._processing = false;
             },
 
             /*
              * Telemetry and Profiler related helpers for recording tab switch
@@ -4323,17 +4413,18 @@
             /**
              * Something has occurred that might mean that we've completed
              * the tab switch (layers are ready, paints are done, spinners
              * are hidden). This checks to make sure all conditions are
              * satisfied, and then records the tab switch as finished.
              */
             maybeFinishTabSwitch() {
               if (this.switchInProgress && this.requestedTab &&
-                  this.getTabState(this.requestedTab) == this.STATE_LOADED) {
+                  (this.getTabState(this.requestedTab) == this.STATE_LOADED ||
+                   this.requestedTab === this.blankTab)) {
                 // After this point the tab has switched from the content thread's point of view.
                 // The changes will be visible after the next refresh driver tick + composite.
                 let time = TelemetryStopwatch.timeElapsed("FX_TAB_SWITCH_TOTAL_E10S_MS", window);
                 if (time != -1) {
                   TelemetryStopwatch.finish("FX_TAB_SWITCH_TOTAL_E10S_MS", window);
                   this.log("DEBUG: tab switch time = " + time);
                   this.addMarker("AsyncTabSwitch:Finish");
                 }
@@ -4414,16 +4505,17 @@
               for (let i = 0; i < this.tabbrowser.tabs.length; i++) {
                 let tab = this.tabbrowser.tabs[i];
                 let state = this.getTabState(tab);
 
                 accum += i + ":";
                 if (tab === this.lastVisibleTab) accum += "V";
                 if (tab === this.loadingTab) accum += "L";
                 if (tab === this.requestedTab) accum += "R";
+                if (tab === this.blankTab) accum += "B";
                 if (state == this.STATE_LOADED) accum += "(+)";
                 if (state == this.STATE_LOADING) accum += "(+?)";
                 if (state == this.STATE_UNLOADED) accum += "(-)";
                 if (state == this.STATE_UNLOADING) accum += "(-?)";
                 accum += " ";
               }
               if (this._useDumpForLogging) {
                 dump(accum + "\n");
--- a/browser/base/content/test/siteIdentity/browser.ini
+++ b/browser/base/content/test/siteIdentity/browser.ini
@@ -42,16 +42,17 @@ support-files =
 [browser_csp_block_all_mixedcontent.js]
 tags = mcb
 support-files =
   file_csp_block_all_mixedcontent.html
   file_csp_block_all_mixedcontent.js
 [browser_identity_UI.js]
 [browser_identityBlock_focus.js]
 support-files = ../general/permissions.html
+[browser_identityPopup_focus.js]
 [browser_insecureLoginForms.js]
 support-files =
   insecure_opener.html
   !/toolkit/components/passwordmgr/test/browser/form_basic.html
   !/toolkit/components/passwordmgr/test/browser/insecure_test.html
   !/toolkit/components/passwordmgr/test/browser/insecure_test_subframe.html
 [browser_mcb_redirect.js]
 tags = mcb
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/siteIdentity/browser_identityPopup_focus.js
@@ -0,0 +1,27 @@
+/* Tests the focus behavior of the identity popup. */
+
+// Access the identity popup via mouseclick. Focus should not be moved inside.
+add_task(function* testIdentityPopupFocusClick() {
+  yield SpecialPowers.pushPrefEnv({"set": [["accessibility.tabfocus", 7]]});
+  yield BrowserTestUtils.withNewTab("https://example.com", function*() {
+    let shown = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popupshown");
+    EventUtils.synthesizeMouseAtCenter(gIdentityHandler._identityBox, {});
+    yield shown;
+    isnot(Services.focus.focusedElement, document.getElementById("identity-popup-security-expander"));
+  });
+});
+
+// Access the identity popup via keyboard. Focus should be moved inside.
+add_task(function* testIdentityPopupFocusKeyboard() {
+  yield SpecialPowers.pushPrefEnv({"set": [["accessibility.tabfocus", 7]]});
+  yield BrowserTestUtils.withNewTab("https://example.com", function*() {
+    let focused = BrowserTestUtils.waitForEvent(gIdentityHandler._identityBox, "focus");
+    gIdentityHandler._identityBox.focus();
+    yield focused;
+    let shown = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popupshown");
+    EventUtils.synthesizeKey(" ", {});
+    yield shown;
+    is(Services.focus.focusedElement, document.getElementById("identity-popup-security-expander"));
+  });
+});
+
--- a/browser/base/content/test/tabs/browser_tabSpinnerProbe.js
+++ b/browser/base/content/test/tabs/browser_tabSpinnerProbe.js
@@ -80,14 +80,22 @@ function* testProbe(aProbe) {
   yield BrowserTestUtils.removeTab(tab2);
   yield BrowserTestUtils.removeTab(tab1);
   ok(sum(snapshot.counts) > 0,
    `Spinner probe should now have a value in some bucket`);
 }
 
 add_task(function* setup() {
   yield SpecialPowers.pushPrefEnv({
-    set: [["dom.ipc.processCount", 1]]
+    set: [
+      ["dom.ipc.processCount", 1],
+      // We can interrupt JS to paint now, which is great for
+      // users, but bad for testing spinners. We temporarily
+      // disable that feature for this test so that we can
+      // easily get ourselves into a predictable tab spinner
+      // state.
+      ["browser.tabs.remote.force-paint", false],
+    ]
   });
 });
 
 add_task(testProbe.bind(null, "FX_TAB_SWITCH_SPINNER_VISIBLE_MS"));
 add_task(testProbe.bind(null, "FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS"));
--- a/browser/base/content/test/webextensions/browser_extension_sideloading.js
+++ b/browser/base/content/test/webextensions/browser_extension_sideloading.js
@@ -167,16 +167,18 @@ add_task(function* () {
   yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
 
   registerCleanupFunction(function*() {
     // Return to about:blank when we're done
     gBrowser.selectedBrowser.loadURI("about:blank");
     yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
   });
 
+  hookExtensionsTelemetry();
+
   let changePromise = new Promise(resolve => {
     ExtensionsUI.on("change", function listener() {
       ExtensionsUI.off("change", listener);
       resolve();
     });
   });
   ExtensionsUI._checkForSideloaded();
   yield changePromise;
@@ -322,12 +324,15 @@ add_task(function* () {
   disablePromise = promiseSetDisabled(mock4);
   panel.button.click();
   value = yield disablePromise;
   is(value, false, "userDisabled should be set on addon 4");
 
   addon4 = yield AddonManager.getAddonByID(ID4);
   is(addon4.userDisabled, false, "Addon 4 should be enabled");
 
+  // We should have recorded 1 cancelled followed by 3 accepted sideloads.
+  expectTelemetry(["sideloadRejected", "sideloadAccepted", "sideloadAccepted", "sideloadAccepted"]);
+
   isnot(menuButton.getAttribute("badge-status"), "addon-alert", "Should no longer have addon alert badge");
 
   yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
--- a/browser/base/content/test/webextensions/browser_extension_update_background.js
+++ b/browser/base/content/test/webextensions/browser_extension_update_background.js
@@ -47,16 +47,18 @@ add_task(function* setup() {
 
   registerCleanupFunction(function*() {
     // Return to about:blank when we're done
     gBrowser.selectedBrowser.loadURI("about:blank");
     yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
   });
 });
 
+hookExtensionsTelemetry();
+
 // Helper function to test background updates.
 function* backgroundUpdateTest(url, id, checkIconFn) {
   yield SpecialPowers.pushPrefEnv({set: [
     // Turn on background updates
     ["extensions.update.enabled", true],
 
     // Point updates to the local mochitest server
     ["extensions.update.background.url", `${BASE}/browser_webext_update.json`],
@@ -158,16 +160,19 @@ function* backgroundUpdateTest(url, id, 
 
   addon = yield updatePromise;
   is(addon.version, "2.0", "Should have upgraded to the new version");
 
   yield BrowserTestUtils.removeTab(tab);
 
   is(getBadgeStatus(), "", "Addon alert badge should be gone");
 
+  // Should have recorded 1 canceled followed by 1 accepted update.
+  expectTelemetry(["updateRejected", "updateAccepted"]);
+
   addon.uninstall();
   yield SpecialPowers.popPrefEnv();
 }
 
 function checkDefaultIcon(icon) {
   is(icon, "chrome://mozapps/skin/extensions/extensionGeneric.svg",
      "Popup has the default extension icon");
 }
--- a/browser/base/content/test/webextensions/browser_permissions_addons_search.js
+++ b/browser/base/content/test/webextensions/browser_permissions_addons_search.js
@@ -34,9 +34,9 @@ async function installSearch(filename) {
   // abracadabara XBL
   item.clientTop;
 
   let install = win.document.getAnonymousElementByAttribute(item, "anonid", "install-status");
   let button = win.document.getAnonymousElementByAttribute(install, "anonid", "install-remote-btn");
   EventUtils.synthesizeMouseAtCenter(button, {}, win);
 }
 
-add_task(() => testInstallMethod(installSearch));
+add_task(() => testInstallMethod(installSearch, "installAmo"));
--- a/browser/base/content/test/webextensions/browser_permissions_installTrigger.js
+++ b/browser/base/content/test/webextensions/browser_permissions_installTrigger.js
@@ -6,9 +6,9 @@ async function installTrigger(filename) 
   gBrowser.selectedBrowser.loadURI(INSTALL_PAGE);
   await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
 
   ContentTask.spawn(gBrowser.selectedBrowser, `${BASE}/${filename}`, function*(url) {
     content.wrappedJSObject.installTrigger(url);
   });
 }
 
-add_task(() => testInstallMethod(installTrigger));
+add_task(() => testInstallMethod(installTrigger, "installAmo"));
--- a/browser/base/content/test/webextensions/browser_permissions_local_file.js
+++ b/browser/base/content/test/webextensions/browser_permissions_local_file.js
@@ -15,9 +15,9 @@ async function installFile(filename) {
 
   await BrowserOpenAddonsMgr("addons://list/extension");
   let contentWin = gBrowser.selectedTab.linkedBrowser.contentWindow;
 
   // Do the install...
   contentWin.gViewController.doCommand("cmd_installFromFile");
 }
 
-add_task(() => testInstallMethod(installFile));
+add_task(() => testInstallMethod(installFile, "installLocal"));
--- a/browser/base/content/test/webextensions/browser_permissions_mozAddonManager.js
+++ b/browser/base/content/test/webextensions/browser_permissions_mozAddonManager.js
@@ -6,9 +6,9 @@ async function installMozAM(filename) {
   gBrowser.selectedBrowser.loadURI(INSTALL_PAGE);
   await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
 
   await ContentTask.spawn(gBrowser.selectedBrowser, `${BASE}/${filename}`, function*(url) {
     yield content.wrappedJSObject.installMozAM(url);
   });
 }
 
-add_task(() => testInstallMethod(installMozAM));
+add_task(() => testInstallMethod(installMozAM, "installAmo"));
--- a/browser/base/content/test/webextensions/head.js
+++ b/browser/base/content/test/webextensions/head.js
@@ -1,12 +1,14 @@
 
 const BASE = getRootDirectory(gTestPath)
   .replace("chrome://mochitests/content/", "https://example.com/");
 
+Cu.import("resource:///modules/ExtensionsUI.jsm");
+
 /**
  * Wait for the given PopupNotification to display
  *
  * @param {string} name
  *        The name of the notification to wait for.
  *
  * @returns {Promise}
  *          Resolves with the notification window.
@@ -196,32 +198,39 @@ function checkNotification(panel, checkI
 /**
  * Test that install-time permission prompts work for a given
  * installation method.
  *
  * @param {Function} installFn
  *        Callable that takes the name of an xpi file to install and
  *        starts to install it.  Should return a Promise that resolves
  *        when the install is finished or rejects if the install is canceled.
+ * @param {string} telemetryBase
+ *        If supplied, the base type for telemetry events that should be
+ *        recorded for this install method.
  *
  * @returns {Promise}
  */
-async function testInstallMethod(installFn) {
+async function testInstallMethod(installFn, telemetryBase) {
   const PERMS_XPI = "browser_webext_permissions.xpi";
   const NO_PERMS_XPI = "browser_webext_nopermissions.xpi";
   const ID = "permissions@test.mozilla.org";
 
   await SpecialPowers.pushPrefEnv({set: [
     ["extensions.webapi.testing", true],
     ["extensions.install.requireBuiltInCerts", false],
 
     // XXX remove this when prompts are enabled by default
     ["extensions.webextPermissionPrompts", true],
   ]});
 
+  if (telemetryBase !== undefined) {
+    hookExtensionsTelemetry();
+  }
+
   let testURI = makeURI("https://example.com/");
   Services.perms.add(testURI, "install", Services.perms.ALLOW_ACTION);
   registerCleanupFunction(() => Services.perms.remove(testURI, "install"));
 
   async function runOnce(filename, cancel) {
     let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
 
     let installPromise = new Promise(resolve => {
@@ -311,16 +320,22 @@ async function testInstallMethod(install
   // 2. Same as #1 but with an extension that requests some permissions.
   await runOnce(PERMS_XPI, true);
 
   // 3. Repeat with the same extension from step 2 but this time,
   //    accept the permissions to install the extension.  (Then uninstall
   //    the extension to clean up.)
   await runOnce(PERMS_XPI, false);
 
+  if (telemetryBase !== undefined) {
+    // Should see 2 canceled installs followed by 1 successful install
+    // for this method.
+    expectTelemetry([`${telemetryBase}Rejected`, `${telemetryBase}Rejected`, `${telemetryBase}Accepted`]);
+  }
+
   await SpecialPowers.popPrefEnv();
 }
 
 // The tests in this directory install a bunch of extensions but they
 // need to uninstall them before exiting, as a stray leftover extension
 // after one test can foul up subsequent tests.
 // So, add a task to run before any tests that grabs a list of all the
 // add-ons that are pre-installed in the test environment and then checks
@@ -342,8 +357,26 @@ add_task(async function() {
     for (let addon of await AddonManager.getAllAddons()) {
       if (!existingAddons.has(addon.id)) {
         ok(false, `Addon ${addon.id} was left installed at the end of the test`);
         addon.uninstall();
       }
     }
   });
 });
+
+let collectedTelemetry = [];
+function hookExtensionsTelemetry() {
+  let originalHistogram = ExtensionsUI.histogram;
+  ExtensionsUI.histogram = {
+    add(value) { collectedTelemetry.push(value); },
+  };
+  registerCleanupFunction(() => {
+    is(collectedTelemetry.length, 0, "No unexamined telemetry after test is finished");
+    ExtensionsUI.histogram = originalHistogram;
+  });
+}
+
+function expectTelemetry(values) {
+  Assert.deepEqual(values, collectedTelemetry);
+  collectedTelemetry = [];
+}
+
new file mode 100644
--- /dev/null
+++ b/browser/base/content/theme-vars.inc.css
@@ -0,0 +1,166 @@
+%if 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/. */
+%endif
+
+:root[lwthemeicons~="--back-icon"] #back-button:-moz-lwtheme {
+  list-style-image: var(--back-icon) !important;
+}
+
+:root[lwthemeicons~="--forward-icon"] #forward-button:-moz-lwtheme {
+  list-style-image: var(--forward-icon) !important;
+}
+
+:root[lwthemeicons~="--reload-icon"] #urlbar-reload-button:-moz-lwtheme {
+  list-style-image: var(--reload-icon) !important;
+}
+
+:root[lwthemeicons~="--stop-icon"] #urlbar-stop-button:-moz-lwtheme {
+  list-style-image: var(--stop-icon) !important;
+}
+
+:root[lwthemeicons~="--bookmark_star-icon"] #bookmarks-menu-button:-moz-lwtheme {
+  list-style-image: var(--bookmark_star-icon) !important;
+}
+
+:root[lwthemeicons~="--bookmark_menu-icon"] #bookmarks-menu-button[cui-areatype='toolbar'] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon:-moz-lwtheme {
+  list-style-image: var(--bookmark_menu-icon) !important;
+}
+
+:root[lwthemeicons~="--downloads-icon"] #downloads-button:-moz-lwtheme {
+  list-style-image: var(--downloads-icon) !important;
+}
+
+:root[lwthemeicons~="--home-icon"] #home-button:-moz-lwtheme {
+  list-style-image: var(--home-icon) !important;
+}
+
+:root[lwthemeicons~="--app_menu-icon"] #PanelUI-menu-button:-moz-lwtheme {
+  list-style-image: var(--app_menu-icon) !important;
+}
+
+:root[lwthemeicons~="--cut-icon"] #cut-button:-moz-lwtheme {
+  list-style-image: var(--cut-icon) !important;
+}
+
+:root[lwthemeicons~="--copy-icon"] #copy-button:-moz-lwtheme {
+  list-style-image: var(--copy-icon) !important;
+}
+
+:root[lwthemeicons~="--paste-icon"] #paste-button:-moz-lwtheme {
+  list-style-image: var(--paste-icon) !important;
+}
+
+:root[lwthemeicons~="--new_window-icon"] #new-window-button:-moz-lwtheme {
+  list-style-image: var(--new_window-icon) !important;
+}
+
+:root[lwthemeicons~="--new_private_window-icon"] #privatebrowsing-button:-moz-lwtheme {
+  list-style-image: var(--new_private_window-icon) !important;
+}
+
+:root[lwthemeicons~="--save_page-icon"] #save-page-button:-moz-lwtheme {
+  list-style-image: var(--save_page-icon) !important;
+}
+
+:root[lwthemeicons~="--print-icon"] #print-button:-moz-lwtheme {
+  list-style-image: var(--print-icon) !important;
+}
+
+:root[lwthemeicons~="--history-icon"] #history-panelmenu:-moz-lwtheme {
+  list-style-image: var(--history-icon) !important;
+}
+
+:root[lwthemeicons~="--full_screen-icon"] #fullscreen-button:-moz-lwtheme {
+  list-style-image: var(--full_screen-icon) !important;
+}
+
+:root[lwthemeicons~="--find-icon"] #find-button:-moz-lwtheme {
+  list-style-image: var(--find-icon) !important;
+}
+
+:root[lwthemeicons~="--options-icon"] #preferences-button:-moz-lwtheme {
+  list-style-image: var(--options-icon) !important;
+}
+
+:root[lwthemeicons~="--addons-icon"] #add-ons-button:-moz-lwtheme {
+  list-style-image: var(--addons-icon) !important;
+}
+
+:root[lwthemeicons~="--developer-icon"] #developer-button:-moz-lwtheme {
+  list-style-image: var(--developer-icon) !important;
+}
+
+:root[lwthemeicons~="--synced_tabs-icon"] #sync-button:-moz-lwtheme {
+  list-style-image: var(--synced_tabs-icon) !important;
+}
+
+:root[lwthemeicons~="--open_file-icon"] #open-file-button:-moz-lwtheme {
+  list-style-image: var(--open_file-icon) !important;
+}
+
+:root[lwthemeicons~="--sidebars-icon"] #sidebar-button:-moz-lwtheme {
+  list-style-image: var(--sidebars-icon) !important;
+}
+
+:root[lwthemeicons~="--share_page-icon"] #social-share-button:-moz-lwtheme {
+  list-style-image: var(--share_page-icon) !important;
+}
+
+:root[lwthemeicons~="--subscribe-icon"] #feed-button:-moz-lwtheme {
+  list-style-image: var(--subscribe-icon) !important;
+}
+
+:root[lwthemeicons~="--text_encoding-icon"] #characterencoding-button:-moz-lwtheme {
+  list-style-image: var(--text_encoding-icon) !important;
+}
+
+:root[lwthemeicons~="--email_link-icon"] #email-link-button:-moz-lwtheme {
+  list-style-image: var(--email_link-icon) !important;
+}
+
+:root[lwthemeicons~="--forget-icon"] #panic-button:-moz-lwtheme {
+  list-style-image: var(--forget-icon) !important;
+}
+
+:root[lwthemeicons~="--pocket-icon"] #pocket-button:-moz-lwtheme {
+  list-style-image: var(--pocket-icon) !important;
+}
+
+:root[lwthemeicons~="--back-icon"] #back-button:-moz-lwtheme,
+:root[lwthemeicons~="--forward-icon"] #forward-button:-moz-lwtheme,
+:root[lwthemeicons~="--bookmark_star-icon"] #bookmarks-menu-button:-moz-lwtheme,
+:root[lwthemeicons~="--bookmark_menu-icon"] #bookmarks-menu-button[cui-areatype='toolbar'] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon:-moz-lwtheme,
+:root[lwthemeicons~="--downloads-icon"] #downloads-button:-moz-lwtheme,
+:root[lwthemeicons~="--home-icon"] #home-button:-moz-lwtheme,
+:root[lwthemeicons~="--app_menu-icon"] #PanelUI-menu-button:-moz-lwtheme,
+:root[lwthemeicons~="--cut-icon"] #cut-button:-moz-lwtheme,
+:root[lwthemeicons~="--copy-icon"] #copy-button:-moz-lwtheme,
+:root[lwthemeicons~="--paste-icon"] #paste-button:-moz-lwtheme,
+:root[lwthemeicons~="--new_window-icon"] #new-window-button:-moz-lwtheme,
+:root[lwthemeicons~="--new_private_window-icon"] #privatebrowsing-button:-moz-lwtheme,
+:root[lwthemeicons~="--save_page-icon"] #save-page-button:-moz-lwtheme,
+:root[lwthemeicons~="--print-icon"] #print-button:-moz-lwtheme,
+:root[lwthemeicons~="--history-icon"] #history-panelmenu:-moz-lwtheme,
+:root[lwthemeicons~="--full_screen-icon"] #fullscreen-button:-moz-lwtheme,
+:root[lwthemeicons~="--find-icon"] #find-button:-moz-lwtheme,
+:root[lwthemeicons~="--options-icon"] #preferences-button:-moz-lwtheme,
+:root[lwthemeicons~="--addons-icon"] #add-ons-button:-moz-lwtheme,
+:root[lwthemeicons~="--developer-icon"] #developer-button:-moz-lwtheme,
+:root[lwthemeicons~="--synced_tabs-icon"] #sync-button:-moz-lwtheme,
+:root[lwthemeicons~="--open_file-icon"] #open-file-button:-moz-lwtheme,
+:root[lwthemeicons~="--sidebars-icon"] #sidebar-button:-moz-lwtheme,
+:root[lwthemeicons~="--share_page-icon"] #social-share-button:-moz-lwtheme,
+:root[lwthemeicons~="--subscribe-icon"] #feed-button:-moz-lwtheme,
+:root[lwthemeicons~="--text_encoding-icon"] #characterencoding-button:-moz-lwtheme,
+:root[lwthemeicons~="--email_link-icon"] #email-link-button:-moz-lwtheme,
+:root[lwthemeicons~="--forget-icon"] #panic-button:-moz-lwtheme,
+:root[lwthemeicons~="--pocket-icon"] #pocket-button:-moz-lwtheme {
+  -moz-image-region: rect(0, 18px, 18px, 0) !important;
+}
+
+:root[lwthemeicons~="--reload-icon"] #urlbar-reload-button:-moz-lwtheme,
+:root[lwthemeicons~="--stop-icon"] #urlbar-stop-button:-moz-lwtheme {
+  -moz-image-region: rect(0, 14px, 14px, 0) !important;
+}
--- a/browser/components/extensions/ext-url-overrides.js
+++ b/browser/components/extensions/ext-url-overrides.js
@@ -3,102 +3,47 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
                                    "@mozilla.org/browser/aboutnewtab-service;1",
                                    "nsIAboutNewTabService");
-XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
-                                  "resource://gre/modules/Preferences.jsm");
 
 // Bug 1320736 tracks creating a generic precedence manager for handling
 // multiple addons modifying the same properties, and bug 1330494 has been filed
 // to track utilizing this manager for chrome_url_overrides. Until those land,
 // the edge cases surrounding multiple addons using chrome_url_overrides will
 // be ignored and precedence will be first come, first serve.
 let overrides = {
   // A queue of extensions in line to override the newtab page (sorted oldest to newest).
   newtab: [],
-  // A queue of extensions in line to override the home page (sorted oldest to newest).
-  home: [],
 };
 
-/**
- * Resets the specified page to its default value.
- *
- * @param {string} page The page to override. Accepted values are "newtab" and "home".
- */
-function resetPage(page) {
-  switch (page) {
-    case "newtab":
-      aboutNewTabService.resetNewTabURL();
-      break;
-    case "home":
-      Preferences.reset("browser.startup.homepage");
-      break;
-    default:
-      throw new Error("Unrecognized override type");
-  }
-}
-
-/**
- * Overrides the specified page to the specified URL.
- *
- * @param {string} page The page to override. Accepted values are "newtab" and "home".
- * @param {string} url The resolved URL to use for the page override.
- */
-function overridePage(page, url) {
-  switch (page) {
-    case "newtab":
-      aboutNewTabService.newTabURL = url;
-      break;
-    case "home":
-      Preferences.set("browser.startup.homepage", url);
-      break;
-    default:
-      throw new Error("Unrecognized override type");
-  }
-}
-
-/**
- * Updates the page to the URL specified by the extension next in line. If no extensions
- * are in line, the page is reset to its default value.
- *
- * @param {string} page The page to override.
- */
-function updatePage(page) {
-  if (overrides[page].length) {
-    overridePage(page, overrides[page][0].url);
-  } else {
-    resetPage(page);
-  }
-}
-
 /* eslint-disable mozilla/balanced-listeners */
 extensions.on("manifest_chrome_url_overrides", (type, directive, extension, manifest) => {
-  if (Object.keys(overrides).length > 1) {
-    extension.manifestError("Extensions can override only one page.");
-  }
+  if (manifest.chrome_url_overrides.newtab) {
+    let newtab = manifest.chrome_url_overrides.newtab;
+    let url = extension.baseURI.resolve(newtab);
 
-  for (let page of Object.keys(overrides)) {
-    if (manifest.chrome_url_overrides[page]) {
-      let relativeURL = manifest.chrome_url_overrides[page];
-      let url = extension.baseURI.resolve(relativeURL);
-      // Store the extension ID instead of a hard reference to the extension.
-      overrides[page].push({id: extension.id, url});
-      updatePage(page);
-      break;
+    // Only set the newtab URL if no other extension is overriding it.
+    if (!overrides.newtab.length) {
+      aboutNewTabService.newTabURL = url;
     }
+
+    overrides.newtab.push({id: extension.id, url});
   }
 });
 
 extensions.on("shutdown", (type, extension) => {
-  for (let page of Object.keys(overrides)) {
-    let i = overrides[page].findIndex(o => o.id === extension.id);
-    if (i !== -1) {
-      overrides[page].splice(i, 1);
-      updatePage(page);
+  let i = overrides.newtab.findIndex(o => o.id === extension.id);
+  if (i !== -1) {
+    overrides.newtab.splice(i, 1);
+
+    if (overrides.newtab.length) {
+      aboutNewTabService.newTabURL = overrides.newtab[0].url;
+    } else {
+      aboutNewTabService.resetNewTabURL();
     }
   }
 });
 /* eslint-enable mozilla/balanced-listeners */
--- a/browser/components/extensions/schemas/url_overrides.json
+++ b/browser/components/extensions/schemas/url_overrides.json
@@ -9,21 +9,16 @@
             "type": "object",
             "optional": true,
             "properties": {
               "newtab": {
                 "$ref": "ExtensionURL",
                 "optional": true,
                 "preprocess": "localize"
               },
-              "home": {
-                "$ref": "ExtensionURL",
-                "optional": true,
-                "preprocess": "localize"
-              },
               "bookmarks": {
                 "unsupported": true,
                 "$ref": "ExtensionURL",
                 "optional": true,
                 "preprocess": "localize"
               },
               "history": {
                 "unsupported": true,
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -111,19 +111,18 @@ support-files =
 [browser_ext_tabs_query.js]
 [browser_ext_tabs_reload.js]
 [browser_ext_tabs_reload_bypass_cache.js]
 [browser_ext_tabs_sendMessage.js]
 [browser_ext_tabs_cookieStoreId.js]
 [browser_ext_tabs_update.js]
 [browser_ext_tabs_zoom.js]
 [browser_ext_tabs_update_url.js]
+[browser_ext_themes_icons.js]
 [browser_ext_topwindowid.js]
-[browser_ext_url_overrides_all.js]
-[browser_ext_url_overrides_home.js]
 [browser_ext_url_overrides_newtab.js]
 [browser_ext_webRequest.js]
 [browser_ext_webNavigation_frameId0.js]
 [browser_ext_webNavigation_getFrames.js]
 [browser_ext_webNavigation_onCreatedNavigationTarget.js]
 [browser_ext_webNavigation_onCreatedNavigationTarget_window_open.js]
 [browser_ext_webNavigation_urlbar_transitions.js]
 [browser_ext_windows.js]
--- a/browser/components/extensions/test/browser/browser_ext_contextMenus_icons.js
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_icons.js
@@ -2,18 +2,17 @@
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 add_task(function* () {
   let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
     "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html");
 
   let encodedImageData = "iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAC4klEQVRYhdWXLWzbQBSADQtDAwsHC1tUhUxqfL67lk2tdn+OJg0ODU0rLByqgqINBY6tmlbn7LMTJ5FaFVVBk1G0oUGjG2jT2Y7jxmmcbU/6iJ+f36fz+e5sGP9riCGm9hB37RG+scd4Yo/wsDXCZyIE2xuXsce4bY+wXkAsQtzYmExrfFgvkJkRbkzo1ehoxx5iXcgI/9iYUGt8WH9MqDXEcmNChmEYrRCf2SHWeYgQx3x0tLNRIeKQLTtEFyJEep4NTuhk8BC+yMrwEE3+iozo42d8gK7FAOkMsRiiN8QhW2ttSK5QTfRRV4QoymVeJMvPvDp7gCZigD613MN6yRFA3SWarow9QB9LCfG+NeF9qCtjAKOSQjCqVKhfVsiHEQ+grgx/lRGqUihAc1uL8EFD+KCRO+GrF4J61phcoRoPoEzkYhZYpykh5sMb7kOdIeY+jHKur4QI4Feh4AFX1nVeLxrAvQchGsBz5ls6wa2QdwcvIcE2863bTH79KOvsz/uUYJsp+J0pSzNlDckVqqVGUAF+n6uS7txcOl6wot4JVy70ufDLy4pWLUQVPE81pRI0mGe9oxLMHSeohHvMs/STUNaUK6vDPCvOyxMFDx4achehRDJmHnydnkPww5OFfLxrGIZBFDyYl4LpMzlTQFIP6AQx86w2UeYBccFpJrcKv5L9eGDtUAU6RIELqsB74uynjy/UBRF1gS5BTFxwQT1wTiXoUg9MH7m/3NZRRoi5IJytUbMgzv4Wc832+oQkiKgEehmyMkkpKsFkQV11QsRJL5rJYBLItQgRaUZEmnoZXsomz3vGiWw+I9KMF9SVFOqZEemZekli1jN3U/UOqhHHvC6oWWGElhfSpGdOk6+O9prdwvtLj5BjRsQxdRnot+Zeifpy/2/0stktKTRNLmbk0mwXyl8253fyojj+8rxOHNAhjjm5n0/5OOCGOKBzkrMO0Z75lvSAzKlrF32Z/3z8BqLAn+yMV7VhAAAAAElFTkSuQmCC";
-  let decodedImageData = atob(encodedImageData);
-  const IMAGE_ARRAYBUFFER = Uint8Array.from(decodedImageData, byte => byte.charCodeAt(0)).buffer;
+  const IMAGE_ARRAYBUFFER = imageBufferFromDataURI(encodedImageData);
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["contextMenus"],
       "icons": {
         "18": "extension.png",
       },
     },
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_themes_icons.js
@@ -0,0 +1,275 @@
+"use strict";
+
+const ENCODED_IMAGE_DATA = "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgNjQgNjQiPjxwYXRoIGQ9Im01NS45IDMyLjFsLTIyLjctMTQuOWMwIDAgMTIuOS0xNy40IDE5LjQtMTQuOSAzLjEgMS4xIDUuNCAyNS4xIDMuMyAyOS44IiBmaWxsPSIjM2U0MzQ3Ii8+PHBhdGggZD0ibTU0LjkgMzMuOWwtOS00LjFjMCAwLTUuMy0xNCA2LjEtMjQuMSAyLjQgMiA1LjEgMjUgMi45IDI4LjIiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJtOC4xIDMyLjFsMjIuNi0xNC45YzAgMC0xMi45LTE3LjQtMTkuNC0xNC45LTMgMS4xLTUuMyAyNS4xLTMuMiAyOS44IiBmaWxsPSIjM2U0MzQ3Ii8+PHBhdGggZD0ibTkuMSAzMy45bDktNC4xYzAgMCA1LjMtMTQtNi4xLTI0LjEtMi40IDItNS4xIDI1LTIuOSAyOC4yIiBmaWxsPSIjZmZmIi8+PHBhdGggZD0iTTMyLDEzQzE4LjksMTMsMiwzMy42LDIsNDUuNEMyMC41LDQ1LjQsMTkuNyw2MiwzMiw2MnMxMS41LTE2LjYsMzAtMTYuNkM2MiwzMy42LDQ1LjEsMTMsMzIsMTN6IiBmaWxsPSIjZmY4NzM2Ii8+PGcgZmlsbD0iI2ZmZiI+PHBhdGggZD0iTTMyLDU2LjJjMCw1LjEsOS42LDQuMiw5LjUtMi45YzYuNy05LjQsMTkuOS04LjcsMTkuOS04LjdDMzkuNiwzMi40LDMyLDU2LjIsMzIsNTYuMnoiLz48cGF0aCBkPSJNMzIsNTYuMmMwLDUuMS05LjYsNC4yLTkuNS0yLjlDMTUuOCw0NCwyLjYsNDQuNywyLjYsNDQuN0MyNC40LDMyLjQsMzIsNTYuMiwzMiw1Ni4yeiIvPjwvZz48ZyBmaWxsPSIjZmY4NzM2Ij48cGF0aCBkPSJtNTMuNCAxOC41Yy00IC43LTQuOSA2LjMtNC45IDYuM2w2IDUuM2MtMi4zLTUuOS0xLjEtMTEuNi0xLjEtMTEuNiIvPjxwYXRoIGQ9Im01MS4xIDEzLjVjLTQuNCAzLjktNS4xIDguNy01LjEgOC43bDYgNS4zYy0yLjQtNS44LS45LTE0LS45LTE0Ii8+PHBhdGggZD0ibTEwLjYgMTguNWM0IC43IDQuOSA2LjMgNC45IDYuM2wtNiA1LjNjMi4zLTUuOSAxLjEtMTEuNiAxLjEtMTEuNiIvPjxwYXRoIGQ9Im0xMi45IDEzLjVjNC40IDMuOSA1LjEgOC43IDUuMSA4LjdsLTYgNS4zYzIuNC01LjguOS0xNCAuOS0xNCIvPjwvZz48cGF0aCBkPSJtNTIuOCAzMS4xYy01LjctMS44LTEwLjktMy40LTEzLjguOS0yLjQgMy43LjcgOS40LjcgOS40IDExLjIgMS4yIDEzLjEtMTAuMyAxMy4xLTEwLjMiIGZpbGw9IiMzZTQzNDciLz48ZWxsaXBzZSBjeD0iNDMiIGN5PSIzNi4zIiByeD0iNC4yIiByeT0iNC4xIiBmaWxsPSIjZDVmZjgzIi8+PGcgZmlsbD0iIzNlNDM0NyI+PGVsbGlwc2UgY3g9IjQzIiBjeT0iMzYuMyIgcng9IjIuNyIgcnk9IjIuNyIvPjxwYXRoIGQ9Im0xMS4yIDMxLjFjNS43LTEuOCAxMC45LTMuNCAxMy43LjkgMi40IDMuNy0uNyA5LjQtLjcgOS40LTExLjEgMS4yLTEzLTEwLjMtMTMtMTAuMyIvPjwvZz48ZWxsaXBzZSBjeD0iMjEiIGN5PSIzNi4zIiByeD0iNC4yIiByeT0iNC4xIiBmaWxsPSIjZDVmZjgzIi8+PGcgZmlsbD0iIzNlNDM0NyI+PGVsbGlwc2UgY3g9IjIxIiBjeT0iMzYuMyIgcng9IjIuNyIgcnk9IjIuNyIvPjxwYXRoIGQ9Im00MS4yIDQ3LjljLS43LTIuMy0xLjgtNC40LTMtNi41IDEuMSAyLjEgMiA0LjMgMi41IDYuNi41IDIuMy43IDQuNyAwIDYuOC0uNCAxLTEgMi0xLjggMi42LS44LjYtMS44IDEtMi43IDEtLjkgMC0xLjktLjMtMi41LTEtLjYtLjctLjktMS42LS44LTIuNmwtLjkuMmgtLjljMCAxLS4yIDEuOS0uOCAyLjYtLjYuNy0xLjUgMS0yLjUgMS0uOSAwLTEuOS0uNC0yLjctMS0uOC0uNi0xLjQtMS42LTEuOC0yLjYtLjgtMi4xLS42LTQuNiAwLTYuOC41LTIuMyAxLjUtNC41IDIuNS02LjYtMS4yIDItMi4zIDQuMS0zIDYuNS0uNyAyLjMtMS4xIDQuOC0uNCA3LjMuMyAxLjIgMSAyLjQgMS45IDMuMy45LjkgMi4xIDEuNCAzLjQgMS41IDEuMi4xIDIuNi0uMiAzLjctMS4yLjMtLjIuNS0uNS43LS44LjIuMy40LjYuNy44IDEgMSAyLjQgMS4zIDMuNyAxLjIgMS4zLS4xIDIuNC0uNyAzLjQtMS41LjktLjkgMS42LTIgMS45LTMuMy41LTIuNi4xLTUuMi0uNi03LjUiLz48cGF0aCBkPSJtMzcuNiA1MC4zYy0xLjEtMS4xLTQuNS0xLjItNS42LTEuMi0xIDAtNC41LjEtNS42IDEuMi0uOC44LS4yIDIuOCAxLjkgNC41IDEuMyAxLjEgMi42IDEuNCAzLjYgMS40IDEgMCAyLjMtLjMgMy42LTEuNCAyLjMtMS43IDIuOS0zLjcgMi4xLTQuNSIvPjwvZz48L3N2Zz4=";
+
+  /**
+   * Verifies that the button uses the expected icon.
+   *
+   * @param {string} selector The CSS selector used to find the button
+   *   within the DOM.
+   * @param {boolean} shouldHaveCustomStyling True if the button should
+   *   have custom styling, False otherwise.
+   * @param {string} message The message that is printed to the console
+   *   by the verifyFn.
+   */
+function verifyButtonProperties(selector, shouldHaveCustomStyling, message) {
+  try {
+    let element;
+    // This selector is different than the others because it's the only
+    // toolbarbutton that we ship by default that has type="menu-button",
+    // which don't place a unique ID on the associated dropmarker-icon.
+    if (selector == "#bookmarks-menu-button > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon") {
+      if (message.includes("panel")) {
+        // The dropmarker isn't shown in the menupanel.
+        return;
+      }
+      element = document.querySelector("#bookmarks-menu-button");
+      element = document.getAnonymousElementByAttribute(element, "class", "toolbarbutton-menubutton-dropmarker");
+      element = document.getAnonymousElementByAttribute(element, "class", "dropmarker-icon");
+    } else {
+      element = document.querySelector(selector);
+    }
+
+    let listStyleImage = getComputedStyle(element).listStyleImage;
+    info(`listStyleImage for fox.svg is ${listStyleImage}`);
+    is(listStyleImage.includes("fox.svg"), shouldHaveCustomStyling, message);
+  } catch (ex) {
+    ok(false, `Unable to verify ${selector}: ${ex}`);
+  }
+}
+
+  /**
+   * Verifies that the button uses default styling.
+   *
+   * @param {string} selector The CSS selector used to find the button
+   *   within the DOM.
+   * @param {string} message The message that is printed to the console
+   *   by the verifyFn.
+   */
+function verifyButtonWithoutCustomStyling(selector, message) {
+  verifyButtonProperties(selector, false, message);
+}
+
+  /**
+   * Verifies that the button uses non-default styling.
+   *
+   * @param {string} selector The CSS selector used to find the button
+   *   within the DOM.
+   * @param {string} message The message that is printed to the console
+   *   by the verifyFn.
+   */
+function verifyButtonWithCustomStyling(selector, message) {
+  verifyButtonProperties(selector, true, message);
+}
+
+  /**
+   * Loops through all of the buttons to confirm that they are styled
+   * as expected (either with or without custom styling).
+   *
+   * @param {object} icons Array of an array that specifies which buttons should
+   *   have custom icons.
+   * @param {object} iconInfo An array of arrays that maps API names to
+   *   CSS selectors.
+   * @param {string} area The name of the area that the button resides in.
+   */
+function checkButtons(icons, iconInfo, area) {
+  for (let button of iconInfo) {
+    let iconInfo = icons.find(arr => arr[0] == button[0]);
+    if (iconInfo[1]) {
+      verifyButtonWithCustomStyling(button[1],
+        `The ${button[1]} should have it's icon customized in the ${area}`);
+    } else {
+      verifyButtonWithoutCustomStyling(button[1],
+        `The ${button[1]} should not have it's icon customized in the ${area}`);
+    }
+  }
+}
+
+function* runTestWithIcons(icons) {
+  const FRAME_COLOR = [71, 105, 91];
+  const TAB_TEXT_COLOR = [207, 221, 192, .9];
+  let manifest = {
+    "theme": {
+      "images": {
+        "theme_frame": "fox.svg",
+      },
+      "colors": {
+        "frame": FRAME_COLOR,
+        "tab_text": TAB_TEXT_COLOR,
+      },
+      "icons": {},
+    },
+  };
+  let files = {
+    "fox.svg": imageBufferFromDataURI(ENCODED_IMAGE_DATA),
+  };
+
+  // Each item in this array has the following setup:
+  // At position 0: The name that is used in the theme manifest.
+  // At position 1: The CSS selector for the button in the DOM.
+  // At position 2: The CustomizableUI name for the widget, only defined
+  //                if customizable.
+  const ICON_INFO = [
+    ["back", "#back-button"],
+    ["forward", "#forward-button"],
+    ["reload", "#urlbar-reload-button"],
+    ["stop", "#urlbar-stop-button"],
+    ["bookmark_star", "#bookmarks-menu-button", "bookmarks-menu-button"],
+    ["bookmark_menu", "#bookmarks-menu-button > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon"],
+    ["downloads", "#downloads-button", "downloads-button"],
+    ["home", "#home-button", "home-button"],
+    ["app_menu", "#PanelUI-menu-button"],
+    ["cut", "#cut-button", "edit-controls"],
+    ["copy", "#copy-button"],
+    ["paste", "#paste-button"],
+    ["new_window", "#new-window-button", "new-window-button"],
+    ["new_private_window", "#privatebrowsing-button", "privatebrowsing-button"],
+    ["save_page", "#save-page-button", "save-page-button"],
+    ["print", "#print-button", "print-button"],
+    ["history", "#history-panelmenu", "history-panelmenu"],
+    ["full_screen", "#fullscreen-button", "fullscreen-button"],
+    ["find", "#find-button", "find-button"],
+    ["options", "#preferences-button", "preferences-button"],
+    ["addons", "#add-ons-button", "add-ons-button"],
+    ["developer", "#developer-button", "developer-button"],
+    ["synced_tabs", "#sync-button", "sync-button"],
+    ["open_file", "#open-file-button", "open-file-button"],
+    ["sidebars", "#sidebar-button", "sidebar-button"],
+    ["share_page", "#social-share-button", "social-share-button"],
+    ["subscribe", "#feed-button", "feed-button"],
+    ["text_encoding", "#characterencoding-button", "characterencoding-button"],
+    ["email_link", "#email-link-button", "email-link-button"],
+    ["forget", "#panic-button", "panic-button"],
+    ["pocket", "#pocket-button", "pocket-button"],
+  ];
+
+  window.maximize();
+
+  for (let button of ICON_INFO) {
+    if (button[2]) {
+      CustomizableUI.addWidgetToArea(button[2], CustomizableUI.AREA_NAVBAR);
+    }
+
+    verifyButtonWithoutCustomStyling(button[1],
+      `The ${button[1]} should not have it's icon customized when the test starts`);
+
+    let iconInfo = icons.find(arr => arr[0] == button[0]);
+    manifest.theme.icons[button[0]] = iconInfo[1];
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({manifest, files});
+
+  yield extension.startup();
+
+  checkButtons(icons, ICON_INFO, "toolbar");
+
+  for (let button of ICON_INFO) {
+    if (button[2]) {
+      CustomizableUI.addWidgetToArea(button[2], CustomizableUI.AREA_PANEL);
+    }
+  }
+
+  yield PanelUI.show();
+
+  checkButtons(icons, ICON_INFO, "panel");
+
+  yield PanelUI.hide();
+
+  yield extension.unload();
+
+  for (let button of ICON_INFO) {
+    verifyButtonWithoutCustomStyling(button[1],
+      `The ${button[1]} should not have it's icon customized when the theme is unloaded`);
+  }
+}
+
+add_task(function* setup() {
+  yield SpecialPowers.pushPrefEnv({
+    set: [["extensions.webextensions.themes.enabled", true],
+          ["extensions.webextensions.themes.icons.enabled", true]],
+  });
+});
+
+add_task(function* test_all_icons() {
+  let icons = [
+    ["back", "fox.svg"],
+    ["forward", "fox.svg"],
+    ["reload", "fox.svg"],
+    ["stop", "fox.svg"],
+    ["bookmark_star", "fox.svg"],
+    ["bookmark_menu", "fox.svg"],
+    ["downloads", "fox.svg"],
+    ["home", "fox.svg"],
+    ["app_menu", "fox.svg"],
+    ["cut", "fox.svg"],
+    ["copy", "fox.svg"],
+    ["paste", "fox.svg"],
+    ["new_window", "fox.svg"],
+    ["new_private_window", "fox.svg"],
+    ["save_page", "fox.svg"],
+    ["print", "fox.svg"],
+    ["history", "fox.svg"],
+    ["full_screen", "fox.svg"],
+    ["find", "fox.svg"],
+    ["options", "fox.svg"],
+    ["addons", "fox.svg"],
+    ["developer", "fox.svg"],
+    ["synced_tabs", "fox.svg"],
+    ["open_file", "fox.svg"],
+    ["sidebars", "fox.svg"],
+    ["share_page", "fox.svg"],
+    ["subscribe", "fox.svg"],
+    ["text_encoding", "fox.svg"],
+    ["email_link", "fox.svg"],
+    ["forget", "fox.svg"],
+    ["pocket", "fox.svg"],
+  ];
+  yield runTestWithIcons(icons);
+});
+
+add_task(function* teardown() {
+  CustomizableUI.reset();
+  window.restore();
+});
+
+add_task(function* test_some_icons() {
+  let icons = [
+    ["back", ""],
+    ["forward", ""],
+    ["reload", "fox.svg"],
+    ["stop", ""],
+    ["bookmark_star", ""],
+    ["bookmark_menu", ""],
+    ["downloads", ""],
+    ["home", "fox.svg"],
+    ["app_menu", "fox.svg"],
+    ["cut", ""],
+    ["copy", ""],
+    ["paste", ""],
+    ["new_window", ""],
+    ["new_private_window", ""],
+    ["save_page", ""],
+    ["print", ""],
+    ["history", ""],
+    ["full_screen", ""],
+    ["find", ""],
+    ["options", ""],
+    ["addons", ""],
+    ["developer", ""],
+    ["synced_tabs", ""],
+    ["open_file", ""],
+    ["sidebars", ""],
+    ["share_page", ""],
+    ["subscribe", ""],
+    ["text_encoding", ""],
+    ["email_link", ""],
+    ["forget", ""],
+    ["pocket", "fox.svg"],
+  ];
+  yield runTestWithIcons(icons);
+});
+
+add_task(function* teardown() {
+  CustomizableUI.reset();
+  window.restore();
+});
deleted file mode 100644
--- a/browser/components/extensions/test/browser/browser_ext_url_overrides_all.js
+++ /dev/null
@@ -1,97 +0,0 @@
-/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set sts=2 sw=2 et tw=80: */
-
-"use strict";
-
-XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
-                                   "@mozilla.org/browser/aboutnewtab-service;1",
-                                   "nsIAboutNewTabService");
-
-const NEWTAB_URI = "webext-newtab.html";
-const HOME_URI = "webext-home.html";
-
-add_task(function* test_extensions_overriding_different_pages() {
-  let defaultHomePage = Preferences.get("browser.startup.homepage");
-  let defaultNewtabPage = aboutNewTabService.newTabURL;
-
-  is(Preferences.get("browser.startup.homepage"), defaultHomePage,
-    `Default home url should be ${defaultHomePage}`);
-  is(aboutNewTabService.newTabURL, defaultNewtabPage,
-    `Default newtab url should be ${defaultNewtabPage}`);
-
-  let ext1 = ExtensionTestUtils.loadExtension({
-    manifest: {"chrome_url_overrides": {}},
-  });
-
-  let ext2 = ExtensionTestUtils.loadExtension({
-    manifest: {"chrome_url_overrides": {newtab: NEWTAB_URI}},
-  });
-
-  let ext3 = ExtensionTestUtils.loadExtension({
-    manifest: {"chrome_url_overrides": {home: HOME_URI}},
-  });
-
-  yield ext1.startup();
-
-  is(aboutNewTabService.newTabURL, defaultNewtabPage,
-    `Default newtab url should still be ${defaultNewtabPage}`);
-  is(Preferences.get("browser.startup.homepage"), defaultHomePage,
-    `Default home url should be ${defaultHomePage}`);
-
-  yield ext2.startup();
-
-  ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI),
-    "Newtab url should be overriden by the second extension.");
-  is(Preferences.get("browser.startup.homepage"), defaultHomePage,
-    `Default home url should be ${defaultHomePage}`);
-
-  yield ext1.unload();
-
-  ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI),
-    "Newtab url should still be overriden by the second extension.");
-  is(Preferences.get("browser.startup.homepage"), defaultHomePage,
-    `Default home url should be ${defaultHomePage}`);
-
-  yield ext3.startup();
-
-  ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI),
-    "Newtab url should still be overriden by the second extension.");
-  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI),
-    "Home url should be overriden by the third extension.");
-
-  yield ext2.unload();
-
-  is(aboutNewTabService.newTabURL, defaultNewtabPage,
-    `Newtab url should be reset to ${defaultNewtabPage}`);
-  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI),
-    "Home url should still be overriden by the third extension.");
-
-  yield ext3.unload();
-
-  is(aboutNewTabService.newTabURL, defaultNewtabPage,
-    `Newtab url should be reset to ${defaultNewtabPage}`);
-  is(Preferences.get("browser.startup.homepage"), defaultHomePage,
-    `Home url should be reset to ${defaultHomePage}`);
-});
-
-add_task(function* test_extensions_with_multiple_overrides() {
-  let ext = ExtensionTestUtils.loadExtension({
-    manifest: {"chrome_url_overrides": {
-      newtab: NEWTAB_URI,
-      home: HOME_URI,
-    }},
-  });
-
-  SimpleTest.waitForExplicitFinish();
-  let waitForConsole = new Promise(resolve => {
-    SimpleTest.monitorConsole(resolve, [{
-      message: /Extensions can override only one page./,
-    }]);
-  });
-
-  yield ext.startup();
-  yield ext.unload();
-
-  SimpleTest.endMonitorConsole();
-  yield waitForConsole;
-});
deleted file mode 100644
--- a/browser/components/extensions/test/browser/browser_ext_url_overrides_home.js
+++ /dev/null
@@ -1,74 +0,0 @@
-/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set sts=2 sw=2 et tw=80: */
-
-"use strict";
-
-XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
-                                  "resource://gre/modules/Preferences.jsm");
-
-const HOME_URI_1 = "webext-home-1.html";
-const HOME_URI_2 = "webext-home-2.html";
-const HOME_URI_3 = "webext-home-3.html";
-
-add_task(function* test_multiple_extensions_overriding_newtab_page() {
-  let defaultHomePage = Preferences.get("browser.startup.homepage");
-
-  is(Preferences.get("browser.startup.homepage"), defaultHomePage,
-     `Default home url should be ${defaultHomePage}`);
-
-  let ext1 = ExtensionTestUtils.loadExtension({
-    manifest: {"chrome_url_overrides": {}},
-  });
-
-  let ext2 = ExtensionTestUtils.loadExtension({
-    manifest: {"chrome_url_overrides": {home: HOME_URI_1}},
-  });
-
-  let ext3 = ExtensionTestUtils.loadExtension({
-    manifest: {"chrome_url_overrides": {home: HOME_URI_2}},
-  });
-
-  let ext4 = ExtensionTestUtils.loadExtension({
-    manifest: {"chrome_url_overrides": {home: HOME_URI_3}},
-  });
-
-  yield ext1.startup();
-
-  is(Preferences.get("browser.startup.homepage"), defaultHomePage,
-       `Default home url should still be ${defaultHomePage}`);
-
-  yield ext2.startup();
-
-  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_1),
-     "Home url should be overriden by the second extension.");
-
-  yield ext1.unload();
-
-  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_1),
-     "Home url should still be overriden by the second extension.");
-
-  yield ext3.startup();
-
-  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_1),
-     "Home url should still be overriden by the second extension.");
-
-  yield ext2.unload();
-
-  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_2),
-     "Home url should be overriden by the third extension.");
-
-  yield ext4.startup();
-
-  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_2),
-     "Home url should be overriden by the third extension.");
-
-  yield ext4.unload();
-
-  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_2),
-     "Home url should be overriden by the third extension.");
-
-  yield ext3.unload();
-
-  is(Preferences.get("browser.startup.homepage"), defaultHomePage,
-     `Home url should be reset to ${defaultHomePage}`);
-});
--- a/browser/components/extensions/test/browser/browser_ext_url_overrides_newtab.js
+++ b/browser/components/extensions/test/browser/browser_ext_url_overrides_newtab.js
@@ -71,21 +71,21 @@ add_task(function* test_multiple_extensi
   is(aboutNewTabService.newTabURL, "about:newtab",
      "Newtab url should be reset to about:newtab");
 });
 
 add_task(function* test_sending_message_from_newtab_page() {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "chrome_url_overrides": {
-        newtab: NEWTAB_URI_1,
+        newtab: NEWTAB_URI_2,
       },
     },
     files: {
-      [NEWTAB_URI_1]: `
+      [NEWTAB_URI_2]: `
         <!DOCTYPE html>
         <head>
           <meta charset="utf-8"/></head>
         <html>
           <body>
             <script src="newtab.js"></script>
           </body>
         </html>
--- a/browser/components/extensions/test/browser/head.js
+++ b/browser/components/extensions/test/browser/head.js
@@ -7,17 +7,18 @@
  *          clickBrowserAction clickPageAction
  *          getBrowserActionPopup getPageActionPopup
  *          closeBrowserAction closePageAction
  *          promisePopupShown promisePopupHidden
  *          openContextMenu closeContextMenu
  *          openExtensionContextMenu closeExtensionContextMenu
  *          openActionContextMenu openSubmenu closeActionContextMenu
  *          openTabContextMenu closeTabContextMenu
- *          imageBuffer getListStyleImage getPanelForNode
+ *          imageBuffer imageBufferFromDataURI
+ *          getListStyleImage getPanelForNode
  *          awaitExtensionPanel awaitPopupResize
  *          promiseContentDimensions alterContent
  */
 
 const {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm", {});
 const {CustomizableUI} = Cu.import("resource:///modules/CustomizableUI.jsm", {});
 
 // We run tests under two different configurations, from browser.ini and
@@ -59,18 +60,23 @@ var focusWindow = Task.async(function* f
       resolve();
     }, {capture: true, once: true});
   });
 
   win.focus();
   yield promise;
 });
 
+function imageBufferFromDataURI(encodedImageData) {
+  let decodedImageData = atob(encodedImageData);
+  return Uint8Array.from(decodedImageData, byte => byte.charCodeAt(0)).buffer;
+}
+
 let img = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==";
-var imageBuffer = Uint8Array.from(atob(img), byte => byte.charCodeAt(0)).buffer;
+var imageBuffer = imageBufferFromDataURI(img);
 
 function getListStyleImage(button) {
   let style = button.ownerGlobal.getComputedStyle(button);
 
   let match = /^url\("(.*)"\)$/.exec(style.listStyleImage);
 
   return match && match[1];
 }
--- a/browser/components/feeds/nsFeedSniffer.cpp
+++ b/browser/components/feeds/nsFeedSniffer.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsFeedSniffer.h"
 
+#include "mozilla/Unused.h"
 
 #include "nsNetCID.h"
 #include "nsXPCOM.h"
 #include "nsCOMPtr.h"
 #include "nsStringStream.h"
 
 #include "nsBrowserCompsCID.h"
 
@@ -52,18 +53,18 @@ nsFeedSniffer::ConvertEncodedData(nsIReq
   nsresult rv = NS_OK;
 
  mDecodedData = "";
  nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request));
   if (!httpChannel)
     return NS_ERROR_NO_INTERFACE;
 
   nsAutoCString contentEncoding;
-  httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Encoding"), 
-                                 contentEncoding);
+  mozilla::Unused << httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Encoding"),
+                                                    contentEncoding);
   if (!contentEncoding.IsEmpty()) {
     nsCOMPtr<nsIStreamConverterService> converterService(do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID));
     if (converterService) {
       ToLowerCase(contentEncoding);
 
       nsCOMPtr<nsIStreamListener> converter;
       rv = converterService->AsyncConvertData(contentEncoding.get(), 
                                               "uncompressed", this, nullptr, 
@@ -208,17 +209,17 @@ nsFeedSniffer::GetMIMETypeFromContent(ns
                                       nsACString& sniffedType)
 {
   nsCOMPtr<nsIHttpChannel> channel(do_QueryInterface(request));
   if (!channel)
     return NS_ERROR_NO_INTERFACE;
 
   // Check that this is a GET request, since you can't subscribe to a POST...
   nsAutoCString method;
-  channel->GetRequestMethod(method);
+  mozilla::Unused << channel->GetRequestMethod(method);
   if (!method.EqualsLiteral("GET")) {
     sniffedType.Truncate();
     return NS_OK;
   }
 
   // We need to find out if this is a load of a view-source document. In this
   // case we do not want to override the content type, since the source display
   // does not need to be converted from feed format to XUL. More importantly, 
@@ -260,18 +261,20 @@ nsFeedSniffer::GetMIMETypeFromContent(ns
     // check for an attachment after we have a likely feed.
     if(HasAttachmentDisposition(channel)) {
       sniffedType.Truncate();
       return NS_OK;
     }
 
     // set the feed header as a response header, since we have good metadata
     // telling us that the feed is supposed to be RSS or Atom
-    channel->SetResponseHeader(NS_LITERAL_CSTRING("X-Moz-Is-Feed"),
-                               NS_LITERAL_CSTRING("1"), false);
+    mozilla::DebugOnly<nsresult> rv =
+      channel->SetResponseHeader(NS_LITERAL_CSTRING("X-Moz-Is-Feed"),
+                                 NS_LITERAL_CSTRING("1"), false);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
     sniffedType.AssignLiteral(TYPE_MAYBE_FEED);
     return NS_OK;
   }
 
   // Don't sniff arbitrary types.  Limit sniffing to situations that
   // we think can reasonably arise.
   if (!contentType.EqualsLiteral(TEXT_HTML) &&
       !contentType.EqualsLiteral(APPLICATION_OCTET_STREAM) &&
--- a/browser/components/places/content/places.js
+++ b/browser/components/places/content/places.js
@@ -308,17 +308,17 @@ var PlacesOrganizer = {
 
     let node = this._places.selectedNode;
     if (node) {
       let middleClick = aEvent.button == 1 && aEvent.detail == 1;
       if (middleClick && PlacesUtils.nodeIsContainer(node)) {
         // The command execution function will take care of seeing if the
         // selection is a folder or a different container type, and will
         // load its contents in tabs.
-        PlacesUIUtils.openContainerNodeInTabs(selectedNode, aEvent, this._places);
+        PlacesUIUtils.openContainerNodeInTabs(node, aEvent, this._places);
       }
     }
   },
 
   /**
    * Handle focus changes on the places list and the current content view.
    */
   updateDetailsPane: function PO_updateDetailsPane() {
--- a/browser/components/places/tests/browser/browser.ini
+++ b/browser/components/places/tests/browser/browser.ini
@@ -34,16 +34,17 @@ support-files =
 [browser_drag_bookmarks_on_toolbar.js]
 [browser_forgetthissite_single.js]
 [browser_history_sidebar_search.js]
 [browser_library_batch_delete.js]
 [browser_library_commands.js]
 [browser_library_downloads.js]
 [browser_library_infoBox.js]
 [browser_library_left_pane_fixnames.js]
+[browser_library_left_pane_middleclick.js]
 [browser_library_left_pane_select_hierarchy.js]
 [browser_library_middleclick.js]
 [browser_library_open_leak.js]
 [browser_library_openFlatContainer.js]
 [browser_library_panel_leak.js]
 [browser_library_search.js]
 [browser_library_views_liveupdate.js]
 [browser_markPageAsFollowedLink.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_library_left_pane_middleclick.js
@@ -0,0 +1,196 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+ /**
+ * Tests middle-clicking items in the Library.
+ */
+
+const ENABLE_HISTORY_PREF = "places.history.enabled";
+
+var gLibrary = null;
+var gTests = [];
+var gCurrentTest = null;
+
+// Listener for TabOpen and tabs progress.
+var gTabsListener = {
+  _loadedURIs: [],
+  _openTabsCount: 0,
+
+  handleEvent(aEvent) {
+    if (aEvent.type != "TabOpen")
+      return;
+
+    if (++this._openTabsCount == gCurrentTest.URIs.length) {
+      is(gBrowser.tabs.length, gCurrentTest.URIs.length + 1,
+         "We have opened " + gCurrentTest.URIs.length + " new tab(s)");
+    }
+
+    var tab = aEvent.target;
+    is(tab.ownerGlobal, window,
+       "Tab has been opened in current browser window");
+  },
+
+  onLocationChange(aBrowser, aWebProgress, aRequest, aLocationURI,
+                             aFlags) {
+    var spec = aLocationURI.spec;
+    ok(true, spec);
+    // When a new tab is opened, location is first set to "about:blank", so
+    // we can ignore those calls.
+    // Ignore multiple notifications for the same URI too.
+    if (spec == "about:blank" || this._loadedURIs.includes(spec))
+      return;
+
+    ok(gCurrentTest.URIs.includes(spec),
+       "Opened URI found in list: " + spec);
+
+    if (gCurrentTest.URIs.includes(spec))
+      this._loadedURIs.push(spec);
+
+    if (this._loadedURIs.length == gCurrentTest.URIs.length) {
+      // We have correctly opened all URIs.
+
+      // Reset arrays.
+      this._loadedURIs.length = 0;
+
+      this._openTabsCount = 0;
+
+      executeSoon(function() {
+        // Close all tabs.
+        while (gBrowser.tabs.length > 1)
+          gBrowser.removeCurrentTab();
+
+        // Test finished.  This will move to the next one.
+        waitForFocus(gCurrentTest.finish, gBrowser.ownerGlobal);
+      });
+    }
+  }
+}
+
+// ------------------------------------------------------------------------------
+// Open a folder in tabs.
+
+gTests.push({
+  desc: "Open a folder in tabs.",
+  URIs: ["about:buildconfig", "about:"],
+  _folderId: -1,
+
+  setup() {
+    var bs = PlacesUtils.bookmarks;
+    // Create a new folder.
+    var folderId = bs.createFolder(bs.unfiledBookmarksFolder,
+                                   "Folder",
+                                   bs.DEFAULT_INDEX);
+    this._folderId = folderId;
+
+    // Add bookmarks in folder.
+    this.URIs.forEach(function(aURI) {
+      bs.insertBookmark(folderId,
+                        PlacesUtils._uri(aURI),
+                        bs.DEFAULT_INDEX,
+                        "Title");
+    });
+
+    // Select unsorted bookmarks root in the left pane.
+    gLibrary.PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks");
+    isnot(gLibrary.PlacesOrganizer._places.selectedNode, null,
+          "We correctly have selection in the Library left pane");
+    // Get our bookmark in the right pane.
+    var folderNode = gLibrary.ContentTree.view.view.nodeForTreeIndex(0);
+    is(folderNode.title, "Folder", "Found folder in the right pane");
+  },
+
+  finish() {
+    setTimeout(runNextTest, 0);
+  },
+
+  cleanup() {
+    PlacesUtils.bookmarks.removeItem(this._folderId);
+  }
+});
+
+// ------------------------------------------------------------------------------
+
+function test() {
+  waitForExplicitFinish();
+
+  // Sanity checks.
+  ok(PlacesUtils, "PlacesUtils in context");
+  ok(PlacesUIUtils, "PlacesUIUtils in context");
+
+  // Add tabs listeners.
+  gBrowser.tabContainer.addEventListener("TabOpen", gTabsListener);
+  gBrowser.addTabsProgressListener(gTabsListener);
+
+  // Temporary disable history, so we won't record pages navigation.
+  gPrefService.setBoolPref(ENABLE_HISTORY_PREF, false);
+
+  // Open Library window.
+  openLibrary(function(library) {
+    gLibrary = library;
+    // Kick off tests.
+    runNextTest();
+  });
+}
+
+function runNextTest() {
+  // Cleanup from previous test.
+  if (gCurrentTest)
+    gCurrentTest.cleanup();
+
+  if (gTests.length > 0) {
+    // Goto next test.
+    gCurrentTest = gTests.shift();
+    info("Start of test: " + gCurrentTest.desc);
+    // Test setup will set Library so that the bookmark to be opened is the
+    // first node in the content (right pane) tree.
+    gCurrentTest.setup();
+
+    gLibrary.focus();
+    waitForFocus(function() {
+      // Open the "Other Bookmarks" folder.
+      gLibrary.PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks");
+      gLibrary.PlacesOrganizer._places.selectedNode.containerOpen = true;
+      // Now middle-click on the bookmark contained with it.
+      let bookmarkedNode = gLibrary.PlacesOrganizer._places.selectedNode.getChild(0);
+      mouseEventOnCell(gLibrary.PlacesOrganizer._places,
+        gLibrary.PlacesOrganizer._places.view.treeIndexForNode(bookmarkedNode),
+        0,
+        { button: 1 });
+    }, gLibrary);
+  } else {
+    // No more tests.
+
+    // We must close "Other Bookmarks" ready for other tests.
+    gLibrary.PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks");
+    gLibrary.PlacesOrganizer._places.selectedNode.containerOpen = false;
+
+    // Close Library window.
+    gLibrary.close();
+
+    // Remove tabs listeners.
+    gBrowser.tabContainer.removeEventListener("TabOpen", gTabsListener);
+    gBrowser.removeTabsProgressListener(gTabsListener);
+
+    // Restore history.
+    try {
+      gPrefService.clearUserPref(ENABLE_HISTORY_PREF);
+    } catch (ex) {}
+
+    finish();
+  }
+}
+
+function mouseEventOnCell(aTree, aRowIndex, aColumnIndex, aEventDetails) {
+  var selection = aTree.view.selection;
+  selection.select(aRowIndex);
+  aTree.treeBoxObject.ensureRowIsVisible(aRowIndex);
+  var column = aTree.columns[aColumnIndex];
+
+  // get cell coordinates
+  var rect = aTree.treeBoxObject.getCoordsForCellItem(aRowIndex, column, "text");
+
+  EventUtils.synthesizeMouse(aTree.body, rect.x, rect.y,
+                             aEventDetails, gLibrary);
+}
--- a/browser/components/sessionstore/ContentRestore.jsm
+++ b/browser/components/sessionstore/ContentRestore.jsm
@@ -15,17 +15,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource:///modules/sessionstore/DocShellCapabilities.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FormData",
   "resource://gre/modules/FormData.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PageStyle",
   "resource:///modules/sessionstore/PageStyle.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ScrollPosition",
   "resource://gre/modules/ScrollPosition.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionHistory",
-  "resource:///modules/sessionstore/SessionHistory.jsm");
+  "resource://gre/modules/sessionstore/SessionHistory.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
   "resource:///modules/sessionstore/SessionStorage.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Utils",
   "resource://gre/modules/sessionstore/Utils.jsm");
 
 /**
  * This module implements the content side of session restoration. The chrome
  * side is handled by SessionStore.jsm. The functions in this module are called
--- a/browser/components/sessionstore/SessionStorage.jsm
+++ b/browser/components/sessionstore/SessionStorage.jsm
@@ -69,17 +69,24 @@ var SessionStorageInternal = {
     frameTree.forEach(frame => {
       let principal = getPrincipalForFrame(docShell, frame);
       if (!principal) {
         return;
       }
 
       // Get the origin of the current history entry
       // and use that as a key for the per-principal storage data.
-      let origin = principal.origin;
+      let origin;
+      try {
+        // The origin getter may throw for about:blank iframes as of bug 1340710,
+        // but we should ignore them anyway.
+        origin = principal.origin;
+      } catch (e) {
+        return;
+      }
       if (visitedOrigins.has(origin)) {
         // Don't read a host twice.
         return;
       }
 
       // Mark the current origin as visited.
       visitedOrigins.add(origin);
 
--- a/browser/components/sessionstore/content/content-sessionStore.js
+++ b/browser/components/sessionstore/content/content-sessionStore.js
@@ -23,17 +23,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 
 XPCOMUtils.defineLazyModuleGetter(this, "DocShellCapabilities",
   "resource:///modules/sessionstore/DocShellCapabilities.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PageStyle",
   "resource:///modules/sessionstore/PageStyle.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ScrollPosition",
   "resource://gre/modules/ScrollPosition.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionHistory",
-  "resource:///modules/sessionstore/SessionHistory.jsm");
+  "resource://gre/modules/sessionstore/SessionHistory.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
   "resource:///modules/sessionstore/SessionStorage.jsm");
 
 Cu.import("resource:///modules/sessionstore/FrameTree.jsm", this);
 var gFrameTree = new FrameTree(this);
 
 Cu.import("resource:///modules/sessionstore/ContentRestore.jsm", this);
 XPCOMUtils.defineLazyGetter(this, 'gContentRestore',
--- a/browser/components/sessionstore/moz.build
+++ b/browser/components/sessionstore/moz.build
@@ -28,17 +28,16 @@ EXTRA_JS_MODULES.sessionstore = [
     'FrameTree.jsm',
     'GlobalState.jsm',
     'PageStyle.jsm',
     'PrivacyFilter.jsm',
     'RecentlyClosedTabsAndWindowsMenuUtils.jsm',
     'RunState.jsm',
     'SessionCookies.jsm',
     'SessionFile.jsm',
-    'SessionHistory.jsm',
     'SessionMigration.jsm',
     'SessionSaver.jsm',
     'SessionStorage.jsm',
     'SessionStore.jsm',
     'SessionWorker.js',
     'SessionWorker.jsm',
     'StartupPerformance.jsm',
     'TabAttributes.jsm',
--- a/browser/config/version.txt
+++ b/browser/config/version.txt
@@ -1,1 +1,1 @@
-54.0a1
+55.0a1
--- a/browser/config/version_display.txt
+++ b/browser/config/version_display.txt
@@ -1,1 +1,1 @@
-54.0a1
+55.0a1
--- a/browser/extensions/mortar/host/common/ppapi-runtime.jsm
+++ b/browser/extensions/mortar/host/common/ppapi-runtime.jsm
@@ -3046,16 +3046,69 @@ dump(`callFromJSON: < ${JSON.stringify(c
     PPB_Instance_Private_ExecuteScript: function(json) {
       let instance = this.instances[json.instance];
       let script = String_PP_Var.getAsJSValue(json.script);
       let result = instance.mm.sendRpcMessage("ppapiflash.js:executeScript", script, { instance })[0];
       return [result, { exception: null }];
       //return [new PP_Var(), { exception: PP_Var.fromJSValue(e) }];
     },
 
+    /**
+    * PP_Resource Create([in] PP_Instance instance,
+    *                    [in] PP_InputEvent_Type type,
+    *                    [in] PP_TimeTicks time_stamp,
+    *                    [in] uint32_t modifiers,
+    *                    [in] uint32_t key_code,
+    *                    [in] PP_Var character_text,
+    *                    [in] PP_Var code);
+    */
+    PPB_KeyboardInputEvent_Create: function(json) {
+      let instance = this.instances[json.instance];
+      let charCode = 0;
+      if (PP_VarType[json.character_text.type] ==
+          PP_VarType.PP_VARTYPE_STRING) {
+        charCode = String_PP_Var.getAsJSValue(json.character_text).charCodeAt(0);
+      }
+      let location = instance.window.KeyboardEvent.DOM_KEY_LOCATION_STANDARD;
+      if (PP_InputEvent_Modifier.PP_INPUTEVENT_MODIFIER_ISLEFT &
+          json.modifiers) {
+        location = instance.window.KeyboardEvent.DOM_KEY_LOCATION_LEFT;
+      } else if (PP_InputEvent_Modifier.PP_INPUTEVENT_MODIFIER_ISRIGHT &
+                 json.modifiers) {
+        location = instance.window.KeyboardEvent.DOM_KEY_LOCATION_RIGHT;
+      } else if(PP_InputEvent_Modifier.PP_INPUTEVENT_MODIFIER_ISKEYPAD &
+                json.modifiers) {
+        location = instance.window.KeyboardEvent.DOM_KEY_LOCATION_NUMPAD;
+      }
+
+      // FIXME I skipped to put |PP_Var code| into keyboardEventInit here
+      // because I neither find any useful |code| value passing into here
+      // nor have any PPB APIs which gets access to |code| now.
+      let keyboardEventInit = {
+        altKey: PP_InputEvent_Modifier.PP_INPUTEVENT_MODIFIER_ALTKEY &
+          json.modifiers,
+        charCode: charCode,
+        ctrlKey: PP_InputEvent_Modifier.PP_INPUTEVENT_MODIFIER_CONTROLKEY &
+          json.modifiers,
+        keyCode: json.key_code,
+        location: location,
+        metaKey: PP_InputEvent_Modifier.PP_INPUTEVENT_MODIFIER_METAKEY &
+          json.modifiers,
+        repeat: PP_InputEvent_Modifier.PP_INPUTEVENT_MODIFIER_ISAUTOREPEAT &
+          json.modifiers,
+        shiftKey: PP_InputEvent_Modifier.PP_INPUTEVENT_MODIFIER_SHIFTKEY &
+          json.modifiers,
+      };
+      let eventName = EventByTypes.get(PP_InputEvent_Type[json.type]);
+      let event = new instance.window.KeyboardEvent(eventName,
+                                                    keyboardEventInit);
+      let resource = new KeyboardInputEvent(instance, event);
+      resource.timeStamp = json.time_stamp;
+      return resource;
+    },
 
     /**
      * PP_Bool IsKeyboardInputEvent([in] PP_Resource resource);
      */
     PPB_KeyboardInputEvent_IsKeyboardInputEvent: function(json) {
       let resource = PP_Resource.lookup(json.resource);
       return resource instanceof KeyboardInputEvent ? PP_Bool.PP_TRUE : PP_Bool.PP_FALSE;
     },
--- a/browser/extensions/mortar/host/pdf/bootstrap.js
+++ b/browser/extensions/mortar/host/pdf/bootstrap.js
@@ -7,63 +7,72 @@ const { classes: Cc, interfaces: Ci, uti
 Cu.import("resource://gre/modules/Services.jsm");
 
 function sandboxScript(sandbox)
 {
   dump("sandboxScript " + sandbox.pluginElement + "\n");
   Components.classes["@mozilla.org/moz/jssubscript-loader;1"].getService(Components.interfaces.mozIJSSubScriptLoader).loadSubScript("resource://ppapipdf.js/ppapi-content-sandbox.js", sandbox);
 }
 
-let plugins;
-function startup(data) {
-  dump(">>>STARTED!!!\n");
-
-  let root = data.installPath.parent.parent;
-  let rpclib = root.clone();
-  let pluginlib = root.clone();
-  let os = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
-  if (os == "Darwin") {
-    rpclib.appendRelativePath("ppapi/out/rpc.dylib");
-    pluginlib.appendRelativePath("plugin/libpepperpdfium.dylib");
-  } else if (os == "Linux") {
-    rpclib.appendRelativePath("ppapi/out/rpc.so");
-    pluginlib.appendRelativePath("plugin/libpepperpdfium.so");
-  } else if (os == "WINNT") {
-    rpclib.appendRelativePath("ppapi\\out\\rpc.dll");
-    pluginlib.appendRelativePath("plugin\\pepperpdfium.dll");
-  } else {
-    throw("Don't know the path to the libraries for this OS!");
-  }
-  rpclib = rpclib.path;
-  pluginlib = pluginlib.path;
+const handlerURI = "chrome://ppapipdf.js/content/viewer.html";
+let pdfium = {
+  init(bootstrapData) {
+    let root = bootstrapData.installPath.parent.parent;
+    let rpclib = root.clone();
+    let pluginlib = root.clone();
+    let os = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
+    if (os == "Darwin") {
+      rpclib.appendRelativePath("ppapi/out/rpc.dylib");
+      pluginlib.appendRelativePath("plugin/libpepperpdfium.dylib");
+    } else if (os == "Linux") {
+      rpclib.appendRelativePath("ppapi/out/rpc.so");
+      pluginlib.appendRelativePath("plugin/libpepperpdfium.so");
+    } else if (os == "WINNT") {
+      rpclib.appendRelativePath("ppapi\\out\\rpc.dll");
+      pluginlib.appendRelativePath("plugin\\pepperpdfium.dll");
+    } else {
+      throw("Don't know the path to the libraries for this OS!");
+    }
+    rpclib = rpclib.path;
+    pluginlib = pluginlib.path;
 
-  let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
-  let plugin = pluginHost.registerFakePlugin({
-    handlerURI: "chrome://ppapipdf.js/content/viewer.html",
-    mimeEntries: [
-      { type: "application/pdf", extension: "pdf" },
-      { type: "application/vnd.adobe.pdf", extension: "pdf" },
-      { type: "application/vnd.adobe.pdfxml", extension: "pdfxml" },
-      { type: "application/vnd.adobe.x-mars", extension: "mars" },
-      { type: "application/vnd.adobe.xdp+xml", extension: "xdp" },
-      { type: "application/vnd.adobe.xfdf", extension: "xfdf" },
-      { type: "application/vnd.adobe.xfd+xml", extension: "xfd" },
-      { type: "application/vnd.fdf", extension: "fdf" },
-    ],
-    name: "PPAPI PDF plugin",
-    niceName: "PPAPI PDF plugin",
-    version: "1.0",
-    sandboxScript : `(${sandboxScript.toSource()})(this);`,
-    ppapiProcessArgs: [ rpclib, pluginlib ],
-  });
-  plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+    let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+    let plugin = pluginHost.registerFakePlugin({
+      handlerURI: handlerURI,
+      mimeEntries: [
+        { type: "application/pdf", extension: "pdf" },
+        { type: "application/vnd.adobe.pdf", extension: "pdf" },
+        { type: "application/vnd.adobe.pdfxml", extension: "pdfxml" },
+        { type: "application/vnd.adobe.x-mars", extension: "mars" },
+        { type: "application/vnd.adobe.xdp+xml", extension: "xdp" },
+        { type: "application/vnd.adobe.xfdf", extension: "xfdf" },
+        { type: "application/vnd.adobe.xfd+xml", extension: "xfd" },
+        { type: "application/vnd.fdf", extension: "fdf" },
+      ],
+      name: "PPAPI PDF plugin",
+      niceName: "PPAPI PDF plugin",
+      version: "1.0",
+      sandboxScript : `(${sandboxScript.toSource()})(this);`,
+      ppapiProcessArgs: [ rpclib, pluginlib ],
+    });
+    plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+    Services.ppmm.addMessageListener("ppapi.js:generateRandomBytes", this.generateRandomBytesListener);
+  },
+  uninit() {
+    let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+    pluginHost.unregisterFakePlugin(handlerURI);
+    Services.ppmm.removeMessageListener("ppapi.js:generateRandomBytes", this.generateRandomBytesListener);
+  },
+  generateRandomBytesListener(data) {
+    let rng = Cc["@mozilla.org/security/random-generator;1"].createInstance(Ci.nsIRandomGenerator);
+    return rng.generateRandomBytes(data);
+  },
+}
 
-  let rng = Cc["@mozilla.org/security/random-generator;1"].createInstance(Ci.nsIRandomGenerator);
-  Services.ppmm.addMessageListener("ppapi.js:generateRandomBytes", ({ data }) => {
-    return rng.generateRandomBytes(data);
-  });
-
+function startup(aData) {
+  dump(">>>STARTED!!!\n");
+  pdfium.init(aData);
   dump("<<<STARTED!!!\n");
 }
-
 function shutdown() {
   dump("SHUTDOWN!!!\n");
+  pdfium.uninit();
 }
--- a/browser/extensions/mortar/host/rpc.h
+++ b/browser/extensions/mortar/host/rpc.h
@@ -469,21 +469,27 @@ static void FromJSON_str_t(JSONIterator&
             tokenValue.replace(it, next + 1, "\n");
             break;
           case 't':
             tokenValue.replace(it, next + 1, "\t");
             break;
           case 'u':
             if (tokenValue.cend() - next >= 5) {
               if (*(next + 1) == '0' &&
-                  *(next + 2) == '0' &&
-                  *(next + 3) == '0' &&
-                  *(next + 4) == '0') {
-                tokenValue.replace(it, next + 5, 1, '\0');
-                break;
+                  *(next + 2) == '0') {
+                unsigned int v;
+                std::stringstream x;
+                x << *(next + 3) << *(next + 4);
+                x >> std::hex >> v;
+                // Handle Control characters code units
+                // from U+0000 to U+001F.
+                if ( v < 0x0020) {
+                  tokenValue.replace(it, next + 5, 1, v);
+                  break;
+                }
               }
               Fail("Need to handle unicode escapes in strings: %s.",
                    tokenValue.c_str());
             }
         }
       }
     }
     value = (char*) malloc(length + 1);
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -438,16 +438,18 @@ offlineApps.usage=This website (%S) is n
 offlineApps.manageUsage=Show settings
 offlineApps.manageUsageAccessKey=S
 
 identity.identified.verifier=Verified by: %S
 identity.identified.verified_by_you=You have added a security exception for this site.
 identity.identified.state_and_country=%S, %S
 
 identity.icon.tooltip=Show site information
+identity.showDetails.tooltip=Show connection details
+identity.hideDetails.tooltip=Hide connection details
 
 trackingProtection.intro.title=How Tracking Protection works
 # LOCALIZATION NOTE (trackingProtection.intro.description2):
 # %S is brandShortName. This string should match the one from Step 1 of the tour
 # when it starts from the button shown when a new private window is opened.
 trackingProtection.intro.description2=When you see the shield, %S is blocking some parts of the page that could track your browsing activity.
 # LOCALIZATION NOTE (trackingProtection.intro.step1of3): Indicates that the intro panel is step one of three in a tour.
 trackingProtection.intro.step1of3=1 of 3
--- a/browser/modules/DirectoryLinksProvider.jsm
+++ b/browser/modules/DirectoryLinksProvider.jsm
@@ -190,17 +190,17 @@ var DirectoryLinksProvider = {
   get locale() {
     let matchOS;
     try {
       matchOS = Services.prefs.getBoolPref(PREF_MATCH_OS_LOCALE);
     } catch (e) {}
 
     if (matchOS) {
       return Cc["@mozilla.org/intl/ospreferences;1"].
-             getService(Ci.mozIOSPreferences).getSystemLocale();
+             getService(Ci.mozIOSPreferences).systemLocale;
     }
 
     try {
       let locale = Services.prefs.getComplexValue(PREF_SELECTED_LOCALE,
                                                   Ci.nsIPrefLocalizedString);
       if (locale) {
         return locale.data;
       }
--- a/browser/modules/ExtensionsUI.jsm
+++ b/browser/modules/ExtensionsUI.jsm
@@ -28,18 +28,21 @@ const BROWSER_PROPERTIES = "chrome://bro
 const BRAND_PROPERTIES = "chrome://branding/locale/brand.properties";
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
 this.ExtensionsUI = {
   sideloaded: new Set(),
   updates: new Set(),
   sideloadListener: null,
+  histogram: null,
 
   init() {
+    this.histogram = Services.telemetry.getHistogramById("EXTENSION_INSTALL_PROMPT_RESULT");
+
     Services.obs.addObserver(this, "webextension-permission-prompt", false);
     Services.obs.addObserver(this, "webextension-update-permissions", false);
     Services.obs.addObserver(this, "webextension-install-notify", false);
 
     this._checkForSideloaded();
   },
 
   _checkForSideloaded() {
@@ -83,43 +86,44 @@ this.ExtensionsUI = {
         let win = RecentWindow.getMostRecentBrowserWindow();
         for (let addon of sideloaded) {
           win.openUILinkIn(`about:newaddon?id=${addon.id}`, "tab");
         }
       }
     });
   },
 
-  showAddonsManager(browser, strings, icon) {
+  showAddonsManager(browser, strings, icon, histkey) {
     let global = browser.selectedBrowser.ownerGlobal;
     return global.BrowserOpenAddonsMgr("addons://list/extension").then(aomWin => {
       let aomBrowser = aomWin.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIDocShell)
                              .chromeEventHandler;
-      return this.showPermissionsPrompt(aomBrowser, strings, icon);
+      return this.showPermissionsPrompt(aomBrowser, strings, icon, histkey);
     });
   },
 
   showSideloaded(browser, addon) {
     addon.markAsSeen();
     this.sideloaded.delete(addon);
     this.emit("change");
 
     let strings = this._buildStrings({
       addon,
       permissions: addon.userPermissions,
       type: "sideload",
     });
-    this.showAddonsManager(browser, strings, addon.iconURL).then(answer => {
-      addon.userDisabled = !answer;
-    });
+    this.showAddonsManager(browser, strings, addon.iconURL, "sideload")
+        .then(answer => {
+          addon.userDisabled = !answer;
+        });
   },
 
   showUpdate(browser, info) {
-    this.showAddonsManager(browser, info.strings, info.addon.iconURL)
+    this.showAddonsManager(browser, info.strings, info.addon.iconURL, "update")
         .then(answer => {
           if (answer) {
             info.resolve();
           } else {
             info.reject();
           }
           // At the moment, this prompt will re-appear next time we do an update
           // check.  See bug 1332360 for proposal to avoid this.
@@ -142,23 +146,37 @@ this.ExtensionsUI = {
 
       let strings = this._buildStrings(info);
       // If this is an update with no promptable permissions, just apply it
       if (info.type == "update" && strings.msgs.length == 0) {
         info.resolve();
         return;
       }
 
-      this.showPermissionsPrompt(target, strings, info.icon).then(answer => {
-        if (answer) {
-          info.resolve();
-        } else {
-          info.reject();
-        }
-      });
+      let histkey;
+      if (info.type == "sideload") {
+        histkey = "sideload";
+      } else if (info.type == "update") {
+        histkey = "update";
+      } else if (info.source == "AMO") {
+        histkey = "installAmo";
+      } else if (info.source == "local") {
+        histkey = "installLocal";
+      } else {
+        histkey = "installWeb";
+      }
+
+      this.showPermissionsPrompt(target, strings, info.icon, histkey)
+          .then(answer => {
+            if (answer) {
+              info.resolve();
+            } else {
+              info.reject();
+            }
+          });
     } else if (topic == "webextension-update-permissions") {
       let info = subject.wrappedJSObject;
       info.type = "update";
       let strings = this._buildStrings(info);
 
       // If we don't prompt for any new permissions, just apply it
       if (strings.msgs.length == 0) {
         info.resolve();
@@ -302,17 +320,17 @@ this.ExtensionsUI = {
       result.text = bundle.formatStringFromName("webextPerms.updateText", [addonName], 1);
       result.acceptText = bundle.GetStringFromName("webextPerms.updateAccept.label");
       result.acceptKey = bundle.GetStringFromName("webextPerms.updateAccept.accessKey");
     }
 
     return result;
   },
 
-  showPermissionsPrompt(browser, strings, icon) {
+  showPermissionsPrompt(browser, strings, icon, histkey) {
     function eventCallback(topic) {
       if (topic == "showing") {
         let doc = this.browser.ownerDocument;
         doc.getElementById("addon-webext-perm-header").innerHTML = strings.header;
 
         let textEl = doc.getElementById("addon-webext-perm-text");
         textEl.innerHTML = strings.text;
         textEl.hidden = !strings.text;
@@ -344,23 +362,29 @@ this.ExtensionsUI = {
       eventCallback,
     };
 
     let win = browser.ownerGlobal;
     return new Promise(resolve => {
       let action = {
         label: strings.acceptText,
         accessKey: strings.acceptKey,
-        callback: () => resolve(true),
+        callback: () => {
+          this.histogram.add(histkey + "Accepted");
+          resolve(true);
+        },
       };
       let secondaryActions = [
         {
           label: strings.cancelText,
           accessKey: strings.cancelKey,
-          callback: () => resolve(false),
+          callback: () => {
+            this.histogram.add(histkey + "Rejected");
+            resolve(false);
+          },
         },
       ];
 
       win.PopupNotifications.show(browser, "addon-webext-permissions", "",
                                   "addons-notification-icon",
                                   action, secondaryActions, popupOptions);
     });
   },
--- a/browser/modules/test/browser/browser_UsageTelemetry_content.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_content.js
@@ -76,17 +76,17 @@ add_task(function* test_context_menu() {
   Assert.equal(Object.keys(scalars[SCALAR_CONTEXT_MENU]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, "other-MozSearch.contextmenu", 1);
 
   // Also check events.
   let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
-  events = events.filter(e => e[1] == "navigation" && e[2] == "search");
+  events = (events.default || []).filter(e => e[1] == "navigation" && e[2] == "search");
   checkEvents(events, [["navigation", "search", "contextmenu", null, {engine: "other-MozSearch"}]]);
 
   contextMenu.hidePopup();
   yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
   yield BrowserTestUtils.removeTab(tab);
 });
 
 add_task(function* test_about_newtab() {
@@ -112,13 +112,13 @@ add_task(function* test_about_newtab() {
   Assert.equal(Object.keys(scalars[SCALAR_ABOUT_NEWTAB]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, "other-MozSearch.newtab", 1);
 
   // Also check events.
   let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
-  events = events.filter(e => e[1] == "navigation" && e[2] == "search");
+  events = (events.default || []).filter(e => e[1] == "navigation" && e[2] == "search");
   checkEvents(events, [["navigation", "search", "about_newtab", "enter", {engine: "other-MozSearch"}]]);
 
   yield BrowserTestUtils.removeTab(tab);
 });
--- a/browser/modules/test/browser/browser_UsageTelemetry_content_aboutHome.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_content_aboutHome.js
@@ -76,13 +76,13 @@ add_task(function* test_abouthome_simple
   Assert.equal(Object.keys(scalars[SCALAR_ABOUT_HOME]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, "other-MozSearch.abouthome", 1);
 
   // Also check events.
   let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
-  events = events.filter(e => e[1] == "navigation" && e[2] == "search");
+  events = (events.default || []).filter(e => e[1] == "navigation" && e[2] == "search");
   checkEvents(events, [["navigation", "search", "about_home", "enter", {engine: "other-MozSearch"}]]);
 
   yield BrowserTestUtils.removeTab(tab);
 });
--- a/browser/modules/test/browser/browser_UsageTelemetry_searchbar.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_searchbar.js
@@ -102,17 +102,17 @@ add_task(function* test_plainQuery() {
   Assert.equal(Object.keys(scalars[SCALAR_SEARCHBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, "other-MozSearch.searchbar", 1);
 
   // Also check events.
   let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
-  events = events.filter(e => e[1] == "navigation" && e[2] == "search");
+  events = (events.default || []).filter(e => e[1] == "navigation" && e[2] == "search");
   checkEvents(events, [["navigation", "search", "searchbar", "enter", {engine: "other-MozSearch"}]]);
 
   yield BrowserTestUtils.removeTab(tab);
 });
 
 add_task(function* test_oneOff() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
@@ -136,17 +136,17 @@ add_task(function* test_oneOff() {
   Assert.equal(Object.keys(scalars[SCALAR_SEARCHBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, "other-MozSearch2.searchbar", 1);
 
   // Also check events.
   let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
-  events = events.filter(e => e[1] == "navigation" && e[2] == "search");
+  events = (events.default || []).filter(e => e[1] == "navigation" && e[2] == "search");
   checkEvents(events, [["navigation", "search", "searchbar", "oneoff", {engine: "other-MozSearch2"}]]);
 
   yield BrowserTestUtils.removeTab(tab);
 });
 
 add_task(function* test_suggestion() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
@@ -182,15 +182,15 @@ add_task(function* test_suggestion() {
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   let searchEngineId = "other-" + suggestionEngine.name;
   checkKeyedHistogram(search_hist, searchEngineId + ".searchbar", 1);
 
   // Also check events.
   let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
-  events = events.filter(e => e[1] == "navigation" && e[2] == "search");
+  events = (events.default || []).filter(e => e[1] == "navigation" && e[2] == "search");
   checkEvents(events, [["navigation", "search", "searchbar", "suggestion", {engine: searchEngineId}]]);
 
   Services.search.currentEngine = previousEngine;
   Services.search.removeEngine(suggestionEngine);
   yield BrowserTestUtils.removeTab(tab);
 });
--- a/browser/modules/test/browser/browser_UsageTelemetry_urlbar.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_urlbar.js
@@ -121,17 +121,17 @@ add_task(function* test_simpleQuery() {
   Assert.equal(Object.keys(scalars[SCALAR_URLBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, "other-MozSearch.urlbar", 1);
 
   // Also check events.
   let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
-  events = events.filter(e => e[1] == "navigation" && e[2] == "search");
+  events = (events.default || []).filter(e => e[1] == "navigation" && e[2] == "search");
   checkEvents(events, [["navigation", "search", "urlbar", "enter", {engine: "other-MozSearch"}]]);
 
   // Check the histograms as well.
   let resultIndexes = resultIndexHist.snapshot();
   checkHistogramResults(resultIndexes, 0, "FX_URLBAR_SELECTED_RESULT_INDEX");
 
   let resultTypes = resultTypeHist.snapshot();
   checkHistogramResults(resultTypes,
@@ -166,17 +166,17 @@ add_task(function* test_searchAlias() {
   Assert.equal(Object.keys(scalars[SCALAR_URLBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, "other-MozSearch.urlbar", 1);
 
   // Also check events.
   let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
-  events = events.filter(e => e[1] == "navigation" && e[2] == "search");
+  events = (events.default || []).filter(e => e[1] == "navigation" && e[2] == "search");
   checkEvents(events, [["navigation", "search", "urlbar", "alias", {engine: "other-MozSearch"}]]);
 
   // Check the histograms as well.
   let resultIndexes = resultIndexHist.snapshot();
   checkHistogramResults(resultIndexes, 0, "FX_URLBAR_SELECTED_RESULT_INDEX");
 
   let resultTypes = resultTypeHist.snapshot();
   checkHistogramResults(resultTypes,
@@ -214,17 +214,17 @@ add_task(function* test_oneOff() {
   Assert.equal(Object.keys(scalars[SCALAR_URLBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, "other-MozSearch.urlbar", 1);
 
   // Also check events.
   let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
-  events = events.filter(e => e[1] == "navigation" && e[2] == "search");
+  events = (events.default || []).filter(e => e[1] == "navigation" && e[2] == "search");
   checkEvents(events, [["navigation", "search", "urlbar", "oneoff", {engine: "other-MozSearch"}]]);
 
   // Check the histograms as well.
   let resultIndexes = resultIndexHist.snapshot();
   checkHistogramResults(resultIndexes, 0, "FX_URLBAR_SELECTED_RESULT_INDEX");
 
   let resultTypes = resultTypeHist.snapshot();
   checkHistogramResults(resultTypes,
@@ -274,17 +274,17 @@ add_task(function* test_suggestion() {
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   let searchEngineId = "other-" + suggestionEngine.name;
   checkKeyedHistogram(search_hist, searchEngineId + ".urlbar", 1);
 
   // Also check events.
   let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
-  events = events.filter(e => e[1] == "navigation" && e[2] == "search");
+  events = (events.default || []).filter(e => e[1] == "navigation" && e[2] == "search");
   checkEvents(events, [["navigation", "search", "urlbar", "suggestion", {engine: searchEngineId}]]);
 
   // Check the histograms as well.
   let resultIndexes = resultIndexHist.snapshot();
   checkHistogramResults(resultIndexes, 3, "FX_URLBAR_SELECTED_RESULT_INDEX");
 
   let resultTypes = resultTypeHist.snapshot();
   checkHistogramResults(resultTypes,
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -799,16 +799,17 @@ toolbarpaletteitem[place="palette"] > to
 
 .PanelUI-remotetabs-notabsforclient-label {
   color: GrayText;
   /* This margin is to line this label up with the labels in toolbarbuttons. */
   margin-left: 28px;
 }
 
 .fxaSyncIllustration {
+  width: 180px;
   height: var(--panel-ui-sync-illustration-height);
 }
 
 .PanelUI-remotetabs-prefs-button > .toolbarbutton-text {
   /* !important to override ".cui-widget-panel toolbarbutton > .toolbarbutton-text" above. */
   text-align: center !important;
   text-shadow: none;
 }
--- a/browser/themes/shared/fxa/sync-illustration.svg
+++ b/browser/themes/shared/fxa/sync-illustration.svg
@@ -1,13 +1,13 @@
 <?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/. -->
-<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid" width="320" height="280" viewBox="0 0 320 280" xmlns:xlink="http://www.w3.org/1999/xlink" >
+<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid" viewBox="0 0 320 280" xmlns:xlink="http://www.w3.org/1999/xlink" >
 <style>
 	#blueFill:target ~ use,
 	#blueFill:target ~ g {
 		fill: #bfcbd3;
 	}
 
 	svg {
 		fill:#cdcdcd;
--- a/caps/BasePrincipal.cpp
+++ b/caps/BasePrincipal.cpp
@@ -51,17 +51,16 @@ OriginAttributes::InitPrefs()
 }
 
 void
 OriginAttributes::Inherit(const OriginAttributes& aAttrs)
 {
   mAppId = aAttrs.mAppId;
   mInIsolatedMozBrowser = aAttrs.mInIsolatedMozBrowser;
 
-  StripAttributes(STRIP_ADDON_ID);
 
   mUserContextId = aAttrs.mUserContextId;
 
   mPrivateBrowsingId = aAttrs.mPrivateBrowsingId;
   mFirstPartyDomain = aAttrs.mFirstPartyDomain;
 }
 
 void
@@ -91,39 +90,28 @@ OriginAttributes::CreateSuffix(nsACStrin
 {
   UniquePtr<URLParams> params(new URLParams());
   nsAutoString value;
 
   //
   // Important: While serializing any string-valued attributes, perform a
   // release-mode assertion to make sure that they don't contain characters that
   // will break the quota manager when it uses the serialization for file
-  // naming (see addonId below).
+  // naming.
   //
 
   if (mAppId != nsIScriptSecurityManager::NO_APP_ID) {
     value.AppendInt(mAppId);
     params->Set(NS_LITERAL_STRING("appId"), value);
   }
 
   if (mInIsolatedMozBrowser) {
     params->Set(NS_LITERAL_STRING("inBrowser"), NS_LITERAL_STRING("1"));
   }
 
-  if (!mAddonId.IsEmpty()) {
-    if (mAddonId.FindCharInSet(dom::quota::QuotaManager::kReplaceChars) != kNotFound) {
-#ifdef MOZ_CRASHREPORTER
-      CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("Crash_AddonId"),
-                                         NS_ConvertUTF16toUTF8(mAddonId));
-#endif
-      MOZ_CRASH();
-    }
-    params->Set(NS_LITERAL_STRING("addonId"), mAddonId);
-  }
-
   if (mUserContextId != nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID) {
     value.Truncate();
     value.AppendInt(mUserContextId);
     params->Set(NS_LITERAL_STRING("userContextId"), value);
   }
 
 
   if (mPrivateBrowsingId) {
@@ -199,18 +187,18 @@ public:
         return false;
       }
 
       mOriginAttributes->mInIsolatedMozBrowser = true;
       return true;
     }
 
     if (aName.EqualsLiteral("addonId")) {
-      MOZ_RELEASE_ASSERT(mOriginAttributes->mAddonId.IsEmpty());
-      mOriginAttributes->mAddonId.Assign(aValue);
+      // No longer supported. Silently ignore so that legacy origin strings
+      // don't cause failures.
       return true;
     }
 
     if (aName.EqualsLiteral("userContextId")) {
       nsresult rv;
       int64_t val  = aValue.ToInteger64(&rv);
       NS_ENSURE_SUCCESS(rv, false);
       NS_ENSURE_TRUE(val <= UINT32_MAX, false);
@@ -294,121 +282,111 @@ OriginAttributes::IsPrivateBrowsing(cons
   OriginAttributes attrs;
   if (NS_WARN_IF(!attrs.PopulateFromOrigin(aOrigin, dummy))) {
     return false;
   }
 
   return !!attrs.mPrivateBrowsingId;
 }
 
-BasePrincipal::BasePrincipal()
+BasePrincipal::BasePrincipal(PrincipalKind aKind)
+  : mKind(aKind)
+  , mDomainSet(false)
 {}
 
 BasePrincipal::~BasePrincipal()
 {}
 
 NS_IMETHODIMP
 BasePrincipal::GetOrigin(nsACString& aOrigin)
 {
-  nsresult rv = GetOriginInternal(aOrigin);
+  nsresult rv = GetOriginNoSuffix(aOrigin);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsAutoCString suffix;
-  mOriginAttributes.CreateSuffix(suffix);
+  rv = GetOriginSuffix(suffix);
+  NS_ENSURE_SUCCESS(rv, rv);
   aOrigin.Append(suffix);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 BasePrincipal::GetOriginNoSuffix(nsACString& aOrigin)
 {
+  if (mOriginNoSuffix) {
+    return mOriginNoSuffix->ToUTF8String(aOrigin);
+  }
   return GetOriginInternal(aOrigin);
 }
 
 bool
 BasePrincipal::Subsumes(nsIPrincipal* aOther, DocumentDomainConsideration aConsideration)
 {
   MOZ_ASSERT(aOther);
+  MOZ_ASSERT_IF(Kind() == eCodebasePrincipal, mOriginSuffix);
 
   // Expanded principals handle origin attributes for each of their
   // sub-principals individually, null principals do only simple checks for
   // pointer equality, and system principals are immune to origin attributes
   // checks, so only do this check for codebase principals.
   if (Kind() == eCodebasePrincipal &&
-      OriginAttributesRef() != Cast(aOther)->OriginAttributesRef()) {
+      mOriginSuffix != Cast(aOther)->mOriginSuffix) {
     return false;
   }
 
   return SubsumesInternal(aOther, aConsideration);
 }
 
 NS_IMETHODIMP
 BasePrincipal::Equals(nsIPrincipal *aOther, bool *aResult)
 {
   NS_ENSURE_TRUE(aOther, NS_ERROR_INVALID_ARG);
-  *aResult = Subsumes(aOther, DontConsiderDocumentDomain) &&
-             Cast(aOther)->Subsumes(this, DontConsiderDocumentDomain);
+
+  *aResult = FastEquals(aOther);
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 BasePrincipal::EqualsConsideringDomain(nsIPrincipal *aOther, bool *aResult)
 {
   NS_ENSURE_TRUE(aOther, NS_ERROR_INVALID_ARG);
-  *aResult = Subsumes(aOther, ConsiderDocumentDomain) &&
-             Cast(aOther)->Subsumes(this, ConsiderDocumentDomain);
-  return NS_OK;
-}
-
-bool
-BasePrincipal::EqualsIgnoringAddonId(nsIPrincipal *aOther)
-{
-  MOZ_ASSERT(aOther);
 
-  // Note that this will not work for expanded principals, nor is it intended
-  // to.
-  if (!dom::ChromeUtils::IsOriginAttributesEqualIgnoringAddonId(
-          OriginAttributesRef(), Cast(aOther)->OriginAttributesRef())) {
-    return false;
-  }
+  *aResult = FastEqualsConsideringDomain(aOther);
 
-  return SubsumesInternal(aOther, DontConsiderDocumentDomain) &&
-         Cast(aOther)->SubsumesInternal(this, DontConsiderDocumentDomain);
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 BasePrincipal::Subsumes(nsIPrincipal *aOther, bool *aResult)
 {
   NS_ENSURE_TRUE(aOther, NS_ERROR_INVALID_ARG);
-  *aResult = Subsumes(aOther, DontConsiderDocumentDomain);
+
+  *aResult = FastSubsumes(aOther);
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 BasePrincipal::SubsumesConsideringDomain(nsIPrincipal *aOther, bool *aResult)
 {
   NS_ENSURE_TRUE(aOther, NS_ERROR_INVALID_ARG);
-  *aResult = Subsumes(aOther, ConsiderDocumentDomain);
+
+  *aResult = FastSubsumesConsideringDomain(aOther);
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 BasePrincipal::SubsumesConsideringDomainIgnoringFPD(nsIPrincipal *aOther,
                                                     bool *aResult)
 {
   NS_ENSURE_TRUE(aOther, NS_ERROR_INVALID_ARG);
 
-  if (Kind() == eCodebasePrincipal &&
-      !dom::ChromeUtils::IsOriginAttributesEqualIgnoringFPD(
-            OriginAttributesRef(), aOther->OriginAttributesRef())) {
-    *aResult = false;
-    return NS_OK;
-  }
-
-  *aResult = SubsumesInternal(aOther, ConsiderDocumentDomain);
+  *aResult = FastSubsumesConsideringDomainIgnoringFPD(aOther);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 BasePrincipal::CheckMayLoad(nsIURI* aURI, bool aReport, bool aAllowIfInheritsPrincipal)
 {
   // Check the internal method first, which allows us to quickly approve loads
@@ -554,18 +532,18 @@ BasePrincipal::GetOriginAttributes(JSCon
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 BasePrincipal::GetOriginSuffix(nsACString& aOriginAttributes)
 {
-  mOriginAttributes.CreateSuffix(aOriginAttributes);
-  return NS_OK;
+  MOZ_ASSERT(mOriginSuffix);
+  return mOriginSuffix->ToUTF8String(aOriginAttributes);
 }
 
 NS_IMETHODIMP
 BasePrincipal::GetAppStatus(uint16_t* aAppStatus)
 {
   // TODO: Remove GetAppStatus.
   *aAppStatus = nsIPrincipal::APP_STATUS_NOT_INSTALLED;
   return NS_OK;
@@ -580,23 +558,16 @@ BasePrincipal::GetAppId(uint32_t* aAppId
     return NS_OK;
   }
 
   *aAppId = AppId();
   return NS_OK;
 }
 
 NS_IMETHODIMP
-BasePrincipal::GetAddonId(nsAString& aAddonId)
-{
-  aAddonId.Assign(mOriginAttributes.mAddonId);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 BasePrincipal::GetUserContextId(uint32_t* aUserContextId)
 {
   *aUserContextId = UserContextId();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 BasePrincipal::GetPrivateBrowsingId(uint32_t* aPrivateBrowsingId)
@@ -617,25 +588,29 @@ BasePrincipal::GetUnknownAppId(bool* aUn
 {
   *aUnknownAppId = AppId() == nsIScriptSecurityManager::UNKNOWN_APP_ID;
   return NS_OK;
 }
 
 bool
 BasePrincipal::AddonHasPermission(const nsAString& aPerm)
 {
-  if (mOriginAttributes.mAddonId.IsEmpty()) {
+  nsAutoString addonId;
+  NS_ENSURE_SUCCESS(GetAddonId(addonId), false);
+
+  if (addonId.IsEmpty()) {
     return false;
   }
+
   nsCOMPtr<nsIAddonPolicyService> aps =
     do_GetService("@mozilla.org/addons/policy-service;1");
   NS_ENSURE_TRUE(aps, false);
 
   bool retval = false;
-  nsresult rv = aps->AddonHasPermission(mOriginAttributes.mAddonId, aPerm, &retval);
+  nsresult rv = aps->AddonHasPermission(addonId, aPerm, &retval);
   NS_ENSURE_SUCCESS(rv, false);
   return retval;
 }
 
 already_AddRefed<BasePrincipal>
 BasePrincipal::CreateCodebasePrincipal(nsIURI* aURI, const OriginAttributes& aAttrs)
 {
   // If the URI is supposed to inherit the security context of whoever loads it,
@@ -704,21 +679,45 @@ BasePrincipal::CloneStrippingUserContext
   NS_ENSURE_SUCCESS(rv, nullptr);
 
   return BasePrincipal::CreateCodebasePrincipal(uri, attrs);
 }
 
 bool
 BasePrincipal::AddonAllowsLoad(nsIURI* aURI, bool aExplicit /* = false */)
 {
-  if (mOriginAttributes.mAddonId.IsEmpty()) {
+  nsAutoString addonId;
+  NS_ENSURE_SUCCESS(GetAddonId(addonId), false);
+
+  if (addonId.IsEmpty()) {
     return false;
   }
 
   nsCOMPtr<nsIAddonPolicyService> aps = do_GetService("@mozilla.org/addons/policy-service;1");
   NS_ENSURE_TRUE(aps, false);
 
   bool allowed = false;
-  nsresult rv = aps->AddonMayLoadURI(mOriginAttributes.mAddonId, aURI, aExplicit, &allowed);
+  nsresult rv = aps->AddonMayLoadURI(addonId, aURI, aExplicit, &allowed);
   return NS_SUCCEEDED(rv) && allowed;
 }
 
+void
+BasePrincipal::FinishInit()
+{
+  // First compute the origin suffix since it's infallible.
+  nsAutoCString originSuffix;
+  mOriginAttributes.CreateSuffix(originSuffix);
+  mOriginSuffix = NS_Atomize(originSuffix);
+
+  // Then compute the origin without the suffix.
+  nsAutoCString originNoSuffix;
+  nsresult rv = GetOriginInternal(originNoSuffix);
+  if (NS_FAILED(rv)) {
+    // If GetOriginInternal fails, we will get a null atom for mOriginNoSuffix,
+    // which we deal with anywhere mOriginNoSuffix is used.
+    // Once this is made infallible we can remove those null checks.
+    mOriginNoSuffix = nullptr;
+    return;
+  }
+  mOriginNoSuffix = NS_Atomize(originNoSuffix);
+}
+
 } // namespace mozilla
--- a/caps/BasePrincipal.h
+++ b/caps/BasePrincipal.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_BasePrincipal_h
 #define mozilla_BasePrincipal_h
 
 #include "nsJSPrincipals.h"
 
 #include "mozilla/Attributes.h"
+#include "mozilla/dom/ChromeUtils.h"
 #include "mozilla/dom/ChromeUtilsBinding.h"
 #include "nsIScriptSecurityManager.h"
 
 class nsIContentSecurityPolicy;
 class nsIObjectOutputStream;
 class nsIObjectInputStream;
 class nsIURI;
 
@@ -42,40 +43,34 @@ public:
   // This method 'clones' the OriginAttributes ignoring the addonId value becaue
   // this is computed from the principal URI and never propagated.
   void Inherit(const OriginAttributes& aAttrs);
 
   void SetFirstPartyDomain(const bool aIsTopLevelDocument, nsIURI* aURI);
 
   enum {
     STRIP_FIRST_PARTY_DOMAIN = 0x01,
-    STRIP_ADDON_ID = 0x02,
-    STRIP_USER_CONTEXT_ID = 0x04,
+    STRIP_USER_CONTEXT_ID = 0x02,
   };
 
   inline void StripAttributes(uint32_t aFlags)
   {
     if (aFlags & STRIP_FIRST_PARTY_DOMAIN) {
       mFirstPartyDomain.Truncate();
     }
 
-    if (aFlags & STRIP_ADDON_ID) {
-      mAddonId.Truncate();
-    }
-
     if (aFlags & STRIP_USER_CONTEXT_ID) {
       mUserContextId = nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID;
     }
   }
 
   bool operator==(const OriginAttributes& aOther) const
   {
     return mAppId == aOther.mAppId &&
            mInIsolatedMozBrowser == aOther.mInIsolatedMozBrowser &&
-           mAddonId == aOther.mAddonId &&
            mUserContextId == aOther.mUserContextId &&
            mPrivateBrowsingId == aOther.mPrivateBrowsingId &&
            mFirstPartyDomain == aOther.mFirstPartyDomain;
   }
 
   bool operator!=(const OriginAttributes& aOther) const
   {
     return !(*this == aOther);
@@ -147,20 +142,16 @@ public:
     if (mAppId.WasPassed() && mAppId.Value() != aAttrs.mAppId) {
       return false;
     }
 
     if (mInIsolatedMozBrowser.WasPassed() && mInIsolatedMozBrowser.Value() != aAttrs.mInIsolatedMozBrowser) {
       return false;
     }
 
-    if (mAddonId.WasPassed() && mAddonId.Value() != aAttrs.mAddonId) {
-      return false;
-    }
-
     if (mUserContextId.WasPassed() && mUserContextId.Value() != aAttrs.mUserContextId) {
       return false;
     }
 
     if (mPrivateBrowsingId.WasPassed() && mPrivateBrowsingId.Value() != aAttrs.mPrivateBrowsingId) {
       return false;
     }
 
@@ -179,21 +170,16 @@ public:
     }
 
     if (mInIsolatedMozBrowser.WasPassed() &&
         aOther.mInIsolatedMozBrowser.WasPassed() &&
         mInIsolatedMozBrowser.Value() != aOther.mInIsolatedMozBrowser.Value()) {
       return false;
     }
 
-    if (mAddonId.WasPassed() && aOther.mAddonId.WasPassed() &&
-        mAddonId.Value() != aOther.mAddonId.Value()) {
-      return false;
-    }
-
     if (mUserContextId.WasPassed() && aOther.mUserContextId.WasPassed() &&
         mUserContextId.Value() != aOther.mUserContextId.Value()) {
       return false;
     }
 
     if (mPrivateBrowsingId.WasPassed() && aOther.mPrivateBrowsingId.WasPassed() &&
         mPrivateBrowsingId.Value() != aOther.mPrivateBrowsingId.Value()) {
       return false;
@@ -213,17 +199,24 @@ public:
  * default implementations and other commonalities between principal
  * implementations.
  *
  * We should merge nsJSPrincipals into this class at some point.
  */
 class BasePrincipal : public nsJSPrincipals
 {
 public:
-  BasePrincipal();
+  enum PrincipalKind {
+    eNullPrincipal,
+    eCodebasePrincipal,
+    eExpandedPrincipal,
+    eSystemPrincipal
+  };
+
+  explicit BasePrincipal(PrincipalKind aKind);
 
   enum DocumentDomainConsideration { DontConsiderDocumentDomain, ConsiderDocumentDomain};
   bool Subsumes(nsIPrincipal* aOther, DocumentDomainConsideration aConsideration);
 
   NS_IMETHOD GetOrigin(nsACString& aOrigin) final;
   NS_IMETHOD GetOriginNoSuffix(nsACString& aOrigin) final;
   NS_IMETHOD Equals(nsIPrincipal* other, bool* _retval) final;
   NS_IMETHOD EqualsConsideringDomain(nsIPrincipal* other, bool* _retval) final;
@@ -239,69 +232,170 @@ public:
   NS_IMETHOD GetIsNullPrincipal(bool* aResult) override;
   NS_IMETHOD GetIsCodebasePrincipal(bool* aResult) override;
   NS_IMETHOD GetIsExpandedPrincipal(bool* aResult) override;
   NS_IMETHOD GetIsSystemPrincipal(bool* aResult) override;
   NS_IMETHOD GetOriginAttributes(JSContext* aCx, JS::MutableHandle<JS::Value> aVal) final;
   NS_IMETHOD GetOriginSuffix(nsACString& aOriginSuffix) final;
   NS_IMETHOD GetAppStatus(uint16_t* aAppStatus) final;
   NS_IMETHOD GetAppId(uint32_t* aAppStatus) final;
-  NS_IMETHOD GetAddonId(nsAString& aAddonId) final;
   NS_IMETHOD GetIsInIsolatedMozBrowserElement(bool* aIsInIsolatedMozBrowserElement) final;
   NS_IMETHOD GetUnknownAppId(bool* aUnknownAppId) final;
   NS_IMETHOD GetUserContextId(uint32_t* aUserContextId) final;
   NS_IMETHOD GetPrivateBrowsingId(uint32_t* aPrivateBrowsingId) final;
 
-  bool EqualsIgnoringAddonId(nsIPrincipal *aOther);
-
   virtual bool AddonHasPermission(const nsAString& aPerm);
 
   virtual bool IsCodebasePrincipal() const { return false; };
 
   static BasePrincipal* Cast(nsIPrincipal* aPrin) { return static_cast<BasePrincipal*>(aPrin); }
   static already_AddRefed<BasePrincipal>
   CreateCodebasePrincipal(nsIURI* aURI, const OriginAttributes& aAttrs);
   static already_AddRefed<BasePrincipal> CreateCodebasePrincipal(const nsACString& aOrigin);
 
-  const OriginAttributes& OriginAttributesRef() override { return mOriginAttributes; }
+  const OriginAttributes& OriginAttributesRef() final { return mOriginAttributes; }
   uint32_t AppId() const { return mOriginAttributes.mAppId; }
   uint32_t UserContextId() const { return mOriginAttributes.mUserContextId; }
   uint32_t PrivateBrowsingId() const { return mOriginAttributes.mPrivateBrowsingId; }
   bool IsInIsolatedMozBrowserElement() const { return mOriginAttributes.mInIsolatedMozBrowser; }
 
-  enum PrincipalKind {
-    eNullPrincipal,
-    eCodebasePrincipal,
-    eExpandedPrincipal,
-    eSystemPrincipal
-  };
-
-  virtual PrincipalKind Kind() = 0;
+  PrincipalKind Kind() const { return mKind; }
 
   already_AddRefed<BasePrincipal> CloneStrippingUserContextIdAndFirstPartyDomain();
 
   // Helper to check whether this principal is associated with an addon that
   // allows unprivileged code to load aURI.  aExplicit == true will prevent
   // use of all_urls permission, requiring the domain in its permissions.
   bool AddonAllowsLoad(nsIURI* aURI, bool aExplicit = false);
 
+  // Call these to avoid the cost of virtual dispatch.
+  inline bool FastEquals(nsIPrincipal* aOther);
+  inline bool FastEqualsConsideringDomain(nsIPrincipal* aOther);
+  inline bool FastSubsumes(nsIPrincipal* aOther);
+  inline bool FastSubsumesConsideringDomain(nsIPrincipal* aOther);
+  inline bool FastSubsumesConsideringDomainIgnoringFPD(nsIPrincipal* aOther);
+
 protected:
   virtual ~BasePrincipal();
 
   virtual nsresult GetOriginInternal(nsACString& aOrigin) = 0;
   // Note that this does not check OriginAttributes. Callers that depend on
   // those must call Subsumes instead.
   virtual bool SubsumesInternal(nsIPrincipal* aOther, DocumentDomainConsideration aConsider) = 0;
 
   // Internal, side-effect-free check to determine whether the concrete
   // principal would allow the load ignoring any common behavior implemented in
   // BasePrincipal::CheckMayLoad.
   virtual bool MayLoadInternal(nsIURI* aURI) = 0;
   friend class ::nsExpandedPrincipal;
 
+  // This function should be called as the last step of the initialization of the
+  // principal objects.  It's typically called as the last step from the Init()
+  // method of the child classes.
+  void FinishInit();
+
   nsCOMPtr<nsIContentSecurityPolicy> mCSP;
   nsCOMPtr<nsIContentSecurityPolicy> mPreloadCSP;
+  nsCOMPtr<nsIAtom> mOriginNoSuffix;
+  nsCOMPtr<nsIAtom> mOriginSuffix;
   OriginAttributes mOriginAttributes;
+  PrincipalKind mKind;
+  bool mDomainSet;
 };
 
+inline bool
+BasePrincipal::FastEquals(nsIPrincipal* aOther)
+{
+  auto other = Cast(aOther);
+  if (Kind() != other->Kind()) {
+    // Principals of different kinds can't be equal.
+    return false;
+  }
+
+  // Two principals are considered to be equal if their origins are the same.
+  // If the two principals are codebase principals, their origin attributes
+  // (aka the origin suffix) must also match.
+  // If the two principals are null principals, they're only equal if they're
+  // the same object.
+  if (Kind() == eNullPrincipal || Kind() == eSystemPrincipal) {
+    return this == other;
+  }
+
+  if (mOriginNoSuffix) {
+    if (Kind() == eCodebasePrincipal) {
+      return mOriginNoSuffix == other->mOriginNoSuffix &&
+             mOriginSuffix == other->mOriginSuffix;
+    }
+
+    MOZ_ASSERT(Kind() == eExpandedPrincipal);
+    return mOriginNoSuffix == other->mOriginNoSuffix;
+  }
+
+  // If mOriginNoSuffix is null on one of our principals, we must fall back
+  // to the slow path.
+  return Subsumes(aOther, DontConsiderDocumentDomain) &&
+         other->Subsumes(this, DontConsiderDocumentDomain);
+}
+
+inline bool
+BasePrincipal::FastEqualsConsideringDomain(nsIPrincipal* aOther)
+{
+  // If neither of the principals have document.domain set, we use the fast path
+  // in Equals().  Otherwise, we fall back to the slow path below.
+  auto other = Cast(aOther);
+  if (!mDomainSet && !other->mDomainSet) {
+    return FastEquals(aOther);
+  }
+
+  return Subsumes(aOther, ConsiderDocumentDomain) &&
+         other->Subsumes(this, ConsiderDocumentDomain);
+}
+
+inline bool
+BasePrincipal::FastSubsumes(nsIPrincipal* aOther)
+{
+  // If two principals are equal, then they both subsume each other.
+  // We deal with two special cases first:
+  // Null principals only subsume each other if they are equal, and are only
+  // equal if they're the same object.
+  // Also, if mOriginNoSuffix is null, FastEquals falls back to the slow path
+  // using Subsumes, so we don't want to use it in that case to avoid an
+  // infinite recursion.
+  auto other = Cast(aOther);
+  if (Kind() == eNullPrincipal && other->Kind() == eNullPrincipal) {
+    return this == other;
+  }
+  if (mOriginNoSuffix && FastEquals(aOther)) {
+    return true;
+  }
+
+  // Otherwise, fall back to the slow path.
+  return Subsumes(aOther, DontConsiderDocumentDomain);
+}
+
+inline bool
+BasePrincipal::FastSubsumesConsideringDomain(nsIPrincipal* aOther)
+{
+  // If neither of the principals have document.domain set, we hand off to
+  // FastSubsumes() which has fast paths for some special cases. Otherwise, we fall
+  // back to the slow path below.
+  if (!mDomainSet && !Cast(aOther)->mDomainSet) {
+    return FastSubsumes(aOther);
+  }
+
+  return Subsumes(aOther, ConsiderDocumentDomain);
+}
+
+inline bool
+BasePrincipal::FastSubsumesConsideringDomainIgnoringFPD(nsIPrincipal* aOther)
+{
+  if (Kind() == eCodebasePrincipal &&
+      !dom::ChromeUtils::IsOriginAttributesEqualIgnoringFPD(
+            mOriginAttributes, Cast(aOther)->mOriginAttributes)) {
+    return false;
+  }
+
+ return SubsumesInternal(aOther, ConsiderDocumentDomain);
+}
+
 } // namespace mozilla
 
 #endif /* mozilla_BasePrincipal_h */
--- a/caps/nsExpandedPrincipal.cpp
+++ b/caps/nsExpandedPrincipal.cpp
@@ -40,29 +40,39 @@ struct OriginComparator
     rv = b->GetOrigin(originB);
     NS_ENSURE_SUCCESS(rv, false);
     return a == b;
   }
 };
 
 nsExpandedPrincipal::nsExpandedPrincipal(nsTArray<nsCOMPtr<nsIPrincipal>> &aWhiteList,
                                          const OriginAttributes& aAttrs)
+  : BasePrincipal(eExpandedPrincipal)
 {
   // We force the principals to be sorted by origin so that nsExpandedPrincipal
   // origins can have a canonical form.
   OriginComparator c;
   for (size_t i = 0; i < aWhiteList.Length(); ++i) {
     mPrincipals.InsertElementSorted(aWhiteList[i], c);
   }
   mOriginAttributes = aAttrs;
 }
 
 nsExpandedPrincipal::~nsExpandedPrincipal()
 { }
 
+already_AddRefed<nsExpandedPrincipal>
+nsExpandedPrincipal::Create(nsTArray<nsCOMPtr<nsIPrincipal>>& aWhiteList,
+                            const OriginAttributes& aAttrs)
+{
+  RefPtr<nsExpandedPrincipal> ep = new nsExpandedPrincipal(aWhiteList, aAttrs);
+  ep->FinishInit();
+  return ep.forget();
+}
+
 NS_IMETHODIMP
 nsExpandedPrincipal::GetDomain(nsIURI** aDomain)
 {
   *aDomain = nullptr;
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -155,16 +165,23 @@ nsExpandedPrincipal::GetWhiteList(nsTArr
 }
 
 NS_IMETHODIMP
 nsExpandedPrincipal::GetBaseDomain(nsACString& aBaseDomain)
 {
   return NS_ERROR_NOT_AVAILABLE;
 }
 
+NS_IMETHODIMP
+nsExpandedPrincipal::GetAddonId(nsAString& aAddonId)
+{
+  aAddonId.Truncate();
+  return NS_OK;
+};
+
 bool
 nsExpandedPrincipal::AddonHasPermission(const nsAString& aPerm)
 {
   for (size_t i = 0; i < mPrincipals.Length(); ++i) {
     if (BasePrincipal::Cast(mPrincipals[i])->AddonHasPermission(aPerm)) {
       return true;
     }
   }
--- a/caps/nsExpandedPrincipal.h
+++ b/caps/nsExpandedPrincipal.h
@@ -10,36 +10,39 @@
 #include "nsJSPrincipals.h"
 #include "nsTArray.h"
 #include "nsNetUtil.h"
 #include "mozilla/BasePrincipal.h"
 
 class nsExpandedPrincipal : public nsIExpandedPrincipal
                           , public mozilla::BasePrincipal
 {
-public:
   nsExpandedPrincipal(nsTArray<nsCOMPtr<nsIPrincipal>> &aWhiteList,
                       const mozilla::OriginAttributes& aAttrs);
 
+public:
+  static already_AddRefed<nsExpandedPrincipal>
+  Create(nsTArray<nsCOMPtr<nsIPrincipal>>& aWhiteList,
+         const mozilla::OriginAttributes& aAttrs);
+
   NS_DECL_NSIEXPANDEDPRINCIPAL
   NS_DECL_NSISERIALIZABLE
   NS_IMETHOD_(MozExternalRefCountType) AddRef() override { return nsJSPrincipals::AddRef(); };
   NS_IMETHOD_(MozExternalRefCountType) Release() override { return nsJSPrincipals::Release(); };
   NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
   NS_IMETHOD GetHashValue(uint32_t* aHashValue) override;
   NS_IMETHOD GetURI(nsIURI** aURI) override;
   NS_IMETHOD GetDomain(nsIURI** aDomain) override;
   NS_IMETHOD SetDomain(nsIURI* aDomain) override;
   NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override;
+  NS_IMETHOD GetAddonId(nsAString& aAddonId) override;
   virtual bool AddonHasPermission(const nsAString& aPerm) override;
   virtual nsresult GetScriptLocation(nsACString &aStr) override;
   nsresult GetOriginInternal(nsACString& aOrigin) override;
 
-  PrincipalKind Kind() override { return eExpandedPrincipal; }
-
 protected:
   virtual ~nsExpandedPrincipal();
 
   bool SubsumesInternal(nsIPrincipal* aOther, DocumentDomainConsideration aConsideration) override;
   bool MayLoadInternal(nsIURI* aURI) override;
 
 private:
   nsTArray< nsCOMPtr<nsIPrincipal> > mPrincipals;
--- a/caps/nsNullPrincipal.cpp
+++ b/caps/nsNullPrincipal.cpp
@@ -81,16 +81,18 @@ nsNullPrincipal::Init(const OriginAttrib
                    NS_ERROR_NOT_AVAILABLE);
 
     mURI = aURI;
   } else {
     mURI = nsNullPrincipalURI::Create();
     NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_AVAILABLE);
   }
 
+  FinishInit();
+
   return NS_OK;
 }
 
 nsresult
 nsNullPrincipal::GetScriptLocation(nsACString &aStr)
 {
   return mURI->GetSpec(aStr);
 }
@@ -151,16 +153,23 @@ nsNullPrincipal::MayLoadInternal(nsIURI*
 
 NS_IMETHODIMP
 nsNullPrincipal::GetBaseDomain(nsACString& aBaseDomain)
 {
   // For a null principal, we use our unique uuid as the base domain.
   return mURI->GetPath(aBaseDomain);
 }
 
+NS_IMETHODIMP
+nsNullPrincipal::GetAddonId(nsAString& aAddonId)
+{
+  aAddonId.Truncate();
+  return NS_OK;
+};
+
 /**
  * nsISerializable implementation
  */
 NS_IMETHODIMP
 nsNullPrincipal::Read(nsIObjectInputStream* aStream)
 {
   // Note - nsNullPrincipal use NS_GENERIC_FACTORY_CONSTRUCTOR_INIT, which means
   // that the Init() method has already been invoked by the time we deserialize.
--- a/caps/nsNullPrincipal.h
+++ b/caps/nsNullPrincipal.h
@@ -31,43 +31,45 @@ class nsIURI;
 #define NS_NULLPRINCIPAL_SCHEME "moz-nullprincipal"
 
 class nsNullPrincipal final : public mozilla::BasePrincipal
 {
 public:
   // This should only be used by deserialization, and the factory constructor.
   // Other consumers should use the Create and CreateWithInheritedAttributes
   // methods.
-  nsNullPrincipal() {}
+  nsNullPrincipal()
+    : BasePrincipal(eNullPrincipal)
+  {
+  }
 
   NS_DECL_NSISERIALIZABLE
 
   NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
   NS_IMETHOD GetHashValue(uint32_t* aHashValue) override;
   NS_IMETHOD GetURI(nsIURI** aURI) override;
   NS_IMETHOD GetDomain(nsIURI** aDomain) override;
   NS_IMETHOD SetDomain(nsIURI* aDomain) override;
   NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override;
+  NS_IMETHOD GetAddonId(nsAString& aAddonId) override;
   nsresult GetOriginInternal(nsACString& aOrigin) override;
 
   static already_AddRefed<nsNullPrincipal> CreateWithInheritedAttributes(nsIPrincipal* aInheritFrom);
 
   static already_AddRefed<nsNullPrincipal> CreateWithInheritedAttributes(nsIDocShell* aDocShell);
 
   static already_AddRefed<nsNullPrincipal>
   Create(const mozilla::OriginAttributes& aOriginAttributes = mozilla::OriginAttributes(),
          nsIURI* aURI = nullptr);
 
   nsresult Init(const mozilla::OriginAttributes& aOriginAttributes = mozilla::OriginAttributes(),
                 nsIURI* aURI = nullptr);
 
   virtual nsresult GetScriptLocation(nsACString &aStr) override;
 
-  PrincipalKind Kind() override { return eNullPrincipal; }
-
  protected:
   virtual ~nsNullPrincipal() {}
 
   bool SubsumesInternal(nsIPrincipal* aOther, DocumentDomainConsideration aConsideration) override
   {
     return aOther == this;
   }
 
--- a/caps/nsPrincipal.cpp
+++ b/caps/nsPrincipal.cpp
@@ -22,16 +22,17 @@
 #include "nsIProtocolHandler.h"
 #include "nsError.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsNetCID.h"
 #include "jswrapper.h"
 
 #include "mozilla/dom/nsCSPContext.h"
 #include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/HashFunctions.h"
 
 using namespace mozilla;
 
 static bool gCodeBasePrincipalSupport = false;
 
 static bool URIIsImmutable(nsIURI* aURI)
@@ -39,16 +40,29 @@ static bool URIIsImmutable(nsIURI* aURI)
   nsCOMPtr<nsIMutable> mutableObj(do_QueryInterface(aURI));
   bool isMutable;
   return
     mutableObj &&
     NS_SUCCEEDED(mutableObj->GetMutable(&isMutable)) &&
     !isMutable;
 }
 
+static nsIAddonPolicyService*
+GetAddonPolicyService(nsresult* aRv)
+{
+  static nsCOMPtr<nsIAddonPolicyService> addonPolicyService;
+
+  *aRv = NS_OK;
+  if (!addonPolicyService) {
+    addonPolicyService = do_GetService("@mozilla.org/addons/policy-service;1", aRv);
+    ClearOnShutdown(&addonPolicyService);
+  }
+  return addonPolicyService;
+}
+
 NS_IMPL_CLASSINFO(nsPrincipal, nullptr, nsIClassInfo::MAIN_THREAD_ONLY,
                   NS_PRINCIPAL_CID)
 NS_IMPL_QUERY_INTERFACE_CI(nsPrincipal,
                            nsIPrincipal,
                            nsISerializable)
 NS_IMPL_CI_INTERFACE_GETTER(nsPrincipal,
                             nsIPrincipal,
                             nsISerializable)
@@ -58,20 +72,22 @@ NS_IMPL_CI_INTERFACE_GETTER(nsPrincipal,
 nsPrincipal::InitializeStatics()
 {
   Preferences::AddBoolVarCache(&gCodeBasePrincipalSupport,
                                "signed.applets.codebase_principal_support",
                                false);
 }
 
 nsPrincipal::nsPrincipal()
-  : mCodebaseImmutable(false)
+  : BasePrincipal(eCodebasePrincipal)
+  , mCodebaseImmutable(false)
   , mDomainImmutable(false)
   , mInitialized(false)
-{ }
+{
+}
 
 nsPrincipal::~nsPrincipal()
 {
   // let's clear the principal within the csp to avoid a tangling pointer
   if (mCSP) {
     static_cast<nsCSPContext*>(mCSP.get())->clearLoadingPrincipal();
   }
 }
@@ -79,20 +95,35 @@ nsPrincipal::~nsPrincipal()
 nsresult
 nsPrincipal::Init(nsIURI *aCodebase, const OriginAttributes& aOriginAttributes)
 {
   NS_ENSURE_STATE(!mInitialized);
   NS_ENSURE_ARG(aCodebase);
 
   mInitialized = true;
 
+  // Assert that the URI we get here isn't any of the schemes that we know we
+  // should not get here.  These schemes always either inherit their principal
+  // or fall back to a null principal.  These are schemes which return
+  // URI_INHERITS_SECURITY_CONTEXT from their protocol handler's
+  // GetProtocolFlags function.
+  bool hasFlag;
+  Unused << hasFlag; // silence possible compiler warnings.
+  MOZ_DIAGNOSTIC_ASSERT(
+      NS_SUCCEEDED(NS_URIChainHasFlags(aCodebase,
+                                       nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT,
+                                       &hasFlag)) &&
+      !hasFlag);
+
   mCodebase = NS_TryToMakeImmutable(aCodebase);
   mCodebaseImmutable = URIIsImmutable(mCodebase);
   mOriginAttributes = aOriginAttributes;
 
+  FinishInit();
+
   return NS_OK;
 }
 
 nsresult
 nsPrincipal::GetScriptLocation(nsACString &aStr)
 {
   return mCodebase->GetSpec(aStr);
 }
@@ -104,16 +135,27 @@ nsPrincipal::GetOriginInternal(nsACStrin
     return NS_ERROR_FAILURE;
   }
 
   nsCOMPtr<nsIURI> origin = NS_GetInnermostURI(mCodebase);
   if (!origin) {
     return NS_ERROR_FAILURE;
   }
 
+  MOZ_ASSERT(!NS_IsAboutBlank(origin),
+             "The inner URI for about:blank must be moz-safe-about:blank");
+
+  if (!nsScriptSecurityManager::GetStrictFileOriginPolicy() &&
+      NS_URIIsLocalFile(origin)) {
+    // If strict file origin policy is not in effect, all local files are
+    // considered to be same-origin, so return a known dummy origin here.
+    aOrigin.AssignLiteral("file://UNIVERSAL_FILE_URI_ORIGIN");
+    return NS_OK;
+  }
+
   nsAutoCString hostPort;
 
   // chrome: URLs don't have a meaningful origin, so make
   // sure we just get the full spec for them.
   // XXX this should be removed in favor of the solution in
   // bug 160042.
   bool isChrome;
   nsresult rv = origin->SchemeIs("chrome", &isChrome);
@@ -134,17 +176,21 @@ nsPrincipal::GetOriginInternal(nsACStrin
   // containing the magic "^" we use as a separating character for origin
   // attributes.
   //
   // These constraints can generally be achieved by restricting .origin to
   // nsIStandardURL-based URIs, but there are a few other URI schemes that we need
   // to handle.
   bool isBehaved;
   if ((NS_SUCCEEDED(origin->SchemeIs("about", &isBehaved)) && isBehaved) ||
-      (NS_SUCCEEDED(origin->SchemeIs("moz-safe-about", &isBehaved)) && isBehaved) ||
+      (NS_SUCCEEDED(origin->SchemeIs("moz-safe-about", &isBehaved)) && isBehaved &&
+       // We generally consider two about:foo origins to be same-origin, but
+       // about:blank is special since it can be generated from different sources.
+       // We check for moz-safe-about:blank since origin is an innermost URI.
+       !origin->GetSpecOrDefault().EqualsLiteral("moz-safe-about:blank")) ||
       (NS_SUCCEEDED(origin->SchemeIs("indexeddb", &isBehaved)) && isBehaved)) {
     rv = origin->GetAsciiSpec(aOrigin);
     NS_ENSURE_SUCCESS(rv, rv);
     // These URIs could technically contain a '^', but they never should.
     if (NS_WARN_IF(aOrigin.FindChar('^', 0) != -1)) {
       aOrigin.Truncate();
       return NS_ERROR_FAILURE;
     }
@@ -286,23 +332,16 @@ nsPrincipal::MayLoadInternal(nsIURI* aUR
       NS_URIIsLocalFile(aURI) &&
       NS_RelaxStrictFileOriginPolicy(aURI, mCodebase)) {
     return true;
   }
 
   return false;
 }
 
-void
-nsPrincipal::SetURI(nsIURI* aURI)
-{
-  mCodebase = NS_TryToMakeImmutable(aURI);
-  mCodebaseImmutable = URIIsImmutable(mCodebase);
-}
-
 NS_IMETHODIMP
 nsPrincipal::GetHashValue(uint32_t* aValue)
 {
   NS_PRECONDITION(mCodebase, "Need a codebase");
 
   *aValue = nsScriptSecurityManager::HashPrincipalByOrigin(this);
   return NS_OK;
 }
@@ -323,16 +362,17 @@ nsPrincipal::GetDomain(nsIURI** aDomain)
   return NS_EnsureSafeToReturn(mDomain, aDomain);
 }
 
 NS_IMETHODIMP
 nsPrincipal::SetDomain(nsIURI* aDomain)
 {
   mDomain = NS_TryToMakeImmutable(aDomain);
   mDomainImmutable = URIIsImmutable(mDomain);
+  mDomainSet = true;
 
   // Recompute all wrappers between compartments using this principal and other
   // non-chrome compartments.
   AutoSafeJSContext cx;
   JSPrincipals *principals = nsJSPrincipals::get(static_cast<nsIPrincipal*>(this));
   bool success = js::RecomputeWrappers(cx, js::ContentCompartmentsOnly(),
                                        js::CompartmentsWithPrincipals(principals));
   NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
@@ -374,16 +414,45 @@ nsPrincipal::GetBaseDomain(nsACString& a
   if (thirdPartyUtil) {
     return thirdPartyUtil->GetBaseDomain(mCodebase, aBaseDomain);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsPrincipal::GetAddonId(nsAString& aAddonId)
+{
+  if (mAddonIdCache.isSome()) {
+    aAddonId.Assign(mAddonIdCache.ref());
+    return NS_OK;
+  }
+
+  NS_ENSURE_TRUE(mCodebase, NS_ERROR_FAILURE);
+
+  nsresult rv;
+  bool isMozExt;
+  if (NS_SUCCEEDED(mCodebase->SchemeIs("moz-extension", &isMozExt)) && isMozExt) {
+    nsIAddonPolicyService* addonPolicyService = GetAddonPolicyService(&rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsAutoString addonId;
+    rv = addonPolicyService->ExtensionURIToAddonId(mCodebase, addonId);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    mAddonIdCache.emplace(addonId);
+  } else {
+    mAddonIdCache.emplace();
+  }
+
+  aAddonId.Assign(mAddonIdCache.ref());
+  return NS_OK;
+};
+
+NS_IMETHODIMP
 nsPrincipal::Read(nsIObjectInputStream* aStream)
 {
   nsCOMPtr<nsISupports> supports;
   nsCOMPtr<nsIURI> codebase;
   nsresult rv = NS_ReadOptionalObject(aStream, true, getter_AddRefs(supports));
   if (NS_FAILED(rv)) {
     return rv;
   }
--- a/caps/nsPrincipal.h
+++ b/caps/nsPrincipal.h
@@ -20,47 +20,48 @@ class nsPrincipal final : public mozilla
 public:
   NS_DECL_NSISERIALIZABLE
   NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
   NS_IMETHOD GetHashValue(uint32_t* aHashValue) override;
   NS_IMETHOD GetURI(nsIURI** aURI) override;
   NS_IMETHOD GetDomain(nsIURI** aDomain) override;
   NS_IMETHOD SetDomain(nsIURI* aDomain) override;
   NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override;
+  NS_IMETHOD GetAddonId(nsAString& aAddonId) override;
   bool IsCodebasePrincipal() const override { return true; }
   nsresult GetOriginInternal(nsACString& aOrigin) override;
 
   nsPrincipal();
 
   // Init() must be called before the principal is in a usable state.
   nsresult Init(nsIURI* aCodebase,
                 const mozilla::OriginAttributes& aOriginAttributes);
 
   virtual nsresult GetScriptLocation(nsACString& aStr) override;
-  void SetURI(nsIURI* aURI);
 
   /**
    * Called at startup to setup static data, e.g. about:config pref-observers.
    */
   static void InitializeStatics();
 
-  PrincipalKind Kind() override { return eCodebasePrincipal; }
-
   nsCOMPtr<nsIURI> mDomain;
   nsCOMPtr<nsIURI> mCodebase;
   // If mCodebaseImmutable is true, mCodebase is non-null and immutable
   bool mCodebaseImmutable;
   bool mDomainImmutable;
   bool mInitialized;
 
 protected:
   virtual ~nsPrincipal();
 
   bool SubsumesInternal(nsIPrincipal* aOther, DocumentDomainConsideration aConsideration) override;
   bool MayLoadInternal(nsIURI* aURI) override;
+
+private:
+  mozilla::Maybe<nsString> mAddonIdCache;
 };
 
 #define NS_PRINCIPAL_CONTRACTID "@mozilla.org/principal;1"
 #define NS_PRINCIPAL_CID \
 { 0x653e0e4d, 0x3ee4, 0x45fa, \
   { 0xb2, 0x72, 0x97, 0xc2, 0x0b, 0xc0, 0x1e, 0xb8 } }
 
 #endif // nsPrincipal_h__
--- a/caps/nsScriptSecurityManager.cpp
+++ b/caps/nsScriptSecurityManager.cpp
@@ -59,17 +59,16 @@
 #include "mozilla/dom/BindingUtils.h"
 #include <stdint.h>
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/StaticPtr.h"
 #include "nsContentUtils.h"
 #include "nsJSUtils.h"
 #include "nsILoadInfo.h"
-#include "nsXPCOMStrings.h"
 
 // This should be probably defined on some other place... but I couldn't find it
 #define WEBAPPS_PERM_NAME "webapps-manage"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 nsIIOService    *nsScriptSecurityManager::sIOService = nullptr;
@@ -340,30 +339,16 @@ nsScriptSecurityManager::GetChannelResul
                 principalToInherit.forget(aPrincipal);
                 return NS_OK;
             }
         }
     }
     return GetChannelURIPrincipal(aChannel, aPrincipal);
 }
 
-nsresult
-nsScriptSecurityManager::MaybeSetAddonIdFromURI(OriginAttributes& aAttrs, nsIURI* aURI)
-{
-  nsAutoCString scheme;
-  nsresult rv = aURI->GetScheme(scheme);
-  NS_ENSURE_SUCCESS(rv, rv);
-  if (scheme.EqualsLiteral("moz-extension") && GetAddonPolicyService()) {
-    rv = GetAddonPolicyService()->ExtensionURIToAddonId(aURI, aAttrs.mAddonId);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  return NS_OK;
-}
-
 /* The principal of the URI that this channel is loading. This is never
  * affected by things like sandboxed loads, or loads where we forcefully
  * inherit the principal.  Think of this as the principal of the server
  * which this channel is loading from.  Most callers should use
  * GetChannelResultPrincipal instead of GetChannelURIPrincipal.  Only
  * call GetChannelURIPrincipal if you are sure that you want the
  * principal that matches the uri, even in cases when the load is
  * sandboxed or when the load could be a blob or data uri (i.e even when
@@ -391,18 +376,16 @@ nsScriptSecurityManager::GetChannelURIPr
     // For subresource loading, the origin attributes of the loadInfo is from
     // its loadingPrincipal.
     OriginAttributes attrs;
 
     // For addons loadInfo might be null.
     if (loadInfo) {
       attrs.Inherit(loadInfo->GetOriginAttributes());
     }
-    rv = MaybeSetAddonIdFromURI(attrs, uri);
-    NS_ENSURE_SUCCESS(rv, rv);
     nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(uri, attrs);
     prin.forget(aPrincipal);
     return *aPrincipal ? NS_OK : NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 nsScriptSecurityManager::IsSystemPrincipal(nsIPrincipal* aPrincipal,
                                            bool* aIsSystem)
@@ -1161,33 +1144,29 @@ nsScriptSecurityManager::
   NS_ENSURE_STATE(aLoadContext);
   OriginAttributes docShellAttrs;
   bool result = aLoadContext->GetOriginAttributes(docShellAttrs);;
   NS_ENSURE_TRUE(result, NS_ERROR_FAILURE);
 
   OriginAttributes attrs;
   attrs.Inherit(docShellAttrs);
 
-  nsresult rv = MaybeSetAddonIdFromURI(attrs, aURI);
-  NS_ENSURE_SUCCESS(rv, rv);
   nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(aURI, attrs);
   prin.forget(aPrincipal);
   return *aPrincipal ? NS_OK : NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 nsScriptSecurityManager::GetDocShellCodebasePrincipal(nsIURI* aURI,
                                                       nsIDocShell* aDocShell,
                                                       nsIPrincipal** aPrincipal)
 {
   OriginAttributes attrs;
   attrs.Inherit(nsDocShell::Cast(aDocShell)->GetOriginAttributes());
 
-  nsresult rv = MaybeSetAddonIdFromURI(attrs, aURI);
-  NS_ENSURE_SUCCESS(rv, rv);
   nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(aURI, attrs);
   prin.forget(aPrincipal);
   return *aPrincipal ? NS_OK : NS_ERROR_FAILURE;
 }
 
 // static
 nsIPrincipal*
 nsScriptSecurityManager::doGetObjectPrincipal(JSObject *aObj)
@@ -1329,17 +1308,17 @@ nsresult nsScriptSecurityManager::Init()
         mozilla::services::GetStringBundleService();
     if (!bundleService)
         return NS_ERROR_FAILURE;
 
     rv = bundleService->CreateBundle("chrome://global/locale/security/caps.properties", &sStrBundle);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Create our system principal singleton
-    RefPtr<nsSystemPrincipal> system = new nsSystemPrincipal();
+    RefPtr<nsSystemPrincipal> system = nsSystemPrincipal::Create();
 
     mSystemPrincipal = system;
 
     //-- Register security check callback in the JS engine
     //   Currently this is used to control access to function.caller
     sContext = danger::GetJSContext();
 
     static const JSSecurityCallbacks securityCallbacks = {
--- a/caps/nsScriptSecurityManager.h
+++ b/caps/nsScriptSecurityManager.h
@@ -106,19 +106,16 @@ private:
     InitPrefs();
 
     inline void
     ScriptSecurityPrefChanged();
 
     inline void
     AddSitesToFileURIWhitelist(const nsCString& aSiteList);
 
-    // If aURI is a moz-extension:// URI, set mAddonId to the associated addon.
-    nsresult MaybeSetAddonIdFromURI(mozilla::OriginAttributes& aAttrs, nsIURI* aURI);
-
     nsresult GetChannelResultPrincipal(nsIChannel* aChannel,
                                        nsIPrincipal** aPrincipal,
                                        bool aIgnoreSandboxing);
 
     nsresult
     CheckLoadURIFlags(nsIURI* aSourceURI, nsIURI* aTargetURI, nsIURI* aSourceBaseURI,
                       nsIURI* aTargetBaseURI, uint32_t aFlags);
 
--- a/caps/nsSystemPrincipal.cpp
+++ b/caps/nsSystemPrincipal.cpp
@@ -26,16 +26,24 @@ NS_IMPL_QUERY_INTERFACE_CI(nsSystemPrinc
                            nsIPrincipal,
                            nsISerializable)
 NS_IMPL_CI_INTERFACE_GETTER(nsSystemPrincipal,
                             nsIPrincipal,
                             nsISerializable)
 
 #define SYSTEM_PRINCIPAL_SPEC "[System Principal]"
 
+already_AddRefed<nsSystemPrincipal>
+nsSystemPrincipal::Create()
+{
+  RefPtr<nsSystemPrincipal> sp = new nsSystemPrincipal();
+  sp->FinishInit();
+  return sp.forget();
+}
+
 nsresult
 nsSystemPrincipal::GetScriptLocation(nsACString &aStr)
 {
     aStr.AssignLiteral(SYSTEM_PRINCIPAL_SPEC);
     return NS_OK;
 }
 
 ///////////////////////////////////////
@@ -108,16 +116,23 @@ nsSystemPrincipal::SetDomain(nsIURI* aDo
 
 NS_IMETHODIMP
 nsSystemPrincipal::GetBaseDomain(nsACString& aBaseDomain)
 {
   // No base domain for chrome.
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsSystemPrincipal::GetAddonId(nsAString& aAddonId)
+{
+  aAddonId.Truncate();
+  return NS_OK;
+};
+
 //////////////////////////////////////////
 // Methods implementing nsISerializable //
 //////////////////////////////////////////
 
 NS_IMETHODIMP
 nsSystemPrincipal::Read(nsIObjectInputStream* aStream)
 {
     // no-op: CID is sufficient to identify the mSystemPrincipal singleton
--- a/caps/nsSystemPrincipal.h
+++ b/caps/nsSystemPrincipal.h
@@ -17,43 +17,47 @@
 #define NS_SYSTEMPRINCIPAL_CID \
 { 0x4a6212db, 0xaccb, 0x11d3, \
 { 0xb7, 0x65, 0x0, 0x60, 0xb0, 0xb6, 0xce, 0xcb }}
 #define NS_SYSTEMPRINCIPAL_CONTRACTID "@mozilla.org/systemprincipal;1"
 
 
 class nsSystemPrincipal final : public mozilla::BasePrincipal
 {
+  nsSystemPrincipal()
+    : BasePrincipal(eSystemPrincipal)
+  {
+  }
+
 public:
+  static already_AddRefed<nsSystemPrincipal> Create();
+
   NS_DECL_NSISERIALIZABLE
   NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
   NS_IMETHOD GetHashValue(uint32_t* aHashValue) override;
   NS_IMETHOD GetURI(nsIURI** aURI) override;
   NS_IMETHOD GetDomain(nsIURI** aDomain) override;
   NS_IMETHOD SetDomain(nsIURI* aDomain) override;
   NS_IMETHOD GetCsp(nsIContentSecurityPolicy** aCsp) override;
   NS_IMETHOD EnsureCSP(nsIDOMDocument* aDocument, nsIContentSecurityPolicy** aCSP) override;
   NS_IMETHOD GetPreloadCsp(nsIContentSecurityPolicy** aPreloadCSP) override;
   NS_IMETHOD EnsurePreloadCSP(nsIDOMDocument* aDocument, nsIContentSecurityPolicy** aCSP) override;
   NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override;
+  NS_IMETHOD GetAddonId(nsAString& aAddonId) override;
   nsresult GetOriginInternal(nsACString& aOrigin) override;
 
-  nsSystemPrincipal() {}
-
   virtual nsresult GetScriptLocation(nsACString &aStr) override;
 
 protected:
   virtual ~nsSystemPrincipal(void) {}
 
   bool SubsumesInternal(nsIPrincipal *aOther, DocumentDomainConsideration aConsideration) override
   {
     return true;
   }
 
   bool MayLoadInternal(nsIURI* aURI) override
   {
     return true;
   }
-
-  PrincipalKind Kind() override { return eSystemPrincipal; }
 };
 
 #endif // nsSystemPrincipal_h__
--- a/caps/tests/mochitest/test_addonMayLoad.html
+++ b/caps/tests/mochitest/test_addonMayLoad.html
@@ -15,71 +15,59 @@ https://bugzilla.mozilla.org/show_bug.cg
   const Cc = Components.classes;
   const Ci = Components.interfaces;
   const Cu = Components.utils;
   Cu.import("resource://gre/modules/Services.jsm");
   let ssm = Services.scriptSecurityManager;
   let aps = Cc["@mozilla.org/addons/policy-service;1"].getService(Ci.nsIAddonPolicyService).wrappedJSObject;
 
   SimpleTest.waitForExplicitFinish();
+  let oldAddonIdCallback = aps.setExtensionURIToAddonIdCallback(uri => uri.host);
   SimpleTest.registerCleanupFunction(function() {
-    aps.setAddonLoadURICallback('addonA', null);
-    aps.setAddonLoadURICallback('addonB', null);
+    aps.setAddonLoadURICallback('addona', null);
+    aps.setAddonLoadURICallback('addonb', null);
+    aps.setExtensionURIToAddonIdCallback(oldAddonIdCallback);
   });
 
   function tryLoad(sb, uri) {
     let p = new Promise(function(resolve, reject) {
       Cu.exportFunction(resolve, sb, { defineAs: "finish" });
       Cu.exportFunction(reject, sb, { defineAs: "error" });
       sb.eval("try { (function () { " +
               "  var xhr = new XMLHttpRequest();" +
               "  xhr.onreadystatechange = function() { if (xhr.readyState == XMLHttpRequest.DONE) { finish(xhr.status == 200); } };" +
               "  xhr.open('GET', '" + uri + "', true);" +
               "  xhr.send();" +
               "})() } catch (e) { error(e); }");
     });
     return p;
   }
 
-  let exampleCom_addonA = new Cu.Sandbox(ssm.createCodebasePrincipal(Services.io.newURI('http://example.com'), {addonId: 'addonA'}),
-                                         {wantGlobalProperties: ['XMLHttpRequest']});
-  let nullPrin_addonA = new Cu.Sandbox(ssm.createNullPrincipal({addonId: 'addonA'}),
-                                       {wantGlobalProperties: ['XMLHttpRequest']});
-  let exampleCom_addonB = new Cu.Sandbox(ssm.createCodebasePrincipal(Services.io.newURI('http://example.com'), {addonId: 'addonB'}),
-                                         {wantGlobalProperties: ['XMLHttpRequest']});
+  let addonA = new Cu.Sandbox(ssm.createCodebasePrincipal(Services.io.newURI('moz-extension://addonA/'), {}),
+                              {wantGlobalProperties: ['XMLHttpRequest']});
+  let addonB = new Cu.Sandbox(ssm.createCodebasePrincipal(Services.io.newURI('moz-extension://addonB/'), {}),
+                              {wantGlobalProperties: ['XMLHttpRequest']});
 
   function uriForDomain(d) { return d + '/tests/caps/tests/mochitest/file_data.txt' }
 
-  tryLoad(exampleCom_addonA, uriForDomain('http://example.com'))
+  tryLoad(addonA, uriForDomain('http://test1.example.org'))
   .then(function(success) {
-    ok(success, "same-origin load should succeed for addon A");
-    return tryLoad(nullPrin_addonA, uriForDomain('http://example.com'));
-  }).then(function(success) {
-    ok(!success, "null-principal load should fail for addon A");
-    return tryLoad(exampleCom_addonB, uriForDomain('http://example.com'));
-  }).then(function(success) {
-    ok(success, "same-origin load should succeed for addon B");
-    return tryLoad(exampleCom_addonA, uriForDomain('http://test1.example.org'));
-  }).then(function(success) {
     ok(!success, "cross-origin load should fail for addon A");
-    aps.setAddonLoadURICallback('addonA', function(uri) { return /test1/.test(uri.host); });
-    aps.setAddonLoadURICallback('addonB', function(uri) { return /test2/.test(uri.host); });
-    return tryLoad(exampleCom_addonA, uriForDomain('http://test1.example.org'));
+    aps.setAddonLoadURICallback('addona', function(uri) { return /test1/.test(uri.host); });
+    aps.setAddonLoadURICallback('addonb', function(uri) { return /test2/.test(uri.host); });
+    return tryLoad(addonA, uriForDomain('http://test1.example.org'));
   }).then(function(success) {
     ok(success, "whitelisted cross-origin load of test1 should succeed for addon A");
-    return tryLoad(nullPrin_addonA, uriForDomain('http://test1.example.org'));
-  }).then(function(success) {
-    ok(!success, "whitelisted null principal load of test1 should still fail for addon A");
-    return tryLoad(exampleCom_addonB, uriForDomain('http://test1.example.org'));
+    return tryLoad(addonB, uriForDomain('http://test1.example.org'));
   }).then(function(success) {
     ok(!success, "non-whitelisted cross-origin load of test1 should fail for addon B");
-    return tryLoad(exampleCom_addonB, uriForDomain('http://test2.example.org'));
+    return tryLoad(addonB, uriForDomain('http://test2.example.org'));
   }).then(function(success) {
     ok(success, "whitelisted cross-origin load of test2 should succeed for addon B");
-    return tryLoad(exampleCom_addonA, uriForDomain('http://test2.example.org'));
+    return tryLoad(addonA, uriForDomain('http://test2.example.org'));
   }).then(function(success) {
     ok(!success, "non-whitelisted cross-origin load of test2 should fail for addon A");
     SimpleTest.finish();
   }, function(e) {
     ok(false, "Rejected promise chain: " + e);
     SimpleTest.finish();
   });
 
--- a/caps/tests/mochitest/test_extensionURL.html
+++ b/caps/tests/mochitest/test_extensionURL.html
@@ -88,17 +88,17 @@ https://bugzilla.mozilla.org/show_bug.cg
       var p = new Promise(function(resolve, reject) {
         ifr.onload = function() {
           ok(true, 'Loaded ' + url);
           var prin = SpecialPowers.wrap(ifr.contentWindow).document.nodePrincipal;
           function stripTrailingSlash(s) { return s.replace(/\/$/, ''); };
           is(stripTrailingSlash(prin.URI.spec), url, 'Principal uri is correct: ' + url);
           function stripPath(s) { return s.replace(/(.*\/\/.+)\/.*/, '$1'); };
           is(prin.originNoSuffix, stripPath(url), 'Principal origin is correct: ' + prin.originNoSuffix);
-          is(prin.originAttributes.addonId, 'imaginaryaddon-' + url[url.indexOf('/') + 2], 'addonId is correct');
+          is(prin.addonId, 'imaginaryaddon-' + url[url.indexOf('/') + 2], 'addonId is correct');
           if (/_blank/.test(url)) {
             is(SpecialPowers.wrap(ifr.contentWindow).document.documentElement.innerHTML,
                '<head></head><body></body>', 'blank document looks right');
           } else {
             is(SpecialPowers.wrap(ifr.contentWindow).document.title, 'resource test file',
                'document looks right');
           }
           ifr.remove();
--- a/caps/tests/unit/test_origin.js
+++ b/caps/tests/unit/test_origin.js
@@ -43,30 +43,28 @@ function checkSandboxOriginAttributes(ar
 }
 
 // utility function useful for debugging
 function printAttrs(name, attrs) {
   do_print(name + " {\n" +
            "\tappId: " + attrs.appId + ",\n" +
            "\tuserContextId: " + attrs.userContextId + ",\n" +
            "\tinIsolatedMozBrowser: " + attrs.inIsolatedMozBrowser + ",\n" +
-           "\taddonId: '" + attrs.addonId + "',\n" +
            "\tprivateBrowsingId: '" + attrs.privateBrowsingId + "',\n" +
            "\tfirstPartyDomain: '" + attrs.firstPartyDomain + "'\n}");
 }
 
 
 function checkValues(attrs, values) {
   values = values || {};
   //printAttrs("attrs", attrs);
   //printAttrs("values", values);
   do_check_eq(attrs.appId, values.appId || 0);
   do_check_eq(attrs.userContextId, values.userContextId || 0);
   do_check_eq(attrs.inIsolatedMozBrowser, values.inIsolatedMozBrowser || false);
-  do_check_eq(attrs.addonId, values.addonId || '');
   do_check_eq(attrs.privateBrowsingId, values.privateBrowsingId || '');
   do_check_eq(attrs.firstPartyDomain, values.firstPartyDomain || '');
 }
 
 function run_test() {
   // Attributeless origins.
   do_check_eq(ssm.getSystemPrincipal().origin, '[System Principal]');
   checkOriginAttributes(ssm.getSystemPrincipal());
@@ -121,21 +119,16 @@ function run_test() {
   checkOriginAttributes(nullPrin_appBrowser, {appId: 42, inIsolatedMozBrowser: true}, '^appId=42&inBrowser=1');
   do_check_eq(exampleOrg_appBrowser.origin, 'http://example.org^appId=42&inBrowser=1');
 
   // App and browser, different domain.
   var exampleCom_appBrowser = ssm.createCodebasePrincipal(makeURI('https://www.example.com:123'), {appId: 42, inIsolatedMozBrowser: true});
   checkOriginAttributes(exampleCom_appBrowser, {appId: 42, inIsolatedMozBrowser: true}, '^appId=42&inBrowser=1');
   do_check_eq(exampleCom_appBrowser.origin, 'https://www.example.com:123^appId=42&inBrowser=1');
 
-  // Addon.
-  var exampleOrg_addon = ssm.createCodebasePrincipal(makeURI('http://example.org'), {addonId: 'dummy'});
-  checkOriginAttributes(exampleOrg_addon, { addonId: "dummy" }, '^addonId=dummy');
-  do_check_eq(exampleOrg_addon.origin, 'http://example.org^addonId=dummy');
-
   // First party Uri
   var exampleOrg_firstPartyDomain = ssm.createCodebasePrincipal(makeURI('http://example.org'), {firstPartyDomain: 'example.org'});
   checkOriginAttributes(exampleOrg_firstPartyDomain, { firstPartyDomain: "example.org" }, '^firstPartyDomain=example.org');
   do_check_eq(exampleOrg_firstPartyDomain.origin, 'http://example.org^firstPartyDomain=example.org');
 
   // Make sure we don't crash when serializing principals with UNKNOWN_APP_ID.
   try {
     let binaryStream = Cc["@mozilla.org/binaryoutputstream;1"].
@@ -150,23 +143,16 @@ function run_test() {
   }
 
 
   // Just userContext.
   var exampleOrg_userContext = ssm.createCodebasePrincipal(makeURI('http://example.org'), {userContextId: 42});
   checkOriginAttributes(exampleOrg_userContext, { userContextId: 42 }, '^userContextId=42');
   do_check_eq(exampleOrg_userContext.origin, 'http://example.org^userContextId=42');
 
-  // UserContext and Addon.
-  var exampleOrg_userContextAddon = ssm.createCodebasePrincipal(makeURI('http://example.org'), {addonId: 'dummy', userContextId: 42});
-  var nullPrin_userContextAddon = ssm.createNullPrincipal({addonId: 'dummy', userContextId: 42});
-  checkOriginAttributes(exampleOrg_userContextAddon, {addonId: 'dummy', userContextId: 42}, '^addonId=dummy&userContextId=42');
-  checkOriginAttributes(nullPrin_userContextAddon, {addonId: 'dummy', userContextId: 42}, '^addonId=dummy&userContextId=42');
-  do_check_eq(exampleOrg_userContextAddon.origin, 'http://example.org^addonId=dummy&userContextId=42');
-
   // UserContext and App.
   var exampleOrg_userContextApp = ssm.createCodebasePrincipal(makeURI('http://example.org'), {appId: 24, userContextId: 42});
   var nullPrin_userContextApp = ssm.createNullPrincipal({appId: 24, userContextId: 42});
   checkOriginAttributes(exampleOrg_userContextApp, {appId: 24, userContextId: 42}, '^appId=24&userContextId=42');
   checkOriginAttributes(nullPrin_userContextApp, {appId: 24, userContextId: 42}, '^appId=24&userContextId=42');
   do_check_eq(exampleOrg_userContextApp.origin, 'http://example.org^appId=24&userContextId=42');
 
   checkSandboxOriginAttributes(null, {});
@@ -180,21 +166,18 @@ function run_test() {
   // Check that all of the above are cross-origin.
   checkCrossOrigin(exampleOrg_app, exampleOrg);
   checkCrossOrigin(exampleOrg_app, nullPrin_app);
   checkCrossOrigin(exampleOrg_browser, exampleOrg_app);
   checkCrossOrigin(exampleOrg_browser, nullPrin_browser);
   checkCrossOrigin(exampleOrg_appBrowser, exampleOrg_app);
   checkCrossOrigin(exampleOrg_appBrowser, nullPrin_appBrowser);
   checkCrossOrigin(exampleOrg_appBrowser, exampleCom_appBrowser);
-  checkCrossOrigin(exampleOrg_addon, exampleOrg);
   checkCrossOrigin(exampleOrg_firstPartyDomain, exampleOrg);
   checkCrossOrigin(exampleOrg_userContext, exampleOrg);
-  checkCrossOrigin(exampleOrg_userContextAddon, exampleOrg);
-  checkCrossOrigin(exampleOrg_userContext, exampleOrg_userContextAddon);
   checkCrossOrigin(exampleOrg_userContext, exampleOrg_userContextApp);
 
   // Check Principal kinds.
   function checkKind(prin, kind) {
     do_check_eq(prin.isNullPrincipal, kind == 'nullPrincipal');
     do_check_eq(prin.isCodebasePrincipal, kind == 'codebasePrincipal');
     do_check_eq(prin.isExpandedPrincipal, kind == 'expandedPrincipal');
     do_check_eq(prin.isSystemPrincipal, kind == 'systemPrincipal');
@@ -213,17 +196,16 @@ function run_test() {
   var emptyAttrs = ChromeUtils.fillNonDefaultOriginAttributes({});
   checkValues(emptyAttrs);
 
   var uri = "http://example.org";
   var tests = [
     [ "", {} ],
     [ "^appId=5", {appId: 5} ],
     [ "^userContextId=3", {userContextId: 3} ],
-    [ "^addonId=fooBar", {addonId: "fooBar"} ],
     [ "^inBrowser=1", {inIsolatedMozBrowser: true} ],
     [ "^firstPartyDomain=example.org", {firstPartyDomain: "example.org"} ],
     [ "^appId=3&inBrowser=1&userContextId=6",
       {appId: 3, userContextId: 6, inIsolatedMozBrowser: true} ] ];
 
   // check that we can create an origin attributes from an origin properly
   tests.forEach(t => {
     let attrs = ChromeUtils.createOriginAttributesFromOrigin(uri + t[0]);
@@ -299,9 +281,31 @@ function run_test() {
     let orig = ChromeUtils.createOriginAttributesFromOrigin(uri + t[0]);
     checkValues(orig, t[1]);
     let mod = orig;
     mod['firstPartyDomain'] = "";
     checkValues(mod, t[2]);
     do_check_eq(ChromeUtils.originAttributesToSuffix(mod), t[3]);
   });
 
+  var fileURI = makeURI('file:///foo/bar').QueryInterface(Ci.nsIFileURL);
+  var fileTests = [
+    [true, fileURI.spec],
+    [false, "file://UNIVERSAL_FILE_URI_ORIGIN"],
+  ];
+  fileTests.forEach(t => {
+    Services.prefs.setBoolPref("security.fileuri.strict_origin_policy", t[0]);
+    var filePrin = ssm.createCodebasePrincipal(fileURI, {});
+    do_check_eq(filePrin.origin, t[1]);
+  });
+  Services.prefs.clearUserPref("security.fileuri.strict_origin_policy");
+
+  var aboutBlankURI = makeURI('about:blank');
+  var aboutBlankPrin = ssm.createCodebasePrincipal(aboutBlankURI, {});
+  var thrown = false;
+  try {
+    aboutBlankPrin.origin;
+  } catch (e) {
+    thrown = true;
+  }
+  do_check_true(thrown);
+
 }
--- a/config/milestone.txt
+++ b/config/milestone.txt
@@ -5,9 +5,9 @@
 #    x.x.x.x
 #    x.x.x+
 #
 # Referenced by milestone.py.
 # Hopefully I'll be able to automate replacement of *all*
 # hardcoded milestones in the tree from these two files.
 #--------------------------------------------------------
 
-54.0a1
+55.0a1
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_webextension.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension.js
@@ -23,18 +23,17 @@ add_task(function* testWebExtensionsTool
     tab, document, debugBtn,
   } = yield setupTestAboutDebuggingWebExtension(ADDON_NAME, ADDON_MANIFEST_PATH);
 
   // Wait for a notification sent by a script evaluated the test addon via
   // the web console.
   let onCustomMessage = new Promise(done => {
     Services.obs.addObserver(function listener(message, topic) {
       let apiMessage = message.wrappedJSObject;
-      if (!apiMessage.originAttributes ||
-          apiMessage.originAttributes.addonId != ADDON_ID) {
+      if (apiMessage.addonId != ADDON_ID) {
         return;
       }
       Services.obs.removeObserver(listener, "console-api-log-event");
       done(apiMessage.arguments);
     }, "console-api-log-event", false);
   });
 
   // Be careful, this JS function is going to be executed in the addon toolbox,
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_popup.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_popup.js
@@ -39,18 +39,17 @@ function makeWidgetId(id) {
 add_task(function* testWebExtensionsToolboxSwitchToPopup() {
   let {
     tab, document, debugBtn,
   } = yield setupTestAboutDebuggingWebExtension(ADDON_NAME, ADDON_MANIFEST_PATH);
 
   let onReadyForOpenPopup = new Promise(done => {
     Services.obs.addObserver(function listener(message, topic) {
       let apiMessage = message.wrappedJSObject;
-      if (!apiMessage.originAttributes ||
-          apiMessage.originAttributes.addonId != ADDON_ID) {
+      if (apiMessage.addonId != ADDON_ID) {
         return;
       }
 
       if (apiMessage.arguments[0] == "readyForOpenPopup") {
         Services.obs.removeObserver(listener, "console-api-log-event");
         done();
       }
     }, "console-api-log-event", false);
@@ -144,18 +143,17 @@ add_task(function* testWebExtensionsTool
     env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
   });
 
   // Wait for a notification sent by a script evaluated the test addon via
   // the web console.
   let onPopupCustomMessage = new Promise(done => {
     Services.obs.addObserver(function listener(message, topic) {
       let apiMessage = message.wrappedJSObject;
-      if (!apiMessage.originAttributes ||
-          apiMessage.originAttributes.addonId != ADDON_ID) {
+      if (apiMessage.addonId != ADDON_ID) {
         return;
       }
 
       if (apiMessage.arguments[0] == "Popup page function called") {
         Services.obs.removeObserver(listener, "console-api-log-event");
         done(apiMessage.arguments);
       }
     }, "console-api-log-event", false);
--- a/devtools/client/devtools-startup.js
+++ b/devtools/client/devtools-startup.js
@@ -8,17 +8,17 @@
  * core modules like 'devtools-browser.js' that hooks the browser windows
  * and ensure setting up tools.
  *
  * Be careful to lazy load dependencies as much as possible.
  **/
 
 "use strict";
 
-const { interfaces: Ci, utils: Cu } = Components;
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 const kDebuggerPrefs = [
   "devtools.debugger.remote-enabled",
   "devtools.chrome.enabled"
 ];
 const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
 XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
 
 function DevToolsStartup() {}
@@ -119,19 +119,38 @@ DevToolsStartup.prototype = {
     }
     return remoteDebuggingEnabled;
   },
 
   handleDebuggerFlag: function (cmdLine) {
     if (!this._isRemoteDebuggingEnabled()) {
       return;
     }
+
+    let devtoolsThreadResumed = false;
+    let pauseOnStartup = cmdLine.handleFlag("wait-for-jsdebugger", false);
+    if (pauseOnStartup) {
+      let observe = function (subject, topic, data) {
+        devtoolsThreadResumed = true;
+        Services.obs.removeObserver(observe, "devtools-thread-resumed");
+      };
+      Services.obs.addObserver(observe, "devtools-thread-resumed", false);
+    }
+
     const { BrowserToolboxProcess } = Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
     BrowserToolboxProcess.init();
 
+    if (pauseOnStartup) {
+      // Spin the event loop until the debugger connects.
+      let thread = Cc["@mozilla.org/thread-manager;1"].getService().currentThread;
+      while (!devtoolsThreadResumed) {
+        thread.processNextEvent(true);
+      }
+    }
+
     if (cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_AUTO) {
       cmdLine.preventDefault = true;
     }
   },
 
   /**
    * Handle the --start-debugger-server command line flag. The options are:
    * --start-debugger-server
@@ -198,16 +217,19 @@ DevToolsStartup.prototype = {
     if (cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_AUTO) {
       cmdLine.preventDefault = true;
     }
   },
 
   /* eslint-disable max-len */
   helpInfo: "  --jsconsole        Open the Browser Console.\n" +
             "  --jsdebugger       Open the Browser Toolbox.\n" +
+            "  --wait-for-jsdebugger Spin event loop until JS debugger connects.\n" +
+            "                     Enables debugging (some) application startup code paths.\n" +
+            "                     Only has an effect when `--jsdebugger` is also supplied.\n" +
             "  --devtools         Open DevTools on initial load.\n" +
             "  --start-debugger-server [ws:][ <port> | <path> ] Start the debugger server on\n" +
             "                     a TCP port or Unix domain socket path. Defaults to TCP port\n" +
             "                     6000. Use WebSocket protocol if ws: prefix is specified.\n",
   /* eslint-disable max-len */
 
   classID: Components.ID("{9e9a9283-0ce9-4e4a-8f1c-ba129a032c32}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler]),
--- a/devtools/client/framework/test/browser_toolbox_textbox_context_menu.js
+++ b/devtools/client/framework/test/browser_toolbox_textbox_context_menu.js
@@ -31,25 +31,21 @@ add_task(function* () {
   let onContextMenuPopup = once(textboxContextMenu, "popupshowing");
   textboxContextMenu.openPopupAtScreen(0, 0, true);
   yield onContextMenuPopup;
 
   is(cmdUndo.getAttribute("disabled"), "true", "cmdUndo is disabled");
   is(cmdDelete.getAttribute("disabled"), "true", "cmdDelete is disabled");
   is(cmdSelectAll.getAttribute("disabled"), "true", "cmdSelectAll is disabled");
 
-  // Cut/Copy items are enabled in context menu even if there
-  // is no selection. See also Bug 1303033
+  // Cut/Copy/Paste items are enabled in context menu even if there
+  // is no selection. See also Bug 1303033, and 1317322
   is(cmdCut.getAttribute("disabled"), "", "cmdCut is enabled");
   is(cmdCopy.getAttribute("disabled"), "", "cmdCopy is enabled");
-
-  if (isWindows()) {
-    // emptyClipboard only works on Windows (666254), assert paste only for this OS.
-    is(cmdPaste.getAttribute("disabled"), "true", "cmdPaste is disabled");
-  }
+  is(cmdPaste.getAttribute("disabled"), "", "cmdPaste is enabled");
 
   yield cleanup(toolbox);
 });
 
 function* cleanup(toolbox) {
   yield toolbox.destroy();
   gBrowser.removeCurrentTab();
 }
--- a/devtools/client/inspector/computed/test/browser_computed_search-filter_context-menu.js
+++ b/devtools/client/inspector/computed/test/browser_computed_search-filter_context-menu.js
@@ -40,25 +40,21 @@ add_task(function* () {
   EventUtils.synthesizeMouse(searchField, 2, 2,
     {type: "contextmenu", button: 2}, win);
   yield onContextMenuPopup;
 
   is(cmdUndo.getAttribute("disabled"), "true", "cmdUndo is disabled");
   is(cmdDelete.getAttribute("disabled"), "true", "cmdDelete is disabled");
   is(cmdSelectAll.getAttribute("disabled"), "true", "cmdSelectAll is disabled");
 
-  // Cut/Copy items are enabled in context menu even if there
-  // is no selection. See also Bug 1303033
+  // Cut/Copy/Paste items are enabled in context menu even if there is no
+  // selection. See also Bug 1303033, and 1317322
   is(cmdCut.getAttribute("disabled"), "", "cmdCut is enabled");
   is(cmdCopy.getAttribute("disabled"), "", "cmdCopy is enabled");
-
-  if (isWindows()) {
-    // emptyClipboard only works on Windows (666254), assert paste only for this OS.
-    is(cmdPaste.getAttribute("disabled"), "true", "cmdPaste is disabled");
-  }
+  is(cmdPaste.getAttribute("disabled"), "", "cmdPaste is enabled");
 
   info("Closing context menu");
   let onContextMenuHidden = once(searchContextMenu, "popuphidden");
   searchContextMenu.hidePopup();
   yield onContextMenuHidden;
 
   info("Copy text in search field using the context menu");
   searchField.value = TEST_INPUT;
--- a/devtools/client/inspector/rules/test/browser_rules_search-filter_context-menu.js
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_context-menu.js
@@ -39,25 +39,21 @@ add_task(function* () {
   EventUtils.synthesizeMouse(searchField, 2, 2,
     {type: "contextmenu", button: 2}, win);
   yield onContextMenuPopup;
 
   is(cmdUndo.getAttribute("disabled"), "true", "cmdUndo is disabled");
   is(cmdDelete.getAttribute("disabled"), "true", "cmdDelete is disabled");
   is(cmdSelectAll.getAttribute("disabled"), "true", "cmdSelectAll is disabled");
 
-  // Cut/Copy items are enabled in context menu even if there
-  // is no selection. See also Bug 1303033
+  // Cut/Copy/Paste items are enabled in context menu even if there is no
+  // selection. See also Bug 1303033, and 1317322
   is(cmdCut.getAttribute("disabled"), "", "cmdCut is enabled");
   is(cmdCopy.getAttribute("disabled"), "", "cmdCopy is enabled");
-
-  if (isWindows()) {
-    // emptyClipboard only works on Windows (666254), assert paste only for this OS.
-    is(cmdPaste.getAttribute("disabled"), "true", "cmdPaste is disabled");
-  }
+  is(cmdPaste.getAttribute("disabled"), "", "cmdPaste is enabled");
 
   info("Closing context menu");
   let onContextMenuHidden = once(searchContextMenu, "popuphidden");
   searchContextMenu.hidePopup();
   yield onContextMenuHidden;
 
   info("Copy text in search field using the context menu");
   searchField.value = TEST_INPUT;
--- a/devtools/client/inspector/test/browser_inspector_search-filter_context-menu.js
+++ b/devtools/client/inspector/test/browser_inspector_search-filter_context-menu.js
@@ -38,24 +38,20 @@ add_task(function* () {
     {type: "contextmenu", button: 2}, win);
   yield onContextMenuPopup;
 
   is(cmdUndo.getAttribute("disabled"), "true", "cmdUndo is disabled");
   is(cmdDelete.getAttribute("disabled"), "true", "cmdDelete is disabled");
   is(cmdSelectAll.getAttribute("disabled"), "true", "cmdSelectAll is disabled");
 
   // Cut/Copy items are enabled in context menu even if there
-  // is no selection. See also Bug 1303033
+  // is no selection. See also Bug 1303033, and 1317322
   is(cmdCut.getAttribute("disabled"), "", "cmdCut is enabled");
   is(cmdCopy.getAttribute("disabled"), "", "cmdCopy is enabled");
-
-  if (isWindows()) {
-    // emptyClipboard only works on Windows (666254), assert paste only for this OS.
-    is(cmdPaste.getAttribute("disabled"), "true", "cmdPaste is disabled");
-  }
+  is(cmdPaste.getAttribute("disabled"), "", "cmdPaste is enabled");
 
   info("Closing context menu");
   let onContextMenuHidden = once(searchContextMenu, "popuphidden");
   searchContextMenu.hidePopup();
   yield onContextMenuHidden;
 
   info("Copy text in search field using the context menu");
   searchBox.value = TEST_INPUT;
--- a/devtools/client/shared/components/reps/reps.js
+++ b/devtools/client/shared/components/reps/reps.js
@@ -55,23 +55,25 @@ return /******/ (function(modules) { // 
 /***/ function(module, exports, __webpack_require__) {
 
 	const { MODE } = __webpack_require__(1);
 	const { REPS } = __webpack_require__(2);
 	const {
 	  createFactories,
 	  parseURLEncodedText,
 	  parseURLParams,
-	  getSelectableInInspectorGrips
+	  getSelectableInInspectorGrips,
+	  maybeEscapePropertyName
 	} = __webpack_require__(4);
 	
 	module.exports = {
 	  REPS,
 	  MODE,
 	  createFactories,
+	  maybeEscapePropertyName,
 	  parseURLEncodedText,
 	  parseURLParams,
 	  getSelectableInInspectorGrips
 	};
 
 /***/ },
 /* 1 */
 /***/ function(module, exports) {
@@ -349,16 +351,39 @@ return /******/ (function(modules) { // 
 	      }
 	      // Other surrogate characters are passed through.
 	      return match;
 	    }
 	    return "\\u" + ("0000" + c.toString(16)).substr(-4);
 	  }) + "\"";
 	}
 	
+	/**
+	 * Escape a property name, if needed.  "Escaping" in this context
+	 * means surrounding the property name with quotes.
+	 *
+	 * @param {String}
+	 *        name the property name
+	 * @return {String} either the input, or the input surrounded by
+	 *                  quotes, properly quoted in JS syntax.
+	 */
+	function maybeEscapePropertyName(name) {
+	  // Quote the property name if it needs quoting.  This particular
+	  // test is an approximation; see
+	  // https://mathiasbynens.be/notes/javascript-properties.  However,
+	  // the full solution requires a fair amount of Unicode data, and so
+	  // let's defer that until either it's important, or the \p regexp
+	  // syntax lands, see
+	  // https://github.com/tc39/proposal-regexp-unicode-property-escapes.
+	  if (!/^\w+$/.test(name)) {
+	    name = escapeString(name);
+	  }
+	  return name;
+	}
+	
 	function cropMultipleLines(text, limit) {
 	  return escapeNewLines(cropString(text, limit));
 	}
 	
 	function rawCropString(text, limit, alternativeText) {
 	  if (!alternativeText) {
 	    alternativeText = "\u2026";
 	  }
@@ -582,17 +607,18 @@ return /******/ (function(modules) { // 
 	  sanitizeString,
 	  escapeString,
 	  wrapRender,
 	  cropMultipleLines,
 	  parseURLParams,
 	  parseURLEncodedText,
 	  getFileName,
 	  getURLDisplayString,
-	  getSelectableInInspectorGrips
+	  getSelectableInInspectorGrips,
+	  maybeEscapePropertyName
 	};
 
 /***/ },
 /* 5 */
 /***/ function(module, exports) {
 
 	module.exports = {
 	  ELEMENT_NODE: 1,
@@ -973,19 +999,19 @@ return /******/ (function(modules) { // 
 	    function isInteger(x) {
 	      let y = parseInt(x, 10);
 	      if (isNaN(y)) {
 	        return false;
 	      }
 	      return x === y.toString();
 	    }
 	
-	    let props = Object.getOwnPropertyNames(array);
-	    for (let i = 0; i < props.length; i++) {
-	      let p = props[i];
+	    let propsArray = Object.getOwnPropertyNames(array);
+	    for (let i = 0; i < propsArray.length; i++) {
+	      let p = propsArray[i];
 	
 	      // Valid indexes are skipped
 	      if (isInteger(p)) {
 	        continue;
 	      }
 	
 	      // Ignore standard 'length' property, anything else is custom.
 	      if (p != "length") {
@@ -1263,16 +1289,17 @@ return /******/ (function(modules) { // 
 /***/ },
 /* 14 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
 	const React = __webpack_require__(3);
 	const {
 	  createFactories,
+	  maybeEscapePropertyName,
 	  wrapRender
 	} = __webpack_require__(4);
 	const { MODE } = __webpack_require__(1);
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Property for Obj (local JS objects), Grip (remote JS objects)
@@ -1290,34 +1317,42 @@ return /******/ (function(modules) { // 
 	    // Delimiter character used to separate individual properties.
 	    delim: React.PropTypes.string,
 	    // @TODO Change this to Object.values once it's supported in Node's version of V8
 	    mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
 	    objectLink: React.PropTypes.func,
 	    attachedActorIds: React.PropTypes.array,
 	    onDOMNodeMouseOver: React.PropTypes.func,
 	    onDOMNodeMouseOut: React.PropTypes.func,
-	    onInspectIconClick: React.PropTypes.func
+	    onInspectIconClick: React.PropTypes.func,
+	    // Normally a PropRep will quote a property name that isn't valid
+	    // when unquoted; but this flag can be used to suppress the
+	    // quoting.
+	    suppressQuotes: React.PropTypes.bool
 	  },
 	
 	  render: wrapRender(function () {
 	    const Grip = __webpack_require__(15);
 	    let { Rep } = createFactories(__webpack_require__(2));
 	    let {
 	      name,
 	      mode,
 	      equal,
-	      delim
+	      delim,
+	      suppressQuotes
 	    } = this.props;
 	
 	    let key;
 	    // The key can be a simple string, for plain objects,
 	    // or another object for maps and weakmaps.
-	    if (typeof this.props.name === "string") {
-	      key = span({ "className": "nodeName" }, this.props.name);
+	    if (typeof name === "string") {
+	      if (!suppressQuotes) {
+	        name = maybeEscapePropertyName(name);
+	      }
+	      key = span({ "className": "nodeName" }, name);
 	    } else {
 	      key = Rep(Object.assign({}, this.props, {
 	        object: name,
 	        mode: mode || MODE.TINY,
 	        defaultRep: Grip
 	      }));
 	    }
 	
@@ -1419,64 +1454,72 @@ return /******/ (function(modules) { // 
 	    if (indexes.length < max && indexes.length < propertiesLength) {
 	      // There are not enough props yet. Then add uninteresting props to display them.
 	      indexes = indexes.concat(this.getPropIndexes(properties, max - indexes.length, (t, value, name) => {
 	        return !isInterestingProp(t, value, name);
 	      }));
 	    }
 	
 	    const truncate = Object.keys(properties).length > max;
-	    let props = this.getProps(properties, indexes, truncate);
+	    // The server synthesizes some property names for a Proxy, like
+	    // <target> and <handler>; we don't want to quote these because,
+	    // as synthetic properties, they appear more natural when
+	    // unquoted.
+	    const suppressQuotes = object.class === "Proxy";
+	    let propsArray = this.getProps(properties, indexes, truncate, suppressQuotes);
 	    if (truncate) {
 	      // There are some undisplayed props. Then display "more...".
 	      let objectLink = this.props.objectLink || span;
 	
-	      props.push(Caption({
+	      propsArray.push(Caption({
 	        object: objectLink({
 	          object: object
 	        }, `${propertiesLength - max} more…`)
 	      }));
 	    }
 	
-	    return props;
+	    return propsArray;
 	  },
 	
 	  /**
 	   * Get props ordered by index.
 	   *
 	   * @param {Object} properties Props object.
 	   * @param {Array} indexes Indexes of props.
 	   * @param {Boolean} truncate true if the grip will be truncated.
+	   * @param {Boolean} suppressQuotes true if we should suppress quotes
+	   *                  on property names.
 	   * @return {Array} Props.
 	   */
-	  getProps: function (properties, indexes, truncate) {
-	    let props = [];
+	  getProps: function (properties, indexes, truncate, suppressQuotes) {
+	    let propsArray = [];
 	
 	    // Make indexes ordered by ascending.
 	    indexes.sort(function (a, b) {
 	      return a - b;
 	    });
 	
 	    indexes.forEach(i => {
 	      let name = Object.keys(properties)[i];
 	      let value = this.getPropValue(properties[name]);
 	
-	      props.push(PropRep(Object.assign({}, this.props, {
+	      propsArray.push(PropRep(Object.assign({}, this.props, {
 	        mode: MODE.TINY,
 	        name: name,
 	        object: value,
 	        equal: ": ",
 	        delim: i !== indexes.length - 1 || truncate ? ", " : "",
 	        defaultRep: Grip,
 	        // Do not propagate title to properties reps
-	        title: undefined
+	        title: undefined,
+	        suppressQuotes
 	      })));
 	    });
 	
-	    return props;
+	    return propsArray;
 	  },
 	
 	  /**
 	   * Get the indexes of props in the object.
 	   *
 	   * @param {Object} properties Props object.
 	   * @param {Number} max The maximum length of indexes array.
 	   * @param {Function} filter Filter the props you want.
@@ -1525,30 +1568,30 @@ return /******/ (function(modules) { // 
 	        value = property.getterValue;
 	      }
 	    }
 	    return value;
 	  },
 	
 	  render: wrapRender(function () {
 	    let object = this.props.object;
-	    let props = this.safePropIterator(object, this.props.mode === MODE.LONG ? 10 : 3);
+	    let propsArray = this.safePropIterator(object, this.props.mode === MODE.LONG ? 10 : 3);
 	
 	    let objectLink = this.props.objectLink || span;
 	    if (this.props.mode === MODE.TINY) {
 	      return span({ className: "objectBox objectBox-object" }, this.getTitle(object), objectLink({
 	        className: "objectLeftBrace",
 	        object: object
 	      }, ""));
 	    }
 	
 	    return span({ className: "objectBox objectBox-object" }, this.getTitle(object), objectLink({
 	      className: "objectLeftBrace",
 	      object: object
-	    }, " { "), ...props, objectLink({
+	    }, " { "), ...propsArray, objectLink({
 	      className: "objectRightBrace",
 	      object: object
 	    }, " }"));
 	  })
 	});
 	
 	// Registration
 	function supportsObject(object, type) {
@@ -2109,17 +2152,18 @@ return /******/ (function(modules) { // 
 	
 	    return keys.map((key, i) => {
 	      let object = promiseState[key];
 	      return PropRep(Object.assign({}, this.props, {
 	        mode: MODE.TINY,
 	        name: `<${key}>`,
 	        object,
 	        equal: ": ",
-	        delim: i < keys.length - 1 ? ", " : ""
+	        delim: i < keys.length - 1 ? ", " : "",
+	        suppressQuotes: true
 	      }));
 	    });
 	  },
 	
 	  render: wrapRender(function () {
 	    const object = this.props.object;
 	    const { promiseState } = object;
 	    let objectLink = this.props.objectLink || span;
@@ -2131,21 +2175,21 @@ return /******/ (function(modules) { // 
 	        className: "objectLeftBrace",
 	        object: object
 	      }, " { "), Rep({ object: promiseState.state }), objectLink({
 	        className: "objectRightBrace",
 	        object: object
 	      }, " }"));
 	    }
 	
-	    const props = this.getProps(promiseState);
+	    const propsArray = this.getProps(promiseState);
 	    return span({ className: "objectBox objectBox-object" }, this.getTitle(object), objectLink({
 	      className: "objectLeftBrace",
 	      object: object
-	    }, " { "), ...props, objectLink({
+	    }, " { "), ...propsArray, objectLink({
 	      className: "objectRightBrace",
 	      object: object
 	    }, " }"));
 	  })
 	});
 	
 	// Registration
 	function supportsObject(object, type) {
@@ -2451,17 +2495,17 @@ return /******/ (function(modules) { // 
 	      }
 	
 	      if (onInspectIconClick) {
 	        inspectIcon = Svg("open-inspector", {
 	          element: "a",
 	          draggable: false,
 	          // TODO: Localize this with "openNodeInInspector" when Bug 1317038 lands
 	          title: "Click to select the node in the inspector",
-	          onClick: () => onInspectIconClick(object)
+	          onClick: e => onInspectIconClick(object, e)
 	        });
 	      }
 	    }
 	
 	    return span(baseConfig, objectLink({ object }, ...elements), inspectIcon);
 	  })
 	});
 	
@@ -2742,17 +2786,17 @@ return /******/ (function(modules) { // 
 	      }
 	
 	      if (onInspectIconClick) {
 	        inspectIcon = Svg("open-inspector", {
 	          element: "a",
 	          draggable: false,
 	          // TODO: Localize this with "openNodeInInspector" when Bug 1317038 lands
 	          title: "Click to select the node in the inspector",
-	          onClick: () => onInspectIconClick(grip)
+	          onClick: e => onInspectIconClick(grip, e)
 	        });
 	      }
 	    }
 	
 	    if (mode === MODE.TINY) {
 	      return DOM.span(baseConfig, this.getTitle(grip), inspectIcon);
 	    }
 	
@@ -3399,30 +3443,30 @@ return /******/ (function(modules) { // 
 	      }
 	
 	      return indexes;
 	    }, []);
 	  },
 	
 	  render: wrapRender(function () {
 	    let object = this.props.object;
-	    let props = this.safeEntriesIterator(object, this.props.mode === MODE.LONG ? 10 : 3);
+	    let propsArray = this.safeEntriesIterator(object, this.props.mode === MODE.LONG ? 10 : 3);
 	
 	    let objectLink = this.props.objectLink || span;
 	    if (this.props.mode === MODE.TINY) {
 	      return span({ className: "objectBox objectBox-object" }, this.getTitle(object), objectLink({
 	        className: "objectLeftBrace",
 	        object: object
 	      }, ""));
 	    }
 	
 	    return span({ className: "objectBox objectBox-object" }, this.getTitle(object), objectLink({
 	      className: "objectLeftBrace",
 	      object: object
-	    }, " { "), props, objectLink({
+	    }, " { "), propsArray, objectLink({
 	      className: "objectRightBrace",
 	      object: object
 	    }, " }"));
 	  })
 	});
 	
 	function supportsObject(grip, type) {
 	  if (!isGrip(grip)) {
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_array.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_array.html
@@ -9,18 +9,18 @@ Test ArrayRep rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - ArrayRep</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 "use strict";
 /* import-globals-from head.js */
 
 window.onload = Task.async(function* () {
   const { REPS, MODE } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, ArrayRep } = REPS;
 
   let componentUnderTest = ArrayRep;
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_attribute.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_attribute.html
@@ -9,18 +9,18 @@ Test Attribute rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - Attribute</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 window.onload = Task.async(function* () {
   try {
     const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
     let { Rep, Attribute } = REPS;
 
     let gripStub = {
       "type": "object",
       "class": "Attr",
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_comment-node.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_comment-node.html
@@ -9,18 +9,18 @@ Test comment-node rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - comment-node</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 "use strict";
 
 window.onload = Task.async(function* () {
   try {
     const { REPS, MODE } = browserRequire("devtools/client/shared/components/reps/reps");
     let { Rep, CommentNode } = REPS;
 
     let gripStub = {
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_date-time.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_date-time.html
@@ -9,18 +9,18 @@ Test DateTime rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - DateTime</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 window.onload = Task.async(function* () {
   const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, DateTime } = REPS;
 
   try {
     testValid();
     testInvalid();
   } catch(e) {
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_document.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_document.html
@@ -9,18 +9,18 @@ Test Document rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - Document</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 window.onload = Task.async(function* () {
   const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, Document } = REPS;
 
   try {
     let gripStub = {
       "type": "object",
       "class": "HTMLDocument",
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_element-node.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_element-node.html
@@ -9,18 +9,18 @@ Test Element node rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - Element node</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 "use strict";
 
 window.onload = Task.async(function* () {
   const {
     REPS,
     MODE,
     getSelectableInInspectorGrips,
   } = browserRequire("devtools/client/shared/components/reps/reps");
@@ -228,18 +228,21 @@ window.onload = Task.async(function* () 
   function testOnInspectIconClick() {
     const stub = getGripStub("testNode");
     const grips = getSelectableInInspectorGrips(stub);
     is(grips.length, 1, "the stub has one node grip");
 
     const attachedActorIds = getStubAttachedActorIds(grips);
 
     let inspectIconClickedValue = null;
-    let onInspectIconClick = (object) => {
+    let inspectIconClickedEvent = null;
+
+    let onInspectIconClick = (object, event) => {
       inspectIconClickedValue = object;
+      inspectIconClickedEvent = event;
     };
 
     const renderedComponentWithoutInspectIcon = renderComponent(ElementNode.rep, {
       object: stub,
       onInspectIconClick,
       attachedActorIds: ["someOtherId"]
     });
     is(renderedComponentWithoutInspectIcon.querySelector(".open-inspector"), null,
@@ -260,16 +263,18 @@ window.onload = Task.async(function* () 
     });
 
     inspectIconNode = renderedComponent.querySelector(".open-inspector");
     ok(inspectIconNode !== null, "There is an inspect icon as expected");
     TestUtils.Simulate.click(inspectIconNode);
 
     is(inspectIconClickedValue, grips[0],
       "onInspectIconClick is called with expected value when inspect icon is clicked");
+    ok(inspectIconClickedEvent !== null && inspectIconClickedEvent.type === "click",
+      "onInspectIconClick forwarded the original event to the callback");
   }
 
   function getGripStub(name) {
     switch (name) {
       case "testBodyNode":
         return {
           "type": "object",
           "actor": "server1.conn1.child1/obj30",
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_error.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_error.html
@@ -9,18 +9,18 @@ Test Error rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - Error</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 "use strict";
 
 window.onload = Task.async(function* () {
   const { REPS, MODE } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, ErrorRep } = REPS;
 
   try {
     // Test errors with different properties
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_event.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_event.html
@@ -9,18 +9,18 @@ Test Event rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - Event</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 window.onload = Task.async(function* () {
   const {
     REPS,
     MODE,
     getSelectableInInspectorGrips,
   } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, Event } = REPS;
 
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_failure.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_failure.html
@@ -9,18 +9,18 @@ Test fallback for rep rendering when a r
 <head>
   <meta charset="utf-8">
   <title>Rep test - Failure</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 window.onload = Task.async(function* () {
   try {
     const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
     let { Rep, ArrayRep, RegExp } = REPS;
 
     // Force the RegExp rep to crash by creating RegExp grip that throws when accessing
     // the displayString property
     let gripStub = {
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_function.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_function.html
@@ -9,18 +9,18 @@ Test Func rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - Func</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 window.onload = Task.async(function* () {
   const { REPS, MODE } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, Func } = REPS;
 
   const componentUnderTest = Func;
 
   try {
     // Test that correct rep is chosen
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_grip-array.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_grip-array.html
@@ -9,18 +9,18 @@ Test GripArray rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - GripArray</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 window.onload = Task.async(function* () {
   const {
     REPS,
     MODE,
     getSelectableInInspectorGrips,
   } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, GripArray } = REPS;
 
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_grip-map.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_grip-map.html
@@ -9,18 +9,18 @@ Test GripMap rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - GripMap</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 "use strict";
 
 window.onload = Task.async(function* () {
   const {
     REPS,
     MODE,
     getSelectableInInspectorGrips,
   } = browserRequire("devtools/client/shared/components/reps/reps");
@@ -151,17 +151,17 @@ window.onload = Task.async(function* () 
     testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
   }
 
   function testMaxEntries() {
     // Test object:
     // `new Map([["key-a","value-a"], ["key-b","value-b"], ["key-c","value-c"]])`
     const testName = "testMaxEntries";
 
-    const defaultOutput = `Map { key-a: "value-a", key-b: "value-b", key-c: "value-c" }`;
+    const defaultOutput = `Map { "key-a": "value-a", "key-b": "value-b", "key-c": "value-c" }`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
       },
       {
         mode: MODE.TINY,
@@ -181,20 +181,20 @@ window.onload = Task.async(function* () 
   }
 
   function testMoreThanMaxEntries() {
     // Test object = `new Map(
     //   [["key-0", "value-0"], ["key-1", "value-1"]], …, ["key-100", "value-100"]]}`
     const testName = "testMoreThanMaxEntries";
 
     const defaultOutput =
-      `Map { key-0: "value-0", key-1: "value-1", key-2: "value-2", 98 more… }`;
+      `Map { "key-0": "value-0", "key-1": "value-1", "key-2": "value-2", 98 more… }`;
 
     // Generate string with 101 entries, which is the max limit for 'long' mode.
-    let longString = Array.from({length: 10}).map((_, i) => `key-${i}: "value-${i}"`);
+    let longString = Array.from({length: 10}).map((_, i) => `"key-${i}": "value-${i}"`);
     const longOutput = `Map { ${longString.join(", ")}, 91 more… }`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
       },
       {
@@ -215,19 +215,19 @@ window.onload = Task.async(function* () 
   }
 
   function testUninterestingEntries() {
     // Test object:
     // `new Map([["key-a",null], ["key-b",undefined], ["key-c","value-c"], ["key-d",4]])`
     const testName = "testUninterestingEntries";
 
     const defaultOutput =
-      `Map { key-a: null, key-c: "value-c", key-d: 4, 1 more… }`;
+      `Map { "key-a": null, "key-c": "value-c", "key-d": 4, 1 more… }`;
     const longOutput =
-      `Map { key-a: null, key-b: undefined, key-c: "value-c", key-d: 4 }`;
+      `Map { "key-a": null, "key-b": undefined, "key-c": "value-c", "key-d": 4 }`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
       },
       {
         mode: MODE.TINY,
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_grip.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_grip.html
@@ -9,18 +9,18 @@ Test grip rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - grip</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 window.onload = Task.async(function* () {
   const {
     REPS,
     MODE,
     getSelectableInInspectorGrips,
   } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, Grip } = REPS;
 
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_infinity.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_infinity.html
@@ -9,18 +9,18 @@ Test Infinity rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - Infinity</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 "use strict";
 
 window.onload = Task.async(function* () {
   const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, InfinityRep } = REPS;
 
   try {
     yield testInfinity();
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_long-string.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_long-string.html
@@ -9,18 +9,18 @@ Test LongString rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - LongString</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 window.onload = Task.async(function* () {
   const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, LongStringRep } = REPS;
 
   try {
     // Test that correct rep is chosen
     const renderedRep = shallowRenderComponent(Rep, { object: getGripStub("testMultiline") });
     is(renderedRep.type, LongStringRep.rep,
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_nan.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_nan.html
@@ -9,18 +9,18 @@ Test NaN rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - NaN</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 "use strict";
 
 window.onload = Task.async(function* () {
   const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, NaNRep } = REPS;
 
   try {
     yield testNaN();
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_null.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_null.html
@@ -9,18 +9,18 @@ Test Null rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - Null</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 window.onload = Task.async(function* () {
   try {
     const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
     let { Rep, Null } = REPS;
 
     let gripStub = {
       "type": "null"
     };
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_number.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_number.html
@@ -9,18 +9,18 @@ Test Number rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - Number</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 window.onload = Task.async(function* () {
   const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, Number } = REPS;
 
   try {
     yield testInt();
     yield testBoolean();
     yield testNegativeZero();
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_object-with-text.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_object-with-text.html
@@ -9,18 +9,18 @@ Test ObjectWithText rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - ObjectWithText</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 window.onload = Task.async(function* () {
   try {
     const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
     let { Rep, ObjectWithText } = REPS;
 
     let gripStub = {
       "type": "object",
       "class": "CSSStyleRule",
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_object-with-url.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_object-with-url.html
@@ -9,18 +9,18 @@ Test ObjectWithURL rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - ObjectWithURL</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 window.onload = Task.async(function* () {
   try {
     let ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
     let React = browserRequire("devtools/client/shared/vendor/react");
 
     const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
     let { Rep, ObjectWithURL } = REPS;
 
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_object.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_object.html
@@ -9,32 +9,35 @@ Test Obj rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - Obj</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 window.onload = Task.async(function* () {
   const { REPS, MODE } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, Obj } = REPS;
 
   const componentUnderTest = Obj;
 
   try {
     yield testBasic();
 
     // Test property iterator
     yield testMaxProps();
     yield testMoreThanMaxProps();
     yield testUninterestingProps();
 
+    // Test that unusual property names are escaped.
+    yield testEscapedPropertyNames();
+
     // Test that properties are rendered as expected by PropRep
     yield testNested();
 
     // Test that 'more' property doesn't clobber the caption.
     yield testMoreProp();
 
     // Test that you can pass a custom title to the Rep
     yield testCustomTitle();
@@ -154,16 +157,42 @@ window.onload = Task.async(function* () 
         mode: MODE.LONG,
         expectedOutput: defaultOutput,
       }
     ];
 
     testRepRenderModes(modeTests, "testUninterestingProps", componentUnderTest, stub);
   }
 
+  function testEscapedPropertyNames() {
+    const stub = {"":1, "quote-this":2, noquotes:3};
+    const defaultOutput = `Object { "": 1, "quote-this": 2, noquotes: 3 }`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: MODE.TINY,
+        expectedOutput: `Object`,
+      },
+      {
+        mode: MODE.SHORT,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: MODE.LONG,
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, "testEscapedPropertyNames", componentUnderTest, stub);
+  }
+
   function testNested() {
     const stub = {
       objProp: {
         id: 1,
         arr: [2]
       },
       strProp: "test string",
       arrProp: [1]
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_promise.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_promise.html
@@ -9,18 +9,18 @@ Test Promise rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - Promise</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 "use strict";
 
 window.onload = Task.async(function* () {
   const {
     REPS,
     MODE,
     getSelectableInInspectorGrips,
   } = browserRequire("devtools/client/shared/components/reps/reps");
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_regexp.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_regexp.html
@@ -9,18 +9,18 @@ Test RegExp rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - RegExp</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 window.onload = Task.async(function* () {
   try {
     const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
     let { Rep, RegExp } = REPS;
 
     let gripStub = {
       "type": "object",
       "class": "RegExp",
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_string.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_string.html
@@ -9,18 +9,18 @@ Test String rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - String</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 window.onload = Task.async(function* () {
   const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, StringRep } = REPS;
 
   const test_cases = [{
     name: "testMultiline",
     props: {
       object: "aaaaaaaaaaaaaaaaaaaaa\nbbbbbbbbbbbbbbbbbbb\ncccccccccccccccc\n",
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_stylesheet.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_stylesheet.html
@@ -9,18 +9,18 @@ Test Stylesheet rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - Stylesheet</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 window.onload = Task.async(function* () {
   try {
     const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
     let { Rep, StyleSheet } = REPS;
 
     let gripStub = {
       "type": "object",
       "class": "CSSStyleSheet",
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_symbol.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_symbol.html
@@ -9,18 +9,18 @@ Test Symbol rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - String</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 "use strict";
 /* import-globals-from head.js */
 
 window.onload = Task.async(function* () {
   const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, SymbolRep } = REPS;
 
   let gripStubs = new Map();
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_text-node.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_text-node.html
@@ -9,18 +9,18 @@ Test text-node rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - text-node</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 "use strict";
 
 window.onload = Task.async(function* () {
   const {
     REPS,
     MODE,
     getSelectableInInspectorGrips,
   } = browserRequire("devtools/client/shared/components/reps/reps");
@@ -167,18 +167,21 @@ window.onload = Task.async(function* () 
   function testOnInspectIconClick() {
     const stub = gripStubs.get("testRendering");
 
     const grips = getSelectableInInspectorGrips(stub);
     is(grips.length, 1, "the stub has one text node grip");
     const attachedActorIds = getStubAttachedActorIds(grips);
 
     let inspectIconClickedValue = null;
-    let onInspectIconClick = (object) => {
+    let inspectIconClickedEvent = null;
+
+    let onInspectIconClick = (object, event) => {
       inspectIconClickedValue = object;
+      inspectIconClickedEvent = event;
     };
 
     const renderedComponentWithoutInspectIcon = renderComponent(TextNode.rep, {
       object: stub,
       onInspectIconClick,
       attachedActorIds: ["someOtherId"]
     });
     is(renderedComponentWithoutInspectIcon.querySelector(".open-inspector"), null,
@@ -191,14 +194,16 @@ window.onload = Task.async(function* () 
     });
 
     const inspectIconNode = renderedComponent.querySelector(".open-inspector");
     ok(inspectIconNode !== null, "There is an inspect icon as expected");
     TestUtils.Simulate.click(inspectIconNode);
 
     is(inspectIconClickedValue, grips[0],
       "onInspectIconClick is called with expected value when inspect icon is clicked");
+    ok(inspectIconClickedEvent !== null && inspectIconClickedEvent.type === "click",
+      "onInspectIconClick forwarded the original event to the callback");
   }
 });
 </script>
 </pre>
 </body>
 </html>
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_undefined.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_undefined.html
@@ -9,18 +9,18 @@ Test undefined rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - undefined</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 window.onload = Task.async(function* () {
   try {
     let ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
     let React = browserRequire("devtools/client/shared/vendor/react");
     const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
     let { Rep, Undefined } = REPS;
 
     let gripStub = {
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_window.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_window.html
@@ -9,18 +9,18 @@ Test window rep
 <head>
   <meta charset="utf-8">
   <title>Rep tests - window</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript"></script>
-<script type="application/javascript">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
 window.onload = Task.async(function* () {
   try {
     let ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
     let React = browserRequire("devtools/client/shared/vendor/react");
 
     const { REPS, MODE } = browserRequire("devtools/client/shared/components/reps/reps");
     let { Rep, Window } = REPS;
 
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stubs/consoleApi.js
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stubs/consoleApi.js
@@ -683,16 +683,17 @@ stubPreparedMessages.set("console.log(%c
   ],
   "notes": null
 }));
 
 stubPackets.set("console.log('foobar', 'test')", {
   "from": "server1.conn0.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
+    "addonId": "",
     "arguments": [
       "foobar",
       "test"
     ],
     "columnNumber": 27,
     "counter": null,
     "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
     "functionName": "triggerPacket",
@@ -707,16 +708,17 @@ stubPackets.set("console.log('foobar', '
     "category": "webdev"
   }
 });
 
 stubPackets.set("console.log(undefined)", {
   "from": "server1.conn1.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
+    "addonId": "",
     "arguments": [
       {
         "type": "undefined"
       }
     ],
     "columnNumber": 27,
     "counter": null,
     "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
@@ -732,16 +734,17 @@ stubPackets.set("console.log(undefined)"
     "category": "webdev"
   }
 });
 
 stubPackets.set("console.warn('danger, will robinson!')", {
   "from": "server1.conn2.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
+    "addonId": "",
     "arguments": [
       "danger, will robinson!"
     ],
     "columnNumber": 27,
     "counter": null,
     "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
     "functionName": "triggerPacket",
     "groupName": "",
@@ -755,16 +758,17 @@ stubPackets.set("console.warn('danger, w
     "category": "webdev"
   }
 });
 
 stubPackets.set("console.log(NaN)", {
   "from": "server1.conn3.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
+    "addonId": "",
     "arguments": [
       {
         "type": "NaN"
       }
     ],
     "columnNumber": 27,
     "counter": null,
     "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
@@ -780,16 +784,17 @@ stubPackets.set("console.log(NaN)", {
     "category": "webdev"
   }
 });
 
 stubPackets.set("console.log(null)", {
   "from": "server1.conn4.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
+    "addonId": "",
     "arguments": [
       {
         "type": "null"
       }
     ],
     "columnNumber": 27,
     "counter": null,
     "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
@@ -805,16 +810,17 @@ stubPackets.set("console.log(null)", {
     "category": "webdev"
   }
 });
 
 stubPackets.set("console.log('鼬')", {
   "from": "server1.conn5.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
+    "addonId": "",
     "arguments": [
       "鼬"
     ],
     "columnNumber": 27,
     "counter": null,
     "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
     "functionName": "triggerPacket",
     "groupName": "",
@@ -828,16 +834,17 @@ stubPackets.set("console.log('鼬')", {
     "category": "webdev"
   }
 });
 
 stubPackets.set("console.clear()", {
   "from": "server1.conn6.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
+    "addonId": "",
     "arguments": [],
     "columnNumber": 27,
     "counter": null,
     "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
     "functionName": "triggerPacket",
     "groupName": "",
     "level": "clear",
     "lineNumber": 1,
@@ -849,16 +856,17 @@ stubPackets.set("console.clear()", {
     "category": "webdev"
   }
 });
 
 stubPackets.set("console.count('bar')", {
   "from": "server1.conn7.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
+    "addonId": "",
     "arguments": [
       "bar"
     ],
     "columnNumber": 27,
     "counter": {
       "count": 1,
       "label": "bar"
     },
@@ -875,16 +883,17 @@ stubPackets.set("console.count('bar')", 
     "category": "webdev"
   }
 });
 
 stubPackets.set("console.assert(false, {message: 'foobar'})", {
   "from": "server1.conn8.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
+    "addonId": "",
     "arguments": [
       {
         "type": "object",
         "actor": "server1.conn8.child1/obj31",
         "class": "Object",
         "extensible": true,
         "frozen": false,
         "sealed": false,
@@ -928,16 +937,17 @@ stubPackets.set("console.assert(false, {
     "category": "webdev"
   }
 });
 
 stubPackets.set("console.log('hello \nfrom \rthe \"string world!')", {
   "from": "server1.conn9.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
+    "addonId": "",
     "arguments": [
       "hello \nfrom \rthe \"string world!"
     ],
     "columnNumber": 27,
     "counter": null,
     "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
     "functionName": "triggerPacket",
     "groupName": "",
@@ -951,16 +961,17 @@ stubPackets.set("console.log('hello \nfr
     "category": "webdev"
   }
 });
 
 stubPackets.set("console.log('úṇĩçödê țĕșť')", {
   "from": "server1.conn10.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
+    "addonId": "",
     "arguments": [
       "úṇĩçödê țĕșť"
     ],
     "columnNumber": 27,
     "counter": null,
     "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
     "functionName": "triggerPacket",
     "groupName": "",
@@ -974,16 +985,17 @@ stubPackets.set("console.log('úṇĩçödê țĕșť')", {
     "category": "webdev"
   }
 });
 
 stubPackets.set("console.dirxml(window)", {
   "from": "server1.conn11.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
+    "addonId": "",
     "arguments": [
       {
         "type": "object",
         "actor": "server1.conn11.child1/obj31",
         "class": "Window",
         "extensible": true,
         "frozen": false,
         "sealed": false,
@@ -1009,16 +1021,17 @@ stubPackets.set("console.dirxml(window)"
     "category": "webdev"
   }
 });
 
 stubPackets.set("console.trace()", {
   "from": "server1.conn12.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
+    "addonId": "",
     "arguments": [],
     "columnNumber": 3,
     "counter": null,
     "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
     "functionName": "testStacktraceFiltering",
     "groupName": "",
     "level": "trace",
     "lineNumber": 3,
@@ -1053,16 +1066,17 @@ stubPackets.set("console.trace()", {
     "category": "webdev"
   }
 });
 
 stubPackets.set("console.time('bar')", {
   "from": "server1.conn13.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
+    "addonId": "",
     "arguments": [
       "bar"
     ],
     "columnNumber": 1,
     "counter": null,
     "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
     "functionName": "triggerPacket",
     "groupName": "",
@@ -1078,16 +1092,17 @@ stubPackets.set("console.time('bar')", {
     "category": "webdev"
   }
 });
 
 stubPackets.set("console.timeEnd('bar')", {
   "from": "server1.conn13.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
+    "addonId": "",
     "arguments": [
       "bar"
     ],
     "columnNumber": 1,
     "counter": null,
     "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
     "functionName": "triggerPacket",
     "groupName": "",
@@ -1104,16 +1119,17 @@ stubPackets.set("console.timeEnd('bar')"
     "category": "webdev"
   }
 });
 
 stubPackets.set("console.table('bar')", {
   "from": "server1.conn14.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
+    "addonId": "",
     "arguments": [
       "bar"
     ],
     "columnNumber": 1,
     "counter": null,
     "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
     "functionName": "triggerPacket",
     "groupName": "",
@@ -1127,16 +1143,17 @@ stubPackets.set("console.table('bar')", 
     "category": "webdev"
   }
 });
 
 stubPackets.set("console.table(['a', 'b', 'c'])", {
   "from": "server1.conn15.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
+    "addonId": "",
     "arguments": [
       {
         "type": "object",
         "actor": "server1.conn15.child1/obj31",
         "class": "Array",
         "extensible": true,
         "frozen": false,
         "sealed": false,
@@ -1167,16 +1184,17 @@ stubPackets.set("console.table(['a', 'b'
     "category": "webdev"
   }
 });
 
 stubPackets.set("console.group('bar')", {
   "from": "server1.conn16.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
+    "addonId": "",
     "arguments": [
       "bar"
     ],
     "columnNumber": 1,
     "counter": null,
     "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
     "functionName": "triggerPacket",
     "groupName": "bar",
@@ -1190,16 +1208,17 @@ stubPackets.set("console.group('bar')", 
     "category": "webdev"
   }
 });
 
 stubPackets.set("console.groupEnd('bar')", {
   "from": "server1.conn16.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
+    "addonId": "",
     "arguments": [
       "bar"
     ],
     "columnNumber": 1,
     "counter": null,
     "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
     "functionName": "triggerPacket",
     "groupName": "bar",
@@ -1213,16 +1232,17 @@ stubPackets.set("console.groupEnd('bar')
     "category": "webdev"
   }
 });
 
 stubPackets.set("console.groupCollapsed('foo')", {
   "from": "server1.conn17.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
+    "addonId": "",
     "arguments": [
       "foo"
     ],
     "columnNumber": 1,
     "counter": null,
     "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
     "functionName": "triggerPacket",
     "groupName": "foo",
@@ -1236,16 +1256,17 @@ stubPackets.set("console.groupCollapsed(
     "category": "webdev"
   }
 });
 
 stubPackets.set("console.groupEnd('foo')", {
   "from": "server1.conn17.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
+    "addonId": "",
     "arguments": [
       "foo"
     ],
     "columnNumber": 1,
     "counter": null,
     "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
     "functionName": "triggerPacket",
     "groupName": "foo",
@@ -1259,16 +1280,17 @@ stubPackets.set("console.groupEnd('foo')
     "category": "webdev"
   }
 });
 
 stubPackets.set("console.group()", {
   "from": "server1.conn18.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
+    "addonId": "",
     "arguments": [],
     "columnNumber": 1,
     "counter": null,
     "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
     "functionName": "triggerPacket",
     "groupName": "",
     "level": "group",
     "lineNumber": 2,
@@ -1280,16 +1302,17 @@ stubPackets.set("console.group()", {
     "category": "webdev"
   }
 });
 
 stubPackets.set("console.groupEnd()", {
   "from": "server1.conn18.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
+    "addonId": "",
     "arguments": [],
     "columnNumber": 1,
     "counter": null,
     "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
     "functionName": "triggerPacket",
     "groupName": "",
     "level": "groupEnd",
     "lineNumber": 3,
@@ -1301,16 +1324,17 @@ stubPackets.set("console.groupEnd()", {
     "category": "webdev"
   }
 });
 
 stubPackets.set("console.log(%cfoobar)", {
   "from": "server1.conn19.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
+    "addonId": "",
     "arguments": [
       "foo",
       "bar"
     ],
     "columnNumber": 1,
     "counter": null,
     "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
     "functionName": "triggerPacket",
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stubs/networkEvent.js
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stubs/networkEvent.js
@@ -156,24 +156,24 @@ stubPackets.set("GET request", {
       {
         "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html",
         "lineNumber": 3,
         "columnNumber": 1,
         "functionName": "triggerPacket",
         "asyncCause": null
       },
       {
-        "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval",
+        "filename": "resource://testing-common/content-task.js line 52 > eval",
         "lineNumber": 7,
         "columnNumber": 9,
         "functionName": null,
         "asyncCause": null
       },
       {
-        "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js",
+        "filename": "resource://testing-common/content-task.js",
         "lineNumber": 53,
         "columnNumber": 20,
         "functionName": null,
         "asyncCause": null
       }
     ]
   },
   "response": {},
@@ -211,24 +211,24 @@ stubPackets.set("GET request eventTiming
         {
           "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html",
           "lineNumber": 3,
           "columnNumber": 1,
           "functionName": "triggerPacket",
           "asyncCause": null
         },
         {
-          "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval",
+          "filename": "resource://testing-common/content-task.js line 52 > eval",
           "lineNumber": 7,
           "columnNumber": 9,
           "functionName": null,
           "asyncCause": null
         },
         {
-          "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js",
+          "filename": "resource://testing-common/content-task.js",
           "lineNumber": 53,
           "columnNumber": 20,
           "functionName": null,
           "asyncCause": null
         }
       ]
     },
     "response": {
@@ -275,24 +275,24 @@ stubPackets.set("XHR GET request", {
       {
         "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html",
         "lineNumber": 4,
         "columnNumber": 1,
         "functionName": "triggerPacket",
         "asyncCause": null
       },
       {
-        "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval",
+        "filename": "resource://testing-common/content-task.js line 52 > eval",
         "lineNumber": 7,
         "columnNumber": 9,
         "functionName": null,
         "asyncCause": null
       },
       {
-        "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js",
+        "filename": "resource://testing-common/content-task.js",
         "lineNumber": 53,
         "columnNumber": 20,
         "functionName": null,
         "asyncCause": null
       }
     ]
   },
   "response": {},
@@ -330,24 +330,24 @@ stubPackets.set("XHR GET request eventTi
         {
           "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html",
           "lineNumber": 4,
           "columnNumber": 1,
           "functionName": "triggerPacket",
           "asyncCause": null
         },
         {
-          "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval",
+          "filename": "resource://testing-common/content-task.js line 52 > eval",
           "lineNumber": 7,
           "columnNumber": 9,
           "functionName": null,
           "asyncCause": null
         },
         {
-          "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js",
+          "filename": "resource://testing-common/content-task.js",
           "lineNumber": 53,
           "columnNumber": 20,
           "functionName": null,
           "asyncCause": null
         }
       ]
     },
     "response": {
@@ -394,24 +394,24 @@ stubPackets.set("XHR POST request", {
       {
         "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html",
         "lineNumber": 4,
         "columnNumber": 1,
         "functionName": "triggerPacket",
         "asyncCause": null
       },
       {
-        "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval",
+        "filename": "resource://testing-common/content-task.js line 52 > eval",
         "lineNumber": 7,
         "columnNumber": 9,
         "functionName": null,
         "asyncCause": null
       },
       {
-        "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js",
+        "filename": "resource://testing-common/content-task.js",
         "lineNumber": 53,
         "columnNumber": 20,
         "functionName": null,
         "asyncCause": null
       }
     ]
   },
   "response": {},
@@ -449,24 +449,24 @@ stubPackets.set("XHR POST request eventT
         {
           "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html",
           "lineNumber": 4,
           "columnNumber": 1,
           "functionName": "triggerPacket",
           "asyncCause": null
         },
         {
-          "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval",
+          "filename": "resource://testing-common/content-task.js line 52 > eval",
           "lineNumber": 7,
           "columnNumber": 9,
           "functionName": null,
           "asyncCause": null
         },
         {
-          "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js",
+          "filename": "resource://testing-common/content-task.js",
           "lineNumber": 53,
           "columnNumber": 20,
           "functionName": null,
           "asyncCause": null
         }
       ]
     },
     "response": {
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stubs/pageError.js
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stubs/pageError.js
@@ -18,17 +18,17 @@ stubPreparedMessages.set("ReferenceError
   "allowRepeating": true,
   "source": "javascript",
   "timeStamp": 1476573167137,
   "type": "log",
   "level": "error",
   "messageText": "ReferenceError: asdf is not defined",
   "parameters": null,
   "repeat": 1,
-  "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"javascript\",\"timeStamp\":1476573167137,\"type\":\"log\",\"level\":\"error\",\"messageText\":\"ReferenceError: asdf is not defined\",\"parameters\":null,\"repeatId\":null,\"stacktrace\":[{\"filename\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html\",\"lineNumber\":3,\"columnNumber\":5,\"functionName\":\"bar\"},{\"filename\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html\",\"lineNumber\":6,\"columnNumber\":5,\"functionName\":\"foo\"},{\"filename\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html\",\"lineNumber\":9,\"columnNumber\":3,\"functionName\":null},{\"filename\":\"chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval\",\"lineNumber\":6,\"columnNumber\":9,\"functionName\":null},{\"filename\":\"chrome://mochikit/content/tests/BrowserTestUtils/content-task.js\",\"lineNumber\":53,\"columnNumber\":20,\"functionName\":null}],\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html\",\"line\":3,\"column\":5},\"groupId\":null,\"exceptionDocURL\":\"https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_defined?utm_source=mozilla&utm_medium=firefox-console-errors&utm_campaign=default\",\"userProvidedStyles\":null,\"notes\":null}",
+  "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"javascript\",\"timeStamp\":1476573167137,\"type\":\"log\",\"level\":\"error\",\"messageText\":\"ReferenceError: asdf is not defined\",\"parameters\":null,\"repeatId\":null,\"stacktrace\":[{\"filename\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html\",\"lineNumber\":3,\"columnNumber\":5,\"functionName\":\"bar\"},{\"filename\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html\",\"lineNumber\":6,\"columnNumber\":5,\"functionName\":\"foo\"},{\"filename\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html\",\"lineNumber\":9,\"columnNumber\":3,\"functionName\":null},{\"filename\":\"resource://testing-common/content-task.js line 52 > eval\",\"lineNumber\":6,\"columnNumber\":9,\"functionName\":null},{\"filename\":\"resource://testing-common/content-task.js\",\"lineNumber\":53,\"columnNumber\":20,\"functionName\":null}],\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html\",\"line\":3,\"column\":5},\"groupId\":null,\"exceptionDocURL\":\"https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_defined?utm_source=mozilla&utm_medium=firefox-console-errors&utm_campaign=default\",\"userProvidedStyles\":null,\"notes\":null}",
   "stacktrace": [
     {
       "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
       "lineNumber": 3,
       "columnNumber": 5,
       "functionName": "bar"
     },
     {
@@ -39,23 +39,23 @@ stubPreparedMessages.set("ReferenceError
     },
     {
       "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
       "lineNumber": 9,
       "columnNumber": 3,
       "functionName": null
     },
     {
-      "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval",
+      "filename": "resource://testing-common/content-task.js line 52 > eval",
       "lineNumber": 6,
       "columnNumber": 9,
       "functionName": null
     },
     {
-      "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js",
+      "filename": "resource://testing-common/content-task.js",
       "lineNumber": 53,
       "columnNumber": 20,
       "functionName": null
     }
   ],
   "frame": {
     "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
     "line": 3,
@@ -72,26 +72,26 @@ stubPreparedMessages.set("SyntaxError: r
   "allowRepeating": true,
   "source": "javascript",
   "timeStamp": 1487992945524,
   "type": "log",
   "level": "error",
   "messageText": "SyntaxError: redeclaration of let a",
   "parameters": null,
   "repeat": 1,
-  "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"javascript\",\"timeStamp\":1487992945524,\"type\":\"log\",\"level\":\"error\",\"messageText\":\"SyntaxError: redeclaration of let a\",\"parameters\":null,\"repeatId\":null,\"stacktrace\":[{\"filename\":\"chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval\",\"lineNumber\":6,\"columnNumber\":9,\"functionName\":null},{\"filename\":\"chrome://mochikit/content/tests/BrowserTestUtils/content-task.js\",\"lineNumber\":53,\"columnNumber\":20,\"functionName\":null}],\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html\",\"line\":2,\"column\":9},\"groupId\":null,\"userProvidedStyles\":null,\"notes\":[{\"messageBody\":\"Previously declared at line 2, column 6\",\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html\",\"line\":2,\"column\":6}}]}",
+  "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"javascript\",\"timeStamp\":1487992945524,\"type\":\"log\",\"level\":\"error\",\"messageText\":\"SyntaxError: redeclaration of let a\",\"parameters\":null,\"repeatId\":null,\"stacktrace\":[{\"filename\":\"resource://testing-common/content-task.js line 52 > eval\",\"lineNumber\":6,\"columnNumber\":9,\"functionName\":null},{\"filename\":\"resource://testing-common/content-task.js\",\"lineNumber\":53,\"columnNumber\":20,\"functionName\":null}],\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html\",\"line\":2,\"column\":9},\"groupId\":null,\"userProvidedStyles\":null,\"notes\":[{\"messageBody\":\"Previously declared at line 2, column 6\",\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html\",\"line\":2,\"column\":6}}]}",
   "stacktrace": [
     {
-      "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval",
+      "filename": "resource://testing-common/content-task.js line 52 > eval",
       "lineNumber": 6,
       "columnNumber": 9,
       "functionName": null
     },
     {
-      "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js",
+      "filename": "resource://testing-common/content-task.js",
       "lineNumber": 53,
       "columnNumber": 20,
       "functionName": null
     }
   ],
   "frame": {
     "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
     "line": 2,
@@ -145,23 +145,23 @@ stubPackets.set("ReferenceError: asdf is
       },
       {
         "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
         "lineNumber": 9,
         "columnNumber": 3,
         "functionName": null
       },
       {
-        "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval",
+        "filename": "resource://testing-common/content-task.js line 52 > eval",
         "lineNumber": 6,
         "columnNumber": 9,
         "functionName": null
       },
       {
-        "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js",
+        "filename": "resource://testing-common/content-task.js",
         "lineNumber": 53,
         "columnNumber": 20,
         "functionName": null
       }
     ],
     "notes": null
   }
 });
@@ -181,23 +181,23 @@ stubPackets.set("SyntaxError: redeclarat
     "warning": false,
     "error": false,
     "exception": true,
     "strict": false,
     "info": false,
     "private": false,
     "stacktrace": [
       {
-        "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval",
+        "filename": "resource://testing-common/content-task.js line 52 > eval",
         "lineNumber": 6,
         "columnNumber": 9,
         "functionName": null
       },
       {
-        "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js",
+        "filename": "resource://testing-common/content-task.js",
         "lineNumber": 53,
         "columnNumber": 20,
         "functionName": null
       }
     ],
     "notes": [
       {
         "messageBody": "Previously declared at line 2, column 6",
--- a/devtools/server/actors/chrome.js
+++ b/devtools/server/actors/chrome.js
@@ -45,17 +45,28 @@ function ChromeActor(connection) {
   // Defines the default docshell selected for the tab actor
   let window = Services.wm.getMostRecentWindow(DebuggerServer.chromeWindowType);
 
   // Default to any available top level window if there is no expected window
   // (for example when we open firefox with -webide argument)
   if (!window) {
     window = Services.wm.getMostRecentWindow(null);
   }
-  // On xpcshell, there is no window/docshell
+
+  // We really want _some_ window at least, so fallback to the hidden window if
+  // there's nothing else (such as during early startup).
+  if (!window) {
+    try {
+      window = Services.appShell.hiddenDOMWindow;
+    } catch (e) {
+      // On XPCShell, the above line will throw.
+    }
+  }
+
+  // On XPCShell, there is no window/docshell
   let docShell = window ? window.QueryInterface(Ci.nsIInterfaceRequestor)
                                 .getInterface(Ci.nsIDocShell)
                         : null;
   Object.defineProperty(this, "docShell", {
     value: docShell,
     configurable: true
   });
 }
--- a/devtools/server/actors/script.js
+++ b/devtools/server/actors/script.js
@@ -1015,18 +1015,18 @@ const ThreadActor = ActorClassWithSpec(t
         this._options.pauseOnExceptions = aRequest.pauseOnExceptions;
         this._options.ignoreCaughtExceptions = aRequest.ignoreCaughtExceptions;
         this.maybePauseOnExceptions();
         this._maybeListenToEvents(aRequest);
       }
 
       let packet = this._resumed();
       this._popThreadPause();
-      // Tell anyone who cares of the resume (as of now, that's the xpcshell
-      // harness)
+      // Tell anyone who cares of the resume (as of now, that's the xpcshell harness and
+      // devtools-startup.js when handling the --wait-for-jsdebugger flag)
       if (Services.obs) {
         Services.obs.notifyObservers(this, "devtools-thread-resumed", null);
       }
       return packet;
     }, error => {
       return error instanceof Error
         ? { error: "unknownError",
             message: DevToolsUtils.safeErrorString(error) }
--- a/devtools/server/actors/tab.js
+++ b/devtools/server/actors/tab.js
@@ -728,17 +728,17 @@ TabActor.prototype = {
       if (window.parent && window != this._originalWindow) {
         parentID = window.parent
                          .QueryInterface(Ci.nsIInterfaceRequestor)
                          .getInterface(Ci.nsIDOMWindowUtils)
                          .outerWindowID;
       }
 
       // Collect the addonID from the document origin attributes.
-      let addonID = window.document.nodePrincipal.originAttributes.addonId;
+      let addonID = window.document.nodePrincipal.addonId;
 
       return {
         id,
         parentID,
         addonID,
         url: window.location.href,
         title: window.document.title,
       };
--- a/devtools/server/actors/utils/webconsole-listeners.js
+++ b/devtools/server/actors/utils/webconsole-listeners.js
@@ -296,18 +296,17 @@ ConsoleAPIListener.prototype =
     if (this.addonId) {
       // ConsoleAPI.jsm messages contains a consoleID, (and it is currently
       // used in Addon SDK add-ons), the standard 'console' object
       // (which is used in regular webpages and in WebExtensions pages)
       // contains the originAttributes of the source document principal.
 
       // Filtering based on the originAttributes used by
       // the Console API object.
-      if (message.originAttributes &&
-          message.originAttributes.addonId == this.addonId) {
+      if (message.addonId == this.addonId) {
         return true;
       }
 
       // Filtering based on the old-style consoleID property used by
       // the legacy Console JSM module.
       if (message.consoleID && message.consoleID == `addon/${this.addonId}`) {
         return true;
       }
--- a/devtools/server/actors/webextension.js
+++ b/devtools/server/actors/webextension.js
@@ -304,17 +304,17 @@ WebExtensionActor.prototype._allowSource
 /**
  * Return true if the given global is associated with this addon and should be
  * added as a debuggee, false otherwise.
  */
 WebExtensionActor.prototype._shouldAddNewGlobalAsDebuggee = function (newGlobal) {
   const global = unwrapDebuggerObjectGlobal(newGlobal);
 
   if (global instanceof Ci.nsIDOMWindow) {
-    return global.document.nodePrincipal.originAttributes.addonId == this.id;
+    return global.document.nodePrincipal.addonId == this.id;
   }
 
   try {
     // This will fail for non-Sandbox objects, hence the try-catch block.
     let metadata = Cu.getSandboxMetadata(global);
     if (metadata) {
       return metadata.addonID === this.id;
     }
--- a/devtools/shared/css/generated/properties-db.js
+++ b/devtools/shared/css/generated/properties-db.js
@@ -3218,16 +3218,17 @@ exports.CSS_PROPERTIES = {
       "cubic-bezier",
       "currentColor",
       "darken",
       "dashed",
       "default",
       "dialog",
       "difference",
       "disabled",
+      "distribute",
       "dotted",
       "double",
       "drag",
       "dualbutton",
       "ease",
       "ease-in",
       "ease-in-out",
       "ease-out",
@@ -3278,16 +3279,18 @@ exports.CSS_PROPERTIES = {
       "inline-block",
       "inline-end",
       "inline-flex",
       "inline-grid",
       "inline-start",
       "inline-table",
       "inset",
       "inside",
+      "inter-character",
+      "inter-word",
       "intersect",
       "isolate",
       "italic",
       "justify",
       "keep-all",
       "large",
       "larger",
       "last baseline",
@@ -8807,16 +8810,33 @@ exports.CSS_PROPERTIES = {
     ],
     "values": [
       "calc",
       "inherit",
       "initial",
       "unset"
     ]
   },
+  "text-justify": {
+    "isInherited": true,
+    "subproperties": [
+      "text-justify"
+    ],
+    "supports": [],
+    "values": [
+      "auto",
+      "distribute",
+      "inherit",
+      "initial",
+      "inter-character",
+      "inter-word",
+      "none",
+      "unset"
+    ]
+  },
   "text-orientation": {
     "isInherited": true,
     "subproperties": [
       "text-orientation"
     ],
     "supports": [],
     "values": [
       "inherit",
--- a/devtools/shared/tests/unit/test_console_filtering.js
+++ b/devtools/shared/tests/unit/test_console_filtering.js
@@ -2,58 +2,67 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const { console, ConsoleAPI } = require("resource://gre/modules/Console.jsm");
 const { ConsoleAPIListener } = require("devtools/server/actors/utils/webconsole-listeners");
 const Services = require("Services");
 
+// FIXME: This test shouldn't need to rely on low-level internals.
+const {Service} = Cu.import("resource://gre/modules/ExtensionManagement.jsm", {});
+
 var seenMessages = 0;
 var seenTypes = 0;
 
 var callback = {
   onConsoleAPICall: function (message) {
     if (message.consoleID && message.consoleID == "addon/foo") {
       do_check_eq(message.level, "warn");
       do_check_eq(message.arguments[0], "Warning from foo");
       seenTypes |= 1;
-    } else if (message.originAttributes &&
-              message.originAttributes.addonId == "bar") {
+    } else if (message.addonId == "bar") {
       do_check_eq(message.level, "error");
       do_check_eq(message.arguments[0], "Error from bar");
       seenTypes |= 2;
     } else {
       do_check_eq(message.level, "log");
       do_check_eq(message.arguments[0], "Hello from default console");
       seenTypes |= 4;
     }
     seenMessages++;
   }
 };
 
 function createFakeAddonWindow({addonId} = {}) {
-  let baseURI = Services.io.newURI("about:blank");
-  let originAttributes = {addonId};
+  const uuidGen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
+  const uuid = uuidGen.generateUUID().number.slice(1, -1);
+
+  const url = `moz-extension://${uuid}/`;
+  Service.uuidMap.set(uuid, {id: addonId});
+
+  let baseURI = Services.io.newURI(url);
   let principal = Services.scriptSecurityManager
-        .createCodebasePrincipal(baseURI, originAttributes);
+        .createCodebasePrincipal(baseURI, {});
   let chromeWebNav = Services.appShell.createWindowlessBrowser(true);
   let docShell = chromeWebNav.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIDocShell);
   docShell.createAboutBlankContentViewer(principal);
   let addonWindow = docShell.contentViewer.DOMDocument.defaultView;
 
   return {addonWindow, chromeWebNav};
 }
 
 /**
  * Tests that the consoleID property of the ConsoleAPI options gets passed
  * through to console messages.
  */
 function run_test() {
+  Service.init();
+
   // console1 Test Console.jsm messages tagged by the Addon SDK
   // are still filtered correctly.
   let console1 = new ConsoleAPI({
     consoleID: "addon/foo"
   });
 
   // console2 - WebExtension page's console messages tagged
   // by 'originAttributes.addonId' are filtered correctly.
--- a/docshell/base/nsDSURIContentListener.cpp
+++ b/docshell/base/nsDSURIContentListener.cpp
@@ -423,18 +423,18 @@ nsDSURIContentListener::CheckFrameOption
     }
   }
 
   if (!httpChannel) {
     return true;
   }
 
   nsAutoCString xfoHeaderCValue;
-  httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("X-Frame-Options"),
-                                 xfoHeaderCValue);
+  Unused << httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("X-Frame-Options"),
+                                           xfoHeaderCValue);
   NS_ConvertUTF8toUTF16 xfoHeaderValue(xfoHeaderCValue);
 
   // if no header value, there's nothing to do.
   if (xfoHeaderValue.IsEmpty()) {
     return true;
   }
 
   // iterate through all the header values (usually there's only one, but can
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -9,17 +9,16 @@
 #include <algorithm>
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/Casting.h"
 #include "mozilla/dom/ContentChild.h"
-#include "mozilla/dom/ChromeUtils.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/PendingGlobalHistoryEntry.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/ProfileTimelineMarkerBinding.h"
 #include "mozilla/dom/ScreenOrientation.h"
 #include "mozilla/dom/ToJSValue.h"
 #include "mozilla/dom/PermissionMessageUtils.h"
 #include "mozilla/dom/workers/ServiceWorkerManager.h"
@@ -537,67 +536,76 @@ SendPing(void* aClosure, nsIContent* aCo
 
   nsCOMPtr<nsIHttpChannel> httpChan = do_QueryInterface(chan);
   if (!httpChan) {
     return;
   }
 
   // This is needed in order for 3rd-party cookie blocking to work.
   nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(httpChan);
+  nsresult rv;
   if (httpInternal) {
-    httpInternal->SetDocumentURI(doc->GetDocumentURI());
-  }
-
-  httpChan->SetRequestMethod(NS_LITERAL_CSTRING("POST"));
+    rv = httpInternal->SetDocumentURI(doc->GetDocumentURI());
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+  }
+
+  rv = httpChan->SetRequestMethod(NS_LITERAL_CSTRING("POST"));
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
 
   // Remove extraneous request headers (to reduce request size)
-  httpChan->SetRequestHeader(NS_LITERAL_CSTRING("accept"),
-                             EmptyCString(), false);
-  httpChan->SetRequestHeader(NS_LITERAL_CSTRING("accept-language"),
-                             EmptyCString(), false);
-  httpChan->SetRequestHeader(NS_LITERAL_CSTRING("accept-encoding"),
-                             EmptyCString(), false);
+  rv = httpChan->SetRequestHeader(NS_LITERAL_CSTRING("accept"),
+                                  EmptyCString(), false);
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+  rv = httpChan->SetRequestHeader(NS_LITERAL_CSTRING("accept-language"),
+                                  EmptyCString(), false);
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+  rv = httpChan->SetRequestHeader(NS_LITERAL_CSTRING("accept-encoding"),
+                                  EmptyCString(), false);
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
 
   // Always send a Ping-To header.
   nsAutoCString pingTo;
   if (NS_SUCCEEDED(info->target->GetSpec(pingTo))) {
-    httpChan->SetRequestHeader(NS_LITERAL_CSTRING("Ping-To"), pingTo, false);
+    rv = httpChan->SetRequestHeader(NS_LITERAL_CSTRING("Ping-To"), pingTo, false);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
   }
 
   nsCOMPtr<nsIScriptSecurityManager> sm =
     do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
 
   if (sm && info->referrer) {
     bool referrerIsSecure;
     uint32_t flags = nsIProtocolHandler::URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT;
-    nsresult rv = NS_URIChainHasFlags(info->referrer, flags, &referrerIsSecure);
+    rv = NS_URIChainHasFlags(info->referrer, flags, &referrerIsSecure);
 
     // Default to sending less data if NS_URIChainHasFlags() fails.
     referrerIsSecure = NS_FAILED(rv) || referrerIsSecure;
 
     bool sameOrigin =
       NS_SUCCEEDED(sm->CheckSameOriginURI(info->referrer, aURI, false));
 
     // If both the address of the document containing the hyperlink being
     // audited and "ping URL" have the same origin or the document containing
     // the hyperlink being audited was not retrieved over an encrypted
     // connection, send a Ping-From header.
     if (sameOrigin || !referrerIsSecure) {
       nsAutoCString pingFrom;
       if (NS_SUCCEEDED(info->referrer->GetSpec(pingFrom))) {
-        httpChan->SetRequestHeader(NS_LITERAL_CSTRING("Ping-From"), pingFrom,
-                                   false);
+        rv = httpChan->SetRequestHeader(NS_LITERAL_CSTRING("Ping-From"),
+                                        pingFrom, false);
+        MOZ_ASSERT(NS_SUCCEEDED(rv));
       }
     }
 
     // If the document containing the hyperlink being audited was not retrieved
     // over an encrypted connection and its address does not have the same
     // origin as "ping URL", send a referrer.
     if (!sameOrigin && !referrerIsSecure) {
-      httpChan->SetReferrerWithPolicy(info->referrer, info->referrerPolicy);
+      rv = httpChan->SetReferrerWithPolicy(info->referrer, info->referrerPolicy);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
     }
   }
 
   nsCOMPtr<nsIUploadChannel2> uploadChan = do_QueryInterface(httpChan);
   if (!uploadChan) {
     return;
   }
 
@@ -7501,17 +7509,17 @@ nsDocShell::OnRedirectStateChange(nsICha
     nsCOMPtr<nsIURI> referrer;
     // Treat referrer as null if there is an error getting it.
     (void)NS_GetReferrerFromChannel(aOldChannel, getter_AddRefs(referrer));
 
     // Get the HTTP response code, if available.
     uint32_t responseStatus = 0;
     nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aOldChannel);
     if (httpChannel) {
-      (void)httpChannel->GetResponseStatus(&responseStatus);
+      Unused << httpChannel->GetResponseStatus(&responseStatus);
     }
 
     // Add visit N -1 => N
     AddURIVisit(oldURI, referrer, previousURI, previousFlags, responseStatus);
 
     // Since N + 1 could be the final destination, we will not save N => N + 1
     // here.  OnNewURI will do that, so we will cache it.
     SaveLastVisit(aNewChannel, oldURI, aRedirectFlags);
@@ -8023,19 +8031,17 @@ nsDocShell::CreateAboutBlankContentViewe
   // mContentViewer->PermitUnload may release |this| docshell.
   nsCOMPtr<nsIDocShell> kungFuDeathGrip(this);
 
   AutoRestore<bool> creatingDocument(mCreatingDocument);
   mCreatingDocument = true;
 
   if (aPrincipal && !nsContentUtils::IsSystemPrincipal(aPrincipal) &&
       mItemType != typeChrome) {
-    MOZ_ASSERT(ChromeUtils::IsOriginAttributesEqualIgnoringAddonId(
-      aPrincipal->OriginAttributesRef(),
-      mOriginAttributes));
+    MOZ_ASSERT(aPrincipal->OriginAttributesRef() == mOriginAttributes);
   }
 
   // Make sure timing is created.  But first record whether we had it
   // already, so we don't clobber the timing for an in-progress load.
   bool hadTiming = mTiming;
   bool toBeReset = MaybeInitTiming();
   if (mContentViewer) {
     if (aCheckPermitUnload) {
@@ -11145,26 +11151,30 @@ nsDocShell::DoURILoad(nsIURI* aURI,
   }
 
   // hack
   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
   nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal(
     do_QueryInterface(channel));
   if (httpChannelInternal) {
     if (aForceAllowCookies) {
-      httpChannelInternal->SetThirdPartyFlags(
+      rv = httpChannelInternal->SetThirdPartyFlags(
         nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
     }
     if (aFirstParty) {
-      httpChannelInternal->SetDocumentURI(aURI);
+      rv = httpChannelInternal->SetDocumentURI(aURI);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
     } else {
-      httpChannelInternal->SetDocumentURI(aReferrerURI);
-    }
-    httpChannelInternal->SetRedirectMode(
+      rv = httpChannelInternal->SetDocumentURI(aReferrerURI);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
+    }
+    rv = httpChannelInternal->SetRedirectMode(
       nsIHttpChannelInternal::REDIRECT_MODE_MANUAL);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
   }
 
   nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(channel));
   if (props) {
     // save true referrer for those who need it (e.g. xpinstall whitelisting)
     // Currently only http and ftp channels support this.
     props->SetPropertyAsInterface(NS_LITERAL_STRING("docshell.internalReferrer"),
                                   aReferrerURI);
@@ -11235,17 +11245,18 @@ nsDocShell::DoURILoad(nsIURI* aURI,
 
   if (httpChannel) {
     if (aHeadersData) {
       rv = AddHeadersToChannel(aHeadersData, httpChannel);
     }
     // Set the referrer explicitly
     if (aReferrerURI && aSendReferrer) {
       // Referrer is currenly only set for link clicks here.
-      httpChannel->SetReferrerWithPolicy(aReferrerURI, aReferrerPolicy);
+      rv = httpChannel->SetReferrerWithPolicy(aReferrerURI, aReferrerPolicy);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
     }
   }
 
   nsCOMPtr<nsIScriptChannel> scriptChannel = do_QueryInterface(channel);
   if (scriptChannel) {
     // Allow execution against our context if the principals match
     scriptChannel->SetExecutionPolicy(nsIScriptChannel::EXECUTE_NORMAL);
   }
@@ -12327,18 +12338,20 @@ nsDocShell::AddToSessionHistory(nsIURI* 
       nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel));
       if (uploadChannel) {
         uploadChannel->GetUploadStream(getter_AddRefs(inputStream));
       }
       httpChannel->GetOriginalURI(getter_AddRefs(originalURI));
       uint32_t loadFlags;
       aChannel->GetLoadFlags(&loadFlags);
       loadReplace = loadFlags & nsIChannel::LOAD_REPLACE;
-      httpChannel->GetReferrer(getter_AddRefs(referrerURI));
-      httpChannel->GetReferrerPolicy(&referrerPolicy);
+      rv = httpChannel->GetReferrer(getter_AddRefs(referrerURI));
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
+      rv = httpChannel->GetReferrerPolicy(&referrerPolicy);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
 
       discardLayoutState = ShouldDiscardLayoutState(httpChannel);
     }
 
     // XXX Bug 1286838: Replace channel owner with loadInfo triggeringPrincipal
     nsCOMPtr<nsISupports> owner;
     aChannel->GetOwner(getter_AddRefs(owner));
     triggeringPrincipal = do_QueryInterface(owner);
@@ -12354,17 +12367,16 @@ nsDocShell::AddToSessionHistory(nsIURI* 
         if (loadInfo->GetLoadingSandboxed()) {
           if (loadInfo->LoadingPrincipal()) {
             principalToInherit = nsNullPrincipal::CreateWithInheritedAttributes(
             loadInfo->LoadingPrincipal());
           } else {
             // get the OriginAttributes
             OriginAttributes attrs;
             loadInfo->GetOriginAttributes(&attrs);
-            attrs.StripAttributes(OriginAttributes::STRIP_ADDON_ID);
             principalToInherit = nsNullPrincipal::Create(attrs);
           }
         } else {
           principalToInherit = loadInfo->PrincipalToInherit();
         }
       }
     }
   }
@@ -12971,17 +12983,17 @@ nsDocShell::ShouldDiscardLayoutState(nsI
 {
   // By default layout State will be saved.
   if (!aChannel) {
     return false;
   }
 
   // figure out if SH should be saving layout state
   bool noStore = false;
-  aChannel->IsNoStoreResponse(&noStore);
+  Unused << aChannel->IsNoStoreResponse(&noStore);
   return noStore;
 }
 
 NS_IMETHODIMP
 nsDocShell::GetEditor(nsIEditor** aEditor)
 {
   NS_ENSURE_ARG_POINTER(aEditor);
 
@@ -13043,17 +13055,17 @@ bool
 nsDocShell::ChannelIsPost(nsIChannel* aChannel)
 {
   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));
   if (!httpChannel) {
     return false;
   }
 
   nsAutoCString method;
-  httpChannel->GetRequestMethod(method);
+  Unused << httpChannel->GetRequestMethod(method);
   return method.EqualsLiteral("POST");
 }
 
 void
 nsDocShell::ExtractLastVisit(nsIChannel* aChannel,
                              nsIURI** aURI,
                              uint32_t* aChannelRedirectFlags)
 {
--- a/dom/asmjscache/AsmJSCache.cpp
+++ b/dom/asmjscache/AsmJSCache.cpp
@@ -26,17 +26,16 @@
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/BackgroundUtils.h"
 #include "mozilla/ipc/PBackgroundChild.h"
 #include "mozilla/Unused.h"
 #include "nsAutoPtr.h"
 #include "nsIAtom.h"
 #include "nsIFile.h"
 #include "nsIIPCBackgroundChildCreateCallback.h"
-#include "nsIPermissionManager.h"
 #include "nsIPrincipal.h"
 #include "nsIRunnable.h"
 #include "nsISimpleEnumerator.h"
 #include "nsIThread.h"
 #include "nsJSPrincipals.h"
 #include "nsThreadUtils.h"
 #include "nsXULAppAPI.h"
 #include "prio.h"
@@ -345,21 +344,19 @@ public:
 
   ParentRunnable(const PrincipalInfo& aPrincipalInfo,
                  OpenMode aOpenMode,
                  WriteParams aWriteParams)
   : mOwningThread(NS_GetCurrentThread()),
     mPrincipalInfo(aPrincipalInfo),
     mOpenMode(aOpenMode),
     mWriteParams(aWriteParams),
-    mPersistence(quota::PERSISTENCE_TYPE_INVALID),
     mState(eInitial),
     mResult(JS::AsmJSCache_InternalError),
-    mIsApp(false),
-    mEnforcingQuota(true),
+    mDeleteReceived(false),
     mActorDestroyed(false),
     mOpened(false)
   {
     MOZ_ASSERT(XRE_IsParentProcess());
     AssertIsOnOwningThread();
   }
 
 private:
@@ -388,41 +385,16 @@ private:
 
   void
   AssertIsOnNonOwningThread() const
   {
     MOZ_ASSERT(!IsOnBackgroundThread());
     MOZ_ASSERT(!IsOnOwningThread());
   }
 
-  // This method is called on the owning thread when no cache entry was found
-  // to open. If we just tried a lookup in persistent storage then we might
-  // still get a hit in temporary storage (for an asm.js module that wasn't
-  // compiled at install-time).
-  void
-  CacheMiss()
-  {
-    AssertIsOnOwningThread();
-    MOZ_ASSERT(mState == eFailedToReadMetadata ||
-               mState == eWaitingToOpenCacheFileForRead);
-    MOZ_ASSERT(mOpenMode == eOpenForRead);
-
-    if (mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY) {
-      Fail();
-      return;
-    }
-
-    // Try again with a clean slate. InitOnMainThread will see that mPersistence
-    // is initialized and switch to temporary storage.
-    MOZ_ASSERT(mPersistence == quota::PERSISTENCE_TYPE_PERSISTENT);
-    FinishOnOwningThread();
-    mState = eInitial;
-    NS_DispatchToMainThread(this);
-  }
-
   // This method is called on the owning thread when the JS engine is finished
   // reading/writing the cache entry.
   void
   Close()
   {
     AssertIsOnOwningThread();
     MOZ_ASSERT(mState == eOpened);
 
@@ -442,17 +414,17 @@ private:
     MOZ_ASSERT(mState != eFinished);
 
     mState = eFinished;
 
     MOZ_ASSERT(!mOpened);
 
     FinishOnOwningThread();
 
-    if (!mActorDestroyed) {
+    if (!mDeleteReceived && !mActorDestroyed) {
       Unused << Send__delete__(this, mResult);
     }
   }
 
   // The same as method above but is intended to be called off the owning
   // thread.
   void
   FailOnNonOwningThread()
@@ -461,19 +433,16 @@ private:
     MOZ_ASSERT(mState != eOpened &&
                mState != eFailing &&
                mState != eFinished);
 
     mState = eFailing;
     MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
   }
 
-  void
-  InitPersistenceType();
-
   nsresult
   InitOnMainThread();
 
   void
   OpenDirectory();
 
   nsresult
   ReadMetadata();
@@ -514,16 +483,19 @@ private:
   DirectoryLockFailed() override;
 
   // IPDL methods.
   mozilla::ipc::IPCResult
   Recv__delete__(const JS::AsmJSCacheResult& aResult) override
   {
     AssertIsOnOwningThread();
     MOZ_ASSERT(mState != eFinished);
+    MOZ_ASSERT(!mDeleteReceived);
+
+    mDeleteReceived = true;
 
     if (mOpened) {
       Close();
     } else {
       Fail();
     }
 
     MOZ_ASSERT(mState == eFinished);
@@ -566,33 +538,22 @@ private:
 
     mModuleIndex = aModuleIndex;
     mState = eReadyToOpenCacheFileForRead;
     DispatchToIOThread();
 
     return IPC_OK();
   }
 
-  mozilla::ipc::IPCResult
-  RecvCacheMiss() override
-  {
-    AssertIsOnOwningThread();
-
-    CacheMiss();
-
-    return IPC_OK();
-  }
-
   nsCOMPtr<nsIEventTarget> mOwningThread;
   const PrincipalInfo mPrincipalInfo;
   const OpenMode mOpenMode;
   const WriteParams mWriteParams;
 
   // State initialized during eInitial:
-  quota::PersistenceType mPersistence;
   nsCString mSuffix;
   nsCString mGroup;
   nsCString mOrigin;
   RefPtr<DirectoryLock> mDirectoryLock;
 
   // State initialized during eReadyToReadMetadata
   nsCOMPtr<nsIFile> mDirectory;
   nsCOMPtr<nsIFile> mMetadataFile;
@@ -602,138 +563,84 @@ private:
   unsigned mModuleIndex;
 
   enum State {
     eInitial, // Just created, waiting to be dispatched to main thread
     eWaitingToFinishInit, // Waiting to finish initialization
     eWaitingToOpenDirectory, // Waiting to open directory
     eWaitingToOpenMetadata, // Waiting to be called back from OpenDirectory
     eReadyToReadMetadata, // Waiting to read the metadata file on the IO thread
-    eFailedToReadMetadata, // Waiting to be dispatched to owning thread after fail
     eSendingMetadataForRead, // Waiting to send OnOpenMetadataForRead
     eWaitingToOpenCacheFileForRead, // Waiting to hear back from child
     eReadyToOpenCacheFileForRead, // Waiting to open cache file for read
     eSendingCacheFile, // Waiting to send OnOpenCacheFile on the owning thread
     eOpened, // Finished calling OnOpenCacheFile, waiting to be closed
     eFailing, // Just failed, waiting to be dispatched to the owning thread
     eFinished, // Terminal state
   };
   State mState;
   JS::AsmJSCacheResult mResult;
 
-  bool mIsApp;
-  bool mEnforcingQuota;
+  bool mDeleteReceived;
   bool mActorDestroyed;
   bool mOpened;
 };
 
-void
-ParentRunnable::InitPersistenceType()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mState == eInitial);
-
-  if (mOpenMode == eOpenForWrite) {
-    MOZ_ASSERT(mPersistence == quota::PERSISTENCE_TYPE_INVALID);
-
-    // If we are performing install-time caching of an app, we'd like to store
-    // the cache entry in persistent storage so the entry is never evicted,
-    // but we need to check that quota is not enforced for the app.
-    // That justifies us in skipping all quota checks when storing the cache
-    // entry and avoids all the issues around the persistent quota prompt.
-    // If quota is enforced for the app, then we can still cache in temporary
-    // for a likely good first-run experience.
-
-    MOZ_ASSERT_IF(mWriteParams.mInstalled, mIsApp);
-
-    if (mWriteParams.mInstalled &&
-        !QuotaManager::IsQuotaEnforced(quota::PERSISTENCE_TYPE_PERSISTENT,
-                                       mOrigin, mIsApp)) {
-      mPersistence = quota::PERSISTENCE_TYPE_PERSISTENT;
-    } else {
-      mPersistence = quota::PERSISTENCE_TYPE_TEMPORARY;
-    }
-
-    return;
-  }
-
-  // For the reasons described above, apps may have cache entries in both
-  // persistent and temporary storage. At lookup time we don't know how and
-  // where the given script was cached, so start the search in persistent
-  // storage and, if that fails, search in temporary storage. (Non-apps can
-  // only be stored in temporary storage.)
-
-  MOZ_ASSERT_IF(mPersistence != quota::PERSISTENCE_TYPE_INVALID,
-                mIsApp && mPersistence == quota::PERSISTENCE_TYPE_PERSISTENT);
-
-  if (mPersistence == quota::PERSISTENCE_TYPE_INVALID && mIsApp) {
-    mPersistence = quota::PERSISTENCE_TYPE_PERSISTENT;
-  } else {
-    mPersistence = quota::PERSISTENCE_TYPE_TEMPORARY;
-  }
-}
-
 nsresult
 ParentRunnable::InitOnMainThread()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mState == eInitial);
   MOZ_ASSERT(mPrincipalInfo.type() != PrincipalInfo::TNullPrincipalInfo);
 
   nsresult rv;
   nsCOMPtr<nsIPrincipal> principal =
     PrincipalInfoToPrincipal(mPrincipalInfo, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = QuotaManager::GetInfoFromPrincipal(principal, &mSuffix, &mGroup,
-                                          &mOrigin, &mIsApp);
+                                          &mOrigin);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  InitPersistenceType();
-
-  mEnforcingQuota =
-    QuotaManager::IsQuotaEnforced(mPersistence, mOrigin, mIsApp);
-
   return NS_OK;
 }
 
 void
 ParentRunnable::OpenDirectory()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == eWaitingToFinishInit ||
              mState == eWaitingToOpenDirectory);
   MOZ_ASSERT(QuotaManager::Get());
 
   mState = eWaitingToOpenMetadata;
 
   // XXX The exclusive lock shouldn't be needed for read operations.
-  QuotaManager::Get()->OpenDirectory(mPersistence,
+  QuotaManager::Get()->OpenDirectory(quota::PERSISTENCE_TYPE_TEMPORARY,
                                      mGroup,
                                      mOrigin,
-                                     mIsApp,
                                      quota::Client::ASMJS,
                                      /* aExclusive */ true,
                                      this);
 }
 
 nsresult
 ParentRunnable::ReadMetadata()
 {
   AssertIsOnIOThread();
   MOZ_ASSERT(mState == eReadyToReadMetadata);
 
   QuotaManager* qm = QuotaManager::Get();
   MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
 
   nsresult rv =
-    qm->EnsureOriginIsInitialized(mPersistence, mSuffix, mGroup, mOrigin,
-                                  mIsApp, getter_AddRefs(mDirectory));
+    qm->EnsureOriginIsInitialized(quota::PERSISTENCE_TYPE_TEMPORARY, mSuffix,
+                                  mGroup, mOrigin, getter_AddRefs(mDirectory));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     mResult = JS::AsmJSCache_StorageInitFailure;
     return rv;
   }
 
   rv = mDirectory->Append(NS_LITERAL_STRING(ASMJSCACHE_DIRECTORY_NAME));
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -794,35 +701,34 @@ ParentRunnable::OpenCacheFileForWrite()
 
   nsCOMPtr<nsIFile> file;
   nsresult rv = GetCacheFile(mDirectory, mModuleIndex, getter_AddRefs(file));
   NS_ENSURE_SUCCESS(rv, rv);
 
   QuotaManager* qm = QuotaManager::Get();
   MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
 
-  if (mEnforcingQuota) {
-    // Create the QuotaObject before all file IO and keep it alive until caching
-    // completes to get maximum assertion coverage in QuotaManager against
-    // concurrent removal, etc.
-    mQuotaObject = qm->GetQuotaObject(mPersistence, mGroup, mOrigin, file);
-    NS_ENSURE_STATE(mQuotaObject);
+  // Create the QuotaObject before all file IO and keep it alive until caching
+  // completes to get maximum assertion coverage in QuotaManager against
+  // concurrent removal, etc.
+  mQuotaObject = qm->GetQuotaObject(quota::PERSISTENCE_TYPE_TEMPORARY, mGroup,
+                                    mOrigin, file);
+  NS_ENSURE_STATE(mQuotaObject);
 
+  if (!mQuotaObject->MaybeUpdateSize(mWriteParams.mSize,
+                                     /* aTruncate */ false)) {
+    // If the request fails, it might be because mOrigin is using too much
+    // space (MaybeUpdateSize will not evict our own origin since it is
+    // active). Try to make some space by evicting LRU entries until there is
+    // enough space.
+    EvictEntries(mDirectory, mGroup, mOrigin, mWriteParams.mSize, mMetadata);
     if (!mQuotaObject->MaybeUpdateSize(mWriteParams.mSize,
                                        /* aTruncate */ false)) {
-      // If the request fails, it might be because mOrigin is using too much
-      // space (MaybeUpdateSize will not evict our own origin since it is
-      // active). Try to make some space by evicting LRU entries until there is
-      // enough space.
-      EvictEntries(mDirectory, mGroup, mOrigin, mWriteParams.mSize, mMetadata);
-      if (!mQuotaObject->MaybeUpdateSize(mWriteParams.mSize,
-                                         /* aTruncate */ false)) {
-        mResult = JS::AsmJSCache_QuotaExceeded;
-        return NS_ERROR_FAILURE;
-      }
+      mResult = JS::AsmJSCache_QuotaExceeded;
+      return NS_ERROR_FAILURE;
     }
   }
 
   int32_t openFlags = PR_RDWR | PR_TRUNCATE | PR_CREATE_FILE;
   rv = file->OpenNSPRFileDesc(openFlags, 0644, &mFileDesc);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Move the mModuleIndex's LRU entry to the recent end of the queue.
@@ -848,23 +754,22 @@ ParentRunnable::OpenCacheFileForRead()
 
   nsCOMPtr<nsIFile> file;
   nsresult rv = GetCacheFile(mDirectory, mModuleIndex, getter_AddRefs(file));
   NS_ENSURE_SUCCESS(rv, rv);
 
   QuotaManager* qm = QuotaManager::Get();
   MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
 
-  if (mEnforcingQuota) {
-    // Even though it's not strictly necessary, create the QuotaObject before
-    // all file IO and keep it alive until caching completes to get maximum
-    // assertion coverage in QuotaManager against concurrent removal, etc.
-    mQuotaObject = qm->GetQuotaObject(mPersistence, mGroup, mOrigin, file);
-    NS_ENSURE_STATE(mQuotaObject);
-  }
+  // Even though it's not strictly necessary, create the QuotaObject before all
+  // file IO and keep it alive until caching completes to get maximum assertion
+  // coverage in QuotaManager against concurrent removal, etc.
+  mQuotaObject = qm->GetQuotaObject(quota::PERSISTENCE_TYPE_TEMPORARY, mGroup,
+                                    mOrigin, file);
+  NS_ENSURE_STATE(mQuotaObject);
 
   rv = file->GetFileSize(&mFileSize);
   NS_ENSURE_SUCCESS(rv, rv);
 
   int32_t openFlags = PR_RDONLY | nsIFile::OS_READAHEAD;
   rv = file->OpenNSPRFileDesc(openFlags, 0644, &mFileDesc);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -951,18 +856,17 @@ ParentRunnable::Run()
       return NS_OK;
     }
 
     case eReadyToReadMetadata: {
       AssertIsOnIOThread();
 
       rv = ReadMetadata();
       if (NS_FAILED(rv)) {
-        mState = eFailedToReadMetadata;
-        MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
+        FailOnNonOwningThread();
         return NS_OK;
       }
 
       if (mOpenMode == eOpenForRead) {
         mState = eSendingMetadataForRead;
         MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
 
         return NS_OK;
@@ -974,28 +878,16 @@ ParentRunnable::Run()
         return NS_OK;
       }
 
       mState = eSendingCacheFile;
       MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
       return NS_OK;
     }
 
-    case eFailedToReadMetadata: {
-      AssertIsOnOwningThread();
-
-      if (mOpenMode == eOpenForRead) {
-        CacheMiss();
-        return NS_OK;
-      }
-
-      Fail();
-      return NS_OK;
-    }
-
     case eSendingMetadataForRead: {
       AssertIsOnOwningThread();
       MOZ_ASSERT(mOpenMode == eOpenForRead);
 
       mState = eWaitingToOpenCacheFileForRead;
 
       // Metadata is now open.
       if (!SendOnOpenMetadataForRead(mMetadata)) {
@@ -1284,24 +1176,23 @@ private:
   // IPDL methods.
   mozilla::ipc::IPCResult
   RecvOnOpenMetadataForRead(const Metadata& aMetadata) override
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(mState == eOpening);
 
     uint32_t moduleIndex;
-    if (FindHashMatch(aMetadata, mReadParams, &moduleIndex)) {
-      if (!SendSelectCacheFileToRead(moduleIndex)) {
-        return IPC_FAIL_NO_REASON(this);
-      }
+    if (!FindHashMatch(aMetadata, mReadParams, &moduleIndex)) {
+      Fail(JS::AsmJSCache_InternalError);
+      Send__delete__(this, JS::AsmJSCache_InternalError);
       return IPC_OK();
     }
 
-    if (!SendCacheMiss()) {
+    if (!SendSelectCacheFileToRead(moduleIndex)) {
       return IPC_FAIL_NO_REASON(this);
     }
     return IPC_OK();
   }
 
   mozilla::ipc::IPCResult
   RecvOnOpenCacheFile(const int64_t& aFileSize,
                       const FileDescriptor& aFileDesc) override
@@ -1646,34 +1537,32 @@ CloseEntryForRead(size_t aSize,
 
   MOZ_ASSERT(aSize + sizeof(AsmJSCookieType) == childRunnable->FileSize());
   MOZ_ASSERT(aMemory - sizeof(AsmJSCookieType) ==
                childRunnable->MappedMemory());
 }
 
 JS::AsmJSCacheResult
 OpenEntryForWrite(nsIPrincipal* aPrincipal,
-                  bool aInstalled,
                   const char16_t* aBegin,
                   const char16_t* aEnd,
                   size_t aSize,
                   uint8_t** aMemory,
                   intptr_t* aHandle)
 {
   if (size_t(aEnd - aBegin) < sMinCachedModuleLength) {
     return JS::AsmJSCache_ModuleTooSmall;
   }
 
   // Add extra space for the AsmJSCookieType (see OpenEntryForRead).
   aSize += sizeof(AsmJSCookieType);
 
   static_assert(sNumFastHashChars < sMinCachedModuleLength, "HashString safe");
 
   WriteParams writeParams;
-  writeParams.mInstalled = aInstalled;
   writeParams.mSize = aSize;
   writeParams.mFastHash = HashString(aBegin, sNumFastHashChars);
   writeParams.mNumChars = aEnd - aBegin;
   writeParams.mFullHash = HashString(aBegin, writeParams.mNumChars);
 
   ChildRunnable::AutoClose childRunnable;
   ReadParams notARead;
   JS::AsmJSCacheResult openResult =
@@ -1885,33 +1774,30 @@ ParamTraits<Metadata>::Log(const paramTy
 
 void
 ParamTraits<WriteParams>::Write(Message* aMsg, const paramType& aParam)
 {
   WriteParam(aMsg, aParam.mSize);
   WriteParam(aMsg, aParam.mFastHash);
   WriteParam(aMsg, aParam.mNumChars);
   WriteParam(aMsg, aParam.mFullHash);
-  WriteParam(aMsg, aParam.mInstalled);
 }
 
 bool
 ParamTraits<WriteParams>::Read(const Message* aMsg, PickleIterator* aIter,
                                paramType* aResult)
 {
   return ReadParam(aMsg, aIter, &aResult->mSize) &&
          ReadParam(aMsg, aIter, &aResult->mFastHash) &&
          ReadParam(aMsg, aIter, &aResult->mNumChars) &&
-         ReadParam(aMsg, aIter, &aResult->mFullHash) &&
-         ReadParam(aMsg, aIter, &aResult->mInstalled);
+         ReadParam(aMsg, aIter, &aResult->mFullHash);
 }
 
 void
 ParamTraits<WriteParams>::Log(const paramType& aParam, std::wstring* aLog)
 {
   LogParam(aParam.mSize, aLog);
   LogParam(aParam.mFastHash, aLog);
   LogParam(aParam.mNumChars, aLog);
   LogParam(aParam.mFullHash, aLog);
-  LogParam(aParam.mInstalled, aLog);
 }
 
 } // namespace IPC
--- a/dom/asmjscache/AsmJSCache.h
+++ b/dom/asmjscache/AsmJSCache.h
@@ -68,24 +68,22 @@ struct Metadata
 
 // Parameters specific to opening a cache entry for writing
 struct WriteParams
 {
   int64_t mSize;
   int64_t mFastHash;
   int64_t mNumChars;
   int64_t mFullHash;
-  bool mInstalled;
 
   WriteParams()
   : mSize(0),
     mFastHash(0),
     mNumChars(0),
-    mFullHash(0),
-    mInstalled(false)
+    mFullHash(0)
   { }
 };
 
 // Parameters specific to opening a cache entry for reading
 struct ReadParams
 {
   const char16_t* mBegin;
   const char16_t* mLimit;
@@ -116,17 +114,16 @@ OpenEntryForRead(nsIPrincipal* aPrincipa
                  const uint8_t** aMemory,
                  intptr_t *aHandle);
 void
 CloseEntryForRead(size_t aSize,
                   const uint8_t* aMemory,
                   intptr_t aHandle);
 JS::AsmJSCacheResult
 OpenEntryForWrite(nsIPrincipal* aPrincipal,
-                  bool aInstalled,
                   const char16_t* aBegin,
                   const char16_t* aEnd,
                   size_t aSize,
                   uint8_t** aMemory,
                   intptr_t* aHandle);
 void
 CloseEntryForWrite(size_t aSize,
                    uint8_t* aMemory,
--- a/dom/asmjscache/PAsmJSCacheEntry.ipdl
+++ b/dom/asmjscache/PAsmJSCacheEntry.ipdl
@@ -17,17 +17,16 @@ protocol PAsmJSCacheEntry
 
   // When the cache is opened to read, the parent process sends over the
   // origin's Metadata so the child process can select the cache entry to open
   // (based on hash) and notify the parent (via SelectCacheFileToRead).
 child:
   async OnOpenMetadataForRead(Metadata metadata);
 parent:
   async SelectCacheFileToRead(uint32_t moduleIndex);
-  async CacheMiss();
 
 child:
   // Once the cache file has been opened, the child is notified and sent an
   // open file descriptor.
   async OnOpenCacheFile(int64_t fileSize, FileDescriptor fileDesc);
 
 both:
   async __delete__(AsmJSCacheResult result);
--- a/dom/base/ChromeUtils.cpp
+++ b/dom/base/ChromeUtils.cpp
@@ -177,38 +177,16 @@ ChromeUtils::IsOriginAttributesEqual(dom
 {
   return IsOriginAttributesEqual(aA, aB);
 }
 
 /* static */ bool
 ChromeUtils::IsOriginAttributesEqual(const dom::OriginAttributesDictionary& aA,
                                      const dom::OriginAttributesDictionary& aB)
 {
-  return aA.mAddonId == aB.mAddonId &&
-         aA.mAppId == aB.mAppId &&
-         aA.mInIsolatedMozBrowser == aB.mInIsolatedMozBrowser &&
-         aA.mUserContextId == aB.mUserContextId &&
-         aA.mPrivateBrowsingId == aB.mPrivateBrowsingId;
-}
-
-/* static */ bool
-ChromeUtils::IsOriginAttributesEqualIgnoringAddonId(const dom::OriginAttributesDictionary& aA,
-                                                    const dom::OriginAttributesDictionary& aB)
-{
   return aA.mAppId == aB.mAppId &&
          aA.mInIsolatedMozBrowser == aB.mInIsolatedMozBrowser &&
          aA.mUserContextId == aB.mUserContextId &&
          aA.mPrivateBrowsingId == aB.mPrivateBrowsingId;
 }
 
-/* static */ bool
-ChromeUtils::IsOriginAttributesEqualIgnoringFPD(const dom::OriginAttributesDictionary& aA,
-                                                const dom::OriginAttributesDictionary& aB)
-{
-  return aA.mAddonId == aB.mAddonId &&
-         aA.mAppId == aB.mAppId &&
-         aA.mInIsolatedMozBrowser == aB.mInIsolatedMozBrowser &&
-         aA.mUserContextId == aB.mUserContextId &&
-         aA.mPrivateBrowsingId == aB.mPrivateBrowsingId;
-}
-
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/ChromeUtils.h
+++ b/dom/base/ChromeUtils.h
@@ -88,20 +88,22 @@ public:
                           const dom::OriginAttributesDictionary& aA,
                           const dom::OriginAttributesDictionary& aB);
 
   static bool
   IsOriginAttributesEqual(const dom::OriginAttributesDictionary& aA,
                           const dom::OriginAttributesDictionary& aB);
 
   static bool
-  IsOriginAttributesEqualIgnoringAddonId(const dom::OriginAttributesDictionary& aA,
-                                         const dom::OriginAttributesDictionary& aB);
-
-  static bool
   IsOriginAttributesEqualIgnoringFPD(const dom::OriginAttributesDictionary& aA,
-                                     const dom::OriginAttributesDictionary& aB);
+                                     const dom::OriginAttributesDictionary& aB)
+  {
+    return aA.mAppId == aB.mAppId &&
+           aA.mInIsolatedMozBrowser == aB.mInIsolatedMozBrowser &&
+           aA.mUserContextId == aB.mUserContextId &&
+           aA.mPrivateBrowsingId == aB.mPrivateBrowsingId;
+  }
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_ChromeUtils__
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -219,17 +219,17 @@ Element::UpdateLinkState(EventStates aSt
     (mState & ~(NS_EVENT_STATE_VISITED | NS_EVENT_STATE_UNVISITED)) |
     aState;
 }
 
 void
 Element::UpdateState(bool aNotify)
 {
   EventStates oldState = mState;
-  mState = IntrinsicState() | (oldState & ESM_MANAGED_STATES);
+  mState = IntrinsicState() | (oldState & EXTERNALLY_MANAGED_STATES);
   if (aNotify) {
     EventStates changedStates = oldState ^ mState;
     if (!changedStates.IsEmpty()) {
       nsIDocument* doc = GetComposedDoc();
       if (doc) {
         nsAutoScriptBlocker scriptBlocker;
         doc->ContentStateChanged(this, changedStates);
       }
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -510,34 +510,49 @@ private:
   void NotifyStateChange(EventStates aStates);
 
   void NotifyStyleStateChange(EventStates aStates);
 
   // Style state computed from element's state and style locks.
   EventStates StyleStateFromLocks() const;
 
 protected:
-  // Methods for the ESM to manage state bits.  These will handle
-  // setting up script blockers when they notify, so no need to do it
-  // in the callers unless desired.
+  // Methods for the ESM, nsGlobalWindow and focus manager to manage state bits.
+  // These will handle setting up script blockers when they notify, so no need
+  // to do it in the callers unless desired.  States passed here must only be
+  // those in EXTERNALLY_MANAGED_STATES.
   virtual void AddStates(EventStates aStates)
   {
     NS_PRECONDITION(!aStates.HasAtLeastOneOfStates(INTRINSIC_STATES),
-                    "Should only be adding ESM-managed states here");
+                    "Should only be adding externally-managed states here");
     AddStatesSilently(aStates);
     NotifyStateChange(aStates);
   }
   virtual void RemoveStates(EventStates aStates)
   {
     NS_PRECONDITION(!aStates.HasAtLeastOneOfStates(INTRINSIC_STATES),
-                    "Should only be removing ESM-managed states here");
+                    "Should only be removing externally-managed states here");
     RemoveStatesSilently(aStates);
     NotifyStateChange(aStates);
   }
 public:
+  // Public methods to manage state bits in MANUALLY_MANAGED_STATES.
+  void AddManuallyManagedStates(EventStates aStates)
+  {
+    MOZ_ASSERT(MANUALLY_MANAGED_STATES.HasAllStates(aStates),
+               "Should only be adding manually-managed states here");
+    AddStates(aStates);
+  }
+  void RemoveManuallyManagedStates(EventStates aStates)
+  {
+    MOZ_ASSERT(MANUALLY_MANAGED_STATES.HasAllStates(aStates),
+               "Should only be removing manually-managed states here");
+    RemoveStates(aStates);
+  }
+
   virtual void UpdateEditableState(bool aNotify) override;
 
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent,
                               bool aCompileEventHandlers) override;
   virtual void UnbindFromTree(bool aDeep = true,
                               bool aNullParent = true) override;
 
--- a/dom/base/EventSource.cpp
+++ b/dom/base/EventSource.cpp
@@ -990,28 +990,32 @@ EventSourceImpl::GetBaseURI(nsIURI** aBa
   return NS_OK;
 }
 
 void
 EventSourceImpl::SetupHttpChannel()
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(!IsShutDown());
-  mHttpChannel->SetRequestMethod(NS_LITERAL_CSTRING("GET"));
+  DebugOnly<nsresult> rv =
+    mHttpChannel->SetRequestMethod(NS_LITERAL_CSTRING("GET"));
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
 
   /* set the http request headers */
 
-  mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
+  rv = mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
     NS_LITERAL_CSTRING(TEXT_EVENT_STREAM), false);
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
 
   // LOAD_BYPASS_CACHE already adds the Cache-Control: no-cache header
 
   if (!mLastEventID.IsEmpty()) {
-    mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Last-Event-ID"),
+    rv = mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Last-Event-ID"),
       NS_ConvertUTF16toUTF8(mLastEventID), false);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
   }
 }
 
 nsresult
 EventSourceImpl::SetupReferrerPolicy()
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(!IsShutDown());
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -1344,17 +1344,18 @@ Navigator::SendBeaconInternal(const nsAS
   }
 
   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
   if (!httpChannel) {
     // Beacon spec only supports HTTP requests at this time
     aRv.Throw(NS_ERROR_DOM_BAD_URI);
     return false;
   }
-  httpChannel->SetReferrer(documentURI);
+  rv = httpChannel->SetReferrer(documentURI);
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
 
   nsCOMPtr<nsIInputStream> in;
   nsAutoCString contentTypeWithCharset;
   nsAutoCString charset;
   uint64_t length = 0;
 
   if (aBody) {
     aRv = aBody->GetAsStream(getter_AddRefs(in), &length,
@@ -1374,17 +1375,18 @@ Navigator::SendBeaconInternal(const nsAS
       aRv.Throw(NS_ERROR_FAILURE);
       return false;
     }
 
     uploadChannel->ExplicitSetUploadStream(in, contentTypeWithCharset, length,
                                            NS_LITERAL_CSTRING("POST"),
                                            false);
   } else {
-    httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("POST"));
+    rv = httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("POST"));
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
   }
 
   nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(channel);
   if (p) {
     p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
   }
 
   nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel));
--- a/dom/base/PostMessageEvent.cpp
+++ b/dom/base/PostMessageEvent.cpp
@@ -100,17 +100,17 @@ PostMessageEvent::Run()
     if (NS_WARN_IF(!targetPrin))
       return NS_OK;
 
     // Note: This is contrary to the spec with respect to file: URLs, which
     //       the spec groups into a single origin, but given we intentionally
     //       don't do that in other places it seems better to hold the line for
     //       now.  Long-term, we want HTML5 to address this so that we can
     //       be compliant while being safer.
-    if (!BasePrincipal::Cast(targetPrin)->EqualsIgnoringAddonId(mProvidedPrincipal)) {
+    if (!targetPrin->Equals(mProvidedPrincipal)) {
       nsAutoString providedOrigin, targetOrigin;
       nsresult rv = nsContentUtils::GetUTFOrigin(targetPrin, targetOrigin);
       NS_ENSURE_SUCCESS(rv, rv);
       rv = nsContentUtils::GetUTFOrigin(mProvidedPrincipal, providedOrigin);
       NS_ENSURE_SUCCESS(rv, rv);
 
       MOZ_DIAGNOSTIC_ASSERT(providedOrigin != targetOrigin ||
                             (mProvidedPrincipal->OriginAttributesRef() ==
--- a/dom/base/nsContentSink.cpp
+++ b/dom/base/nsContentSink.cpp
@@ -889,17 +889,18 @@ nsContentSink::PrefetchDNS(const nsAStri
     if (NS_SUCCEEDED(rv) && !isLocalResource) {
       nsAutoCString host;
       uri->GetHost(host);
       CopyUTF8toUTF16(host, hostname);
     }
   }
 
   if (!hostname.IsEmpty() && nsHTMLDNSPrefetch::IsAllowed(mDocument)) {
-    nsHTMLDNSPrefetch::PrefetchLow(hostname);
+    nsHTMLDNSPrefetch::PrefetchLow(hostname,
+                                   mDocument->NodePrincipal()->OriginAttributesRef());
   }
 }
 
 void
 nsContentSink::Preconnect(const nsAString& aHref, const nsAString& aCrossOrigin)
 {
   // construct URI using document charset
   const nsACString& charset = mDocument->GetDocumentCharacterSet();
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -3187,36 +3187,36 @@ nsContentUtils::GetOriginAttributes(nsID
 
   nsCOMPtr<nsILoadGroup> loadGroup = aDocument->GetDocumentLoadGroup();
   if (loadGroup) {
     return GetOriginAttributes(loadGroup);
   }
 
   mozilla::OriginAttributes attrs;
   nsCOMPtr<nsIChannel> channel = aDocument->GetChannel();
-  if (channel && NS_GetOriginAttributes(channel, attrs)) {
-    attrs.StripAttributes(OriginAttributes::STRIP_ADDON_ID);
+  if (channel) {
+    NS_GetOriginAttributes(channel, attrs);
   }
   return attrs;
 }
 
 // static
 mozilla::OriginAttributes
 nsContentUtils::GetOriginAttributes(nsILoadGroup* aLoadGroup)
 {
   if (!aLoadGroup) {
     return mozilla::OriginAttributes();
   }
   mozilla::OriginAttributes attrs;
   nsCOMPtr<nsIInterfaceRequestor> callbacks;
   aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
   if (callbacks) {
     nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
-    if (loadContext && loadContext->GetOriginAttributes(attrs)) {
-      attrs.StripAttributes(OriginAttributes::STRIP_ADDON_ID);
+    if (loadContext) {
+      loadContext->GetOriginAttributes(attrs);
     }
   }
   return attrs;
 }
 
 // static
 bool
 nsContentUtils::IsInPrivateBrowsing(nsIDocument* aDoc)
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -3054,18 +3054,17 @@ nsDOMWindowUtils::GetFileReferences(cons
                                     int32_t* aSliceRefCnt, JSContext* aCx,
                                     bool* aResult)
 {
   nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
   NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
 
   nsCString origin;
   nsresult rv =
-    quota::QuotaManager::GetInfoFromWindow(window, nullptr, nullptr, &origin,
-                                           nullptr);
+    quota::QuotaManager::GetInfoFromWindow(window, nullptr, nullptr, &origin);
   NS_ENSURE_SUCCESS(rv, rv);
 
   IDBOpenDBOptions options;
   JS::Rooted<JS::Value> optionsVal(aCx, aOptions);
   if (!options.Init(aCx, optionsVal)) {
     return NS_ERROR_TYPE_ERR;
   }
 
@@ -4090,16 +4089,80 @@ nsDOMWindowUtils::IsTimeoutTracking(uint
   NS_ENSURE_STATE(window);
   nsCOMPtr<nsPIDOMWindowInner> innerWindow = window->GetCurrentInnerWindow();
   NS_ENSURE_STATE(innerWindow);
 
   *aResult = innerWindow->TimeoutManager().IsTimeoutTracking(aTimeoutId);
   return NS_OK;
 }
 
+struct StateTableEntry
+{
+  const char* mStateString;
+  EventStates mState;
+};
+
+static constexpr StateTableEntry kManuallyManagedStates[] = {
+  // none yet; but for example: { "highlight", NS_EVENT_STATE_HIGHLIGHT },
+  { nullptr, EventStates() },
+};
+
+static_assert(!kManuallyManagedStates[ArrayLength(kManuallyManagedStates) - 1]
+               .mStateString,
+              "last kManuallyManagedStates entry must be a sentinel with "
+              "mStateString == nullptr");
+
+static EventStates
+GetEventStateForString(const nsAString& aStateString)
+{
+  for (const StateTableEntry* entry = kManuallyManagedStates;
+       entry->mStateString; ++entry) {
+    if (aStateString.EqualsASCII(entry->mStateString)) {
+      return entry->mState;
+    }
+  }
+  return EventStates();
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::AddManuallyManagedState(nsIDOMElement* aElement,
+                                          const nsAString& aStateString)
+{
+  nsCOMPtr<Element> element = do_QueryInterface(aElement);
+  if (!element) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  EventStates state = GetEventStateForString(aStateString);
+  if (state.IsEmpty()) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  element->AddManuallyManagedStates(state);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::RemoveManuallyManagedState(nsIDOMElement* aElement,
+                                             const nsAString& aStateString)
+{
+  nsCOMPtr<Element> element = do_QueryInterface(aElement);
+  if (!element) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  EventStates state = GetEventStateForString(aStateString);
+  if (state.IsEmpty()) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  element->RemoveManuallyManagedStates(state);
+  return NS_OK;
+}
+
 NS_INTERFACE_MAP_BEGIN(nsTranslationNodeList)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_INTERFACE_MAP_ENTRY(nsITranslationNodeList)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_ADDREF(nsTranslationNodeList)
 NS_IMPL_RELEASE(nsTranslationNodeList)
 
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -2633,21 +2633,21 @@ nsDocument::InitCSP(nsIChannel* aChannel
 
   nsCOMPtr<nsIHttpChannel> httpChannel;
   nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if (httpChannel) {
-    httpChannel->GetResponseHeader(
+    Unused << httpChannel->GetResponseHeader(
         NS_LITERAL_CSTRING("content-security-policy"),
         tCspHeaderValue);
 
-    httpChannel->GetResponseHeader(
+    Unused << httpChannel->GetResponseHeader(
         NS_LITERAL_CSTRING("content-security-policy-report-only"),
         tCspROHeaderValue);
   }
   NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue);
   NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue);
 
   // Check if this is a document from a WebExtension.
   nsString addonId;
@@ -4791,17 +4791,18 @@ nsDocument::SetScriptGlobalObject(nsIScr
   if (csp) {
     static_cast<nsCSPContext*>(csp.get())->flushConsoleMessages();
   }
 
   nsCOMPtr<nsIHttpChannelInternal> internalChannel =
     do_QueryInterface(GetChannel());
   if (internalChannel) {
     nsCOMArray<nsISecurityConsoleMessage> messages;
-    internalChannel->TakeAllSecurityMessages(messages);
+    DebugOnly<nsresult> rv = internalChannel->TakeAllSecurityMessages(messages);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
     SendToConsole(messages);
   }
 
   // Set our visibility state, but do not fire the event.  This is correct
   // because either we're coming out of bfcache (in which case IsVisible() will
   // still test false at this point and no state change will happen) or we're
   // doing the initial document load and don't want to fire the event for this
   // change.
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -1127,16 +1127,17 @@ GK_ATOM(scrollbar, "scrollbar")
 GK_ATOM(scrollbarbutton, "scrollbarbutton")
 GK_ATOM(scrollbarDownBottom, "scrollbar-down-bottom")
 GK_ATOM(scrollbarDownTop, "scrollbar-down-top")
 GK_ATOM(scrollbarUpBottom, "scrollbar-up-bottom")
 GK_ATOM(scrollbarUpTop, "scrollbar-up-top")
 GK_ATOM(scrollbox, "scrollbox")
 GK_ATOM(scrollcorner, "scrollcorner")
 GK_ATOM(scrolling, "scrolling")
+GK_ATOM(scrollPosition, "scroll-position")
 GK_ATOM(section, "section")
 GK_ATOM(select, "select")
 GK_ATOM(selectable, "selectable")
 GK_ATOM(selected, "selected")
 GK_ATOM(selectedIndex, "selectedIndex")
 GK_ATOM(selectedindex, "selectedindex")
 GK_ATOM(self, "self")
 GK_ATOM(seltype, "seltype")
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -3170,17 +3170,16 @@ nsGlobalWindow::SetNewDocument(nsIDocume
   nsCOMPtr<nsIPrincipal> principal = mDoc->NodePrincipal();
   nsString addonId;
   principal->GetAddonId(addonId);
   if (GetDocGroup() && !nsContentUtils::IsSystemPrincipal(principal) && addonId.IsEmpty()) {
     js::SetCompartmentValidAccessPtr(cx, newInnerGlobal,
                                      newInnerWindow->GetDocGroup()->GetValidAccessPtr());
   }
 
-  nsJSContext::PokeGC(JS::gcreason::SET_NEW_DOCUMENT, GetWrapperPreserveColor());
   kungFuDeathGrip->DidInitializeContext();
 
   // We wait to fire the debugger hook until the window is all set up and hooked
   // up with the outer. See bug 969156.
   if (createdInnerWindow) {
     nsContentUtils::AddScriptRunner(
       NewRunnableMethod(newInnerWindow,
                         &nsGlobalWindow::FireOnNewGlobalObject));
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -1178,18 +1178,19 @@ FullGCTimerFired(nsITimer* aTimer, void*
 
 //static
 void
 nsJSContext::GarbageCollectNow(JS::gcreason::Reason aReason,
                                IsIncremental aIncremental,
                                IsShrinking aShrinking,
                                int64_t aSliceMillis)
 {
-  PROFILER_LABEL("nsJSContext", "GarbageCollectNow",
-    js::ProfileEntry::Category::GC);
+  PROFILER_LABEL_PRINTF("nsJSContext", "GarbageCollectNow",
+                        js::ProfileEntry::Category::GC,
+                        "%s", JS::gcreason::ExplainReason(aReason));
 
   MOZ_ASSERT_IF(aSliceMillis, aIncremental == IncrementalGC);
 
   KillGCTimer();
 
   // Reset sPendingLoadCount in case the timer that fired was a
   // timer we scheduled due to a normal GC timer firing while
   // documents were loading. If this happens we're waiting for a
@@ -2395,27 +2396,26 @@ AsmJSCacheOpenEntryForRead(JS::Handle<JS
   nsIPrincipal* principal =
     nsJSPrincipals::get(JS_GetCompartmentPrincipals(js::GetObjectCompartment(aGlobal)));
   return asmjscache::OpenEntryForRead(principal, aBegin, aLimit, aSize, aMemory,
                                       aHandle);
 }
 
 static JS::AsmJSCacheResult
 AsmJSCacheOpenEntryForWrite(JS::Handle<JSObject*> aGlobal,
-                            bool aInstalled,
                             const char16_t* aBegin,
                             const char16_t* aEnd,
                             size_t aSize,
                             uint8_t** aMemory,
                             intptr_t* aHandle)
 {
   nsIPrincipal* principal =
     nsJSPrincipals::get(JS_GetCompartmentPrincipals(js::GetObjectCompartment(aGlobal)));
-  return asmjscache::OpenEntryForWrite(principal, aInstalled, aBegin, aEnd,
-                                       aSize, aMemory, aHandle);
+  return asmjscache::OpenEntryForWrite(principal, aBegin, aEnd, aSize, aMemory,
+                                       aHandle);
 }
 
 class AsyncTaskRunnable final : public Runnable
 {
   ~AsyncTaskRunnable()
   {
     MOZ_ASSERT(!mTask);
   }
--- a/dom/base/nsObjectLoadingContent.cpp
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -2594,18 +2594,19 @@ nsObjectLoadingContent::OpenChannel()
     nsCOMPtr<nsILoadInfo> loadinfo = chan->GetLoadInfo();
     NS_ENSURE_STATE(loadinfo);
     loadinfo->SetPrincipalToInherit(thisContent->NodePrincipal());
   }
 
   // Referrer
   nsCOMPtr<nsIHttpChannel> httpChan(do_QueryInterface(chan));
   if (httpChan) {
-    httpChan->SetReferrerWithPolicy(doc->GetDocumentURI(),
-                                    doc->GetReferrerPolicy());
+    rv = httpChan->SetReferrerWithPolicy(doc->GetDocumentURI(),
+                                         doc->GetReferrerPolicy());
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
 
     // Set the initiator type
     nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChan));
     if (timedChannel) {
       timedChannel->SetInitiatorType(thisContent->LocalName());
     }
   }
 
--- a/dom/base/nsScriptLoader.cpp
+++ b/dom/base/nsScriptLoader.cpp
@@ -1293,25 +1293,28 @@ nsScriptLoader::StartLoad(nsScriptLoadRe
       // other scripts are neither blocked nor prioritized unless marked deferred
       cos->AddClassFlags(nsIClassOfService::Unblocked);
     }
   }
 
   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
   if (httpChannel) {
     // HTTP content negotation has little value in this context.
-    httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
-                                  NS_LITERAL_CSTRING("*/*"),
-                                  false);
-    httpChannel->SetReferrerWithPolicy(mDocument->GetDocumentURI(),
-                                       aRequest->mReferrerPolicy);
+    rv = httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
+                                       NS_LITERAL_CSTRING("*/*"),
+                                       false);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+    rv = httpChannel->SetReferrerWithPolicy(mDocument->GetDocumentURI(),
+                                            aRequest->mReferrerPolicy);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
 
     nsCOMPtr<nsIHttpChannelInternal> internalChannel(do_QueryInterface(httpChannel));
     if (internalChannel) {
-      internalChannel->SetIntegrityMetadata(aRequest->mIntegrity.GetIntegrityString());
+      rv = internalChannel->SetIntegrityMetadata(aRequest->mIntegrity.GetIntegrityString());
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
     }
   }
 
   OriginAttributes attrs;
   attrs.Inherit(mDocument->NodePrincipal()->OriginAttributesRef());
 
   mozilla::net::PredictorLearn(aRequest->mURI, mDocument->GetDocumentURI(),
       nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE, attrs);
--- a/dom/base/nsSyncLoadService.cpp
+++ b/dom/base/nsSyncLoadService.cpp
@@ -138,25 +138,27 @@ nsSyncLoader::LoadDocument(nsIChannel* a
     NS_ENSURE_ARG(aChannel);
     NS_ENSURE_ARG_POINTER(aResult);
     *aResult = nullptr;
     nsresult rv = NS_OK;
 
     mChannel = aChannel;
     nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(mChannel);
     if (http) {
-        http->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
-                               NS_LITERAL_CSTRING("text/xml,application/xml,application/xhtml+xml,*/*;q=0.1"),
-                               false);
+        rv = http->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
+                                    NS_LITERAL_CSTRING("text/xml,application/xml,application/xhtml+xml,*/*;q=0.1"),
+                                    false);
+        MOZ_ASSERT(NS_SUCCEEDED(rv));
         nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
         if (loadInfo) {
             nsCOMPtr<nsIURI> loaderUri;
             loadInfo->TriggeringPrincipal()->GetURI(getter_AddRefs(loaderUri));
             if (loaderUri) {
-              http->SetReferrerWithPolicy(loaderUri, aReferrerPolicy);
+                rv = http->SetReferrerWithPolicy(loaderUri, aReferrerPolicy);
+                MOZ_ASSERT(NS_SUCCEEDED(rv));
             }
         }
     }
 
     // Hook us up to listen to redirects and the like.
     // Do this before setting up the cross-site proxy since
     // that installs its own proxies.
     mChannel->SetNotificationCallbacks(this);
--- a/dom/cache/Context.cpp
+++ b/dom/cache/Context.cpp
@@ -255,17 +255,16 @@ Context::QuotaInitRunnable::OpenDirector
 
   // QuotaManager::OpenDirectory() will hold a reference to us as
   // a listener.  We will then get DirectoryLockAcquired() on the owning
   // thread when it is safe to access our storage directory.
   mState = STATE_WAIT_FOR_DIRECTORY_LOCK;
   QuotaManager::Get()->OpenDirectory(PERSISTENCE_TYPE_DEFAULT,
                                      mQuotaInfo.mGroup,
                                      mQuotaInfo.mOrigin,
-                                     mQuotaInfo.mIsApp,
                                      quota::Client::DOMCACHE,
                                      /* aExclusive */ false,
                                      this);
 }
 
 void
 Context::QuotaInitRunnable::DirectoryLockAcquired(DirectoryLock* aLock)
 {
@@ -372,18 +371,17 @@ Context::QuotaInitRunnable::Run()
         break;
       }
 
       RefPtr<ManagerId> managerId = mManager->GetManagerId();
       nsCOMPtr<nsIPrincipal> principal = managerId->Principal();
       nsresult rv = QuotaManager::GetInfoFromPrincipal(principal,
                                                        &mQuotaInfo.mSuffix,
                                                        &mQuotaInfo.mGroup,
-                                                       &mQuotaInfo.mOrigin,
-                                                       &mQuotaInfo.mIsApp);
+                                                       &mQuotaInfo.mOrigin);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         resolver->Resolve(rv);
         break;
       }
 
       mState = STATE_CREATE_QUOTA_MANAGER;
       MOZ_ALWAYS_SUCCEEDS(
         mInitiatingThread->Dispatch(this, nsIThread::DISPATCH_NORMAL));
@@ -432,17 +430,16 @@ Context::QuotaInitRunnable::Run()
       }
 
       QuotaManager* qm = QuotaManager::Get();
       MOZ_DIAGNOSTIC_ASSERT(qm);
       nsresult rv = qm->EnsureOriginIsInitialized(PERSISTENCE_TYPE_DEFAULT,
                                                   mQuotaInfo.mSuffix,
                                                   mQuotaInfo.mGroup,
                                                   mQuotaInfo.mOrigin,
-                                                  mQuotaInfo.mIsApp,
                                                   getter_AddRefs(mQuotaInfo.mDir));
       if (NS_FAILED(rv)) {
         resolver->Resolve(rv);
         break;
       }
 
       mState = STATE_RUN_ON_TARGET;
 
--- a/dom/cache/ManagerId.cpp
+++ b/dom/cache/ManagerId.cpp
@@ -25,18 +25,17 @@ ManagerId::Create(nsIPrincipal* aPrincip
 
   // The QuotaManager::GetInfoFromPrincipal() has special logic for system
   // and about: principals.  We need to use the same modified origin in
   // order to interpret calls from QM correctly.
   nsCString quotaOrigin;
   nsresult rv = QuotaManager::GetInfoFromPrincipal(aPrincipal,
                                                    nullptr,   // suffix
                                                    nullptr,   // group
-                                                   &quotaOrigin,
-                                                   nullptr);  // is app
+                                                   &quotaOrigin);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   RefPtr<ManagerId> ref = new ManagerId(aPrincipal, quotaOrigin);
   ref.forget(aManagerIdOut);
 
   return NS_OK;
 }
 
--- a/dom/cache/Types.h
+++ b/dom/cache/Types.h
@@ -24,21 +24,19 @@ enum Namespace
 };
 static const Namespace INVALID_NAMESPACE = NUMBER_OF_NAMESPACES;
 
 typedef int64_t CacheId;
 static const CacheId INVALID_CACHE_ID = -1;
 
 struct QuotaInfo
 {
-  QuotaInfo() : mIsApp(false) { }
   nsCOMPtr<nsIFile> mDir;
   nsCString mSuffix;
   nsCString mGroup;
   nsCString mOrigin;
-  bool mIsApp;
 };
 
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_cache_Types_h
--- a/dom/cache/test/mochitest/mochitest.ini
+++ b/dom/cache/test/mochitest/mochitest.ini
@@ -38,10 +38,11 @@ support-files =
 [test_cache_delete.html]
 [test_cache_put_reorder.html]
 [test_cache_https.html]
 [test_cache_redirect.html]
 [test_cache_restart.html]
 [test_cache_shrink.html]
 [test_cache_orphaned_cache.html]
 [test_cache_orphaned_body.html]
+scheme=https
 [test_cache_untrusted.html]
 [test_chrome_constructor.html]
--- a/dom/console/Console.cpp
+++ b/dom/console/Console.cpp
@@ -152,16 +152,25 @@ public:
   }
 
   void
   SetOriginAttributes(const OriginAttributes& aOriginAttributes)
   {
     mOriginAttributes = aOriginAttributes;
   }
 
+  void
+  SetAddonId(nsIPrincipal* aPrincipal)
+  {
+    nsAutoString addonId;
+    aPrincipal->GetAddonId(addonId);
+
+    mAddonId = addonId;
+  }
+
   bool
   PopulateArgumentsSequence(Sequence<JS::Value>& aSequence) const
   {
     AssertIsOnOwningThread();
 
     for (uint32_t i = 0; i < mCopiedArguments.Length(); ++i) {
       if (NS_WARN_IF(!aSequence.AppendElement(mCopiedArguments[i],
                                               fallible))) {
@@ -245,16 +254,18 @@ public:
   uint64_t mOuterIDNumber;
   nsString mOuterIDString;
 
   uint64_t mInnerIDNumber;
   nsString mInnerIDString;
 
   OriginAttributes mOriginAttributes;
 
+  nsString mAddonId;
+
   nsString mMethodString;
 
   // Stack management is complicated, because we want to do it as
   // lazily as possible.  Therefore, we have the following behavior:
   // 1)  mTopStackFrame is initialized whenever we have any JS on the stack
   // 2)  mReifiedStack is initialized if we're created in a worker.
   // 3)  mStack is set (possibly to null if there is no JS on the stack) if
   //     we're created on main thread.
@@ -1212,16 +1223,17 @@ Console::MethodInternal(JSContext* aCx, 
     }
 
     nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
     if (NS_WARN_IF(!principal)) {
       return;
     }
 
     oa = principal->OriginAttributesRef();
+    callData->SetAddonId(principal);
 
 #ifdef DEBUG
     if (!nsContentUtils::IsSystemPrincipal(principal)) {
       nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(mWindow);
       if (webNav) {
         nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav);
         MOZ_ASSERT(loadContext);
 
@@ -1495,16 +1507,18 @@ Console::PopulateConsoleNotificationInTh
 
   // 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;
   } else if (aData->mIDType == ConsoleCallData::eNumber) {
     event.mID.Value().SetAsUnsignedLongLong() = aData->mOuterIDNumber;
--- a/dom/events/DataTransfer.cpp
+++ b/dom/events/DataTransfer.cpp
@@ -676,17 +676,17 @@ DataTransfer::SetDataAtInternal(const ns
   if (aIndex > 0 &&
       (mEventMessage == eCut || mEventMessage == eCopy ||
        mEventMessage == ePaste)) {
     return NS_ERROR_DOM_INDEX_SIZE_ERR;
   }
 
   // Don't allow the custom type to be assigned.
   if (aFormat.EqualsLiteral(kCustomTypesMime)) {
-    return NS_ERROR_TYPE_ERR;
+    return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
   }
 
   if (!PrincipalMaySetData(aFormat, aData, aSubjectPrincipal)) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
   return SetDataWithPrincipal(aFormat, aData, aIndex, aSubjectPrincipal);
 }
--- a/dom/events/EventStates.h
+++ b/dom/events/EventStates.h
@@ -309,17 +309,39 @@ private:
 #define NS_EVENT_STATE_IGNORE NS_DEFINE_EVENT_STATE_MACRO(63)
 
 /**
  * NOTE: do not go over 63 without updating EventStates::InternalType!
  */
 
 #define DIRECTION_STATES (NS_EVENT_STATE_LTR | NS_EVENT_STATE_RTL)
 
-#define ESM_MANAGED_STATES (NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_FOCUS |  \
-                            NS_EVENT_STATE_HOVER | NS_EVENT_STATE_DRAGOVER |   \
-                            NS_EVENT_STATE_URLTARGET | NS_EVENT_STATE_FOCUSRING | \
-                            NS_EVENT_STATE_FULL_SCREEN | NS_EVENT_STATE_UNRESOLVED | \
-                            NS_EVENT_STATE_FOCUS_WITHIN)
+// Event states that can be added and removed through
+// Element::{Add,Remove}ManuallyManagedStates.
+//
+// Take care when manually managing state bits.  You are responsible for
+// setting or clearing the bit when an Element is added or removed from a
+// document (e.g. in BindToTree and UnbindFromTree), if that is an
+// appropriate thing to do for your state bit.
+#define MANUALLY_MANAGED_STATES (             \
+  mozilla::EventStates() /* none so far */    \
+)
 
-#define INTRINSIC_STATES (~ESM_MANAGED_STATES)
+// Event states that are managed externally to an element (by the
+// EventStateManager, or by other code).  As opposed to those in
+// INTRINSIC_STATES, which are are computed by the element itself
+// and returned from Element::IntrinsicState.
+#define EXTERNALLY_MANAGED_STATES (           \
+  MANUALLY_MANAGED_STATES |                   \
+  NS_EVENT_STATE_ACTIVE |                     \
+  NS_EVENT_STATE_DRAGOVER |                   \
+  NS_EVENT_STATE_FOCUS |                      \
+  NS_EVENT_STATE_FOCUSRING |                  \
+  NS_EVENT_STATE_FOCUS_WITHIN |               \
+  NS_EVENT_STATE_FULL_SCREEN |                \
+  NS_EVENT_STATE_HOVER |                      \
+  NS_EVENT_STATE_UNRESOLVED |                 \
+  NS_EVENT_STATE_URLTARGET                    \
+)
+
+#define INTRINSIC_STATES (~EXTERNALLY_MANAGED_STATES)
 
 #endif // mozilla_EventStates_h_
--- a/dom/fetch/FetchDriver.cpp
+++ b/dom/fetch/FetchDriver.cpp
@@ -293,21 +293,25 @@ FetchDriver::HttpFetch()
     // Auth may require prompting, we don't support it yet.
     // The next patch in this same bug prevents this from aborting the request.
     // Credentials checks for CORS are handled by nsCORSListenerProxy,
 
     nsCOMPtr<nsIHttpChannelInternal> internalChan = do_QueryInterface(httpChan);
 
     // Conversion between enumerations is safe due to static asserts in
     // dom/workers/ServiceWorkerManager.cpp
-    internalChan->SetCorsMode(static_cast<uint32_t>(mRequest->Mode()));
-    internalChan->SetRedirectMode(static_cast<uint32_t>(mRequest->GetRedirectMode()));
+    rv = internalChan->SetCorsMode(static_cast<uint32_t>(mRequest->Mode()));
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+    rv = internalChan->SetRedirectMode(static_cast<uint32_t>(mRequest->GetRedirectMode()));
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
     mRequest->MaybeSkipCacheIfPerformingRevalidation();
-    internalChan->SetFetchCacheMode(static_cast<uint32_t>(mRequest->GetCacheMode()));
-    internalChan->SetIntegrityMetadata(mRequest->GetIntegrity());
+    rv = internalChan->SetFetchCacheMode(static_cast<uint32_t>(mRequest->GetCacheMode()));
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+    rv = internalChan->SetIntegrityMetadata(mRequest->GetIntegrity());
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
   }
 
   // Step 5. Proxy authentication will be handled by Necko.
 
   // Continue setting up 'HTTPRequest'. Content-Type and body data.
   nsCOMPtr<nsIUploadChannel2> uploadChan = do_QueryInterface(chan);
   if (uploadChan) {
     nsAutoCString contentType;
@@ -446,30 +450,32 @@ FetchDriver::OnStartRequest(nsIRequest* 
   bool foundOpaqueRedirect = false;
 
   int64_t contentLength = InternalResponse::UNKNOWN_BODY_SIZE;
   rv = channel->GetContentLength(&contentLength);
   MOZ_ASSERT_IF(NS_FAILED(rv), contentLength == InternalResponse::UNKNOWN_BODY_SIZE);
 
   if (httpChannel) {
     uint32_t responseStatus;
-    httpChannel->GetResponseStatus(&responseStatus);
+    rv = httpChannel->GetResponseStatus(&responseStatus);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
 
     if (mozilla::net::nsHttpChannel::IsRedirectStatus(responseStatus)) {
       if (mRequest->GetRedirectMode() == RequestRedirect::Error) {
         FailWithNetworkError();
         return NS_BINDING_FAILED;
       }
       if (mRequest->GetRedirectMode() == RequestRedirect::Manual) {
         foundOpaqueRedirect = true;
       }
     }
 
     nsAutoCString statusText;
-    httpChannel->GetResponseStatusText(statusText);
+    rv = httpChannel->GetResponseStatusText(statusText);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
 
     response = new InternalResponse(responseStatus, statusText);
 
     response->Headers()->FillResponseHeaders(httpChannel);
 
     // If Content-Encoding or Transfer-Encoding headers are set, then the actual
     // Content-Length (which refer to the decoded data) is obscured behind the encodings.
     ErrorResult result;
@@ -734,18 +740,18 @@ FetchDriver::AsyncOnChannelRedirect(nsIC
   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aNewChannel);
   if (httpChannel) {
     SetRequestHeaders(httpChannel);
   }
 
   nsCOMPtr<nsIHttpChannel> oldHttpChannel = do_QueryInterface(aOldChannel);
   nsAutoCString tRPHeaderCValue;
   if (oldHttpChannel) {
-    oldHttpChannel->GetResponseHeader(NS_LITERAL_CSTRING("referrer-policy"),
-                                      tRPHeaderCValue);
+    Unused << oldHttpChannel->GetResponseHeader(NS_LITERAL_CSTRING("referrer-policy"),
+                                                tRPHeaderCValue);
   }
 
   // "HTTP-redirect fetch": step 14 "Append locationURL to request's URL list."
   nsCOMPtr<nsIURI> uri;
   MOZ_ALWAYS_SUCCEEDS(aNewChannel->GetURI(getter_AddRefs(uri)));
 
   nsCOMPtr<nsIURI> uriClone;
   nsresult rv = uri->CloneIgnoringRef(getter_AddRefs(uriClone));
@@ -831,32 +837,40 @@ FetchDriver::SetRequestHeaders(nsIHttpCh
   AutoTArray<InternalHeaders::Entry, 5> headers;
   mRequest->Headers()->GetEntries(headers);
   bool hasAccept = false;
   for (uint32_t i = 0; i < headers.Length(); ++i) {
     if (!hasAccept && headers[i].mName.EqualsLiteral("accept")) {
       hasAccept = true;
     }
     if (headers[i].mValue.IsEmpty()) {
-      aChannel->SetEmptyRequestHeader(headers[i].mName);
+      DebugOnly<nsresult> rv = aChannel->SetEmptyRequestHeader(headers[i].mName);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
     } else {
-      aChannel->SetRequestHeader(headers[i].mName, headers[i].mValue, false /* merge */);
+      DebugOnly<nsresult> rv =
+        aChannel->SetRequestHeader(headers[i].mName, headers[i].mValue,
+                                   false /* merge */);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
     }
   }
 
   if (!hasAccept) {
-    aChannel->SetRequestHeader(NS_LITERAL_CSTRING("accept"),
-                               NS_LITERAL_CSTRING("*/*"),
-                               false /* merge */);
+    DebugOnly<nsresult> rv =
+      aChannel->SetRequestHeader(NS_LITERAL_CSTRING("accept"),
+                                 NS_LITERAL_CSTRING("*/*"),
+                                 false /* merge */);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
   }
 
   if (mRequest->ForceOriginHeader()) {
     nsAutoString origin;
     if (NS_SUCCEEDED(nsContentUtils::GetUTFOrigin(mPrincipal, origin))) {
-      aChannel->SetRequestHeader(NS_LITERAL_CSTRING("origin"),
-                                 NS_ConvertUTF16toUTF8(origin),
-                                 false /* merge */);
+      DebugOnly<nsresult> rv =
+        aChannel->SetRequestHeader(NS_LITERAL_CSTRING("origin"),
+                                   NS_ConvertUTF16toUTF8(origin),
+                                   false /* merge */);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
     }
   }
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/fetch/FetchUtil.cpp
+++ b/dom/fetch/FetchUtil.cpp
@@ -143,17 +143,17 @@ FetchUtil::SetRequestReferrer(nsIPrincip
     rv = NS_NewURI(getter_AddRefs(referrerURI), referrer, nullptr, nullptr);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = aChannel->SetReferrerWithPolicy(referrerURI, policy);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   nsCOMPtr<nsIURI> referrerURI;
-  aChannel->GetReferrer(getter_AddRefs(referrerURI));
+  Unused << aChannel->GetReferrer(getter_AddRefs(referrerURI));
 
   // Step 8 https://fetch.spec.whatwg.org/#main-fetch
   // If request’s referrer is not "no-referrer", set request’s referrer to
   // the result of invoking determine request’s referrer.
   if (referrerURI) {
     nsAutoCString spec;
     rv = referrerURI->GetSpec(spec);
     NS_ENSURE_SUCCESS(rv, rv);
--- a/dom/fetch/InternalHeaders.cpp
+++ b/dom/fetch/InternalHeaders.cpp
@@ -350,17 +350,20 @@ void
 InternalHeaders::FillResponseHeaders(nsIRequest* aRequest)
 {
   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
   if (!httpChannel) {
     return;
   }
 
   RefPtr<FillHeaders> visitor = new FillHeaders(this);
-  httpChannel->VisitResponseHeaders(visitor);
+  nsresult rv = httpChannel->VisitResponseHeaders(visitor);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("failed to fill headers");
+  }
 }
 
 bool
 InternalHeaders::HasOnlySimpleHeaders() const
 {
   for (uint32_t i = 0; i < mList.Length(); ++i) {
     if (!IsSimpleHeader(mList[i].mName, mList[i].mValue)) {
       return false;
--- a/dom/file/FileReader.cpp
+++ b/dom/file/FileReader.cpp
@@ -286,18 +286,23 @@ FileReader::DoReadData(uint64_t aCount)
     MOZ_ASSERT(mResult.Length() == mDataLen, "unexpected mResult length");
     if (uint64_t(oldLen) + aCount > UINT32_MAX)
       return NS_ERROR_OUT_OF_MEMORY;
     char16_t *buf = nullptr;
     mResult.GetMutableData(&buf, oldLen + aCount, fallible);
     NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY);
 
     uint32_t bytesRead = 0;
-    mAsyncStream->ReadSegments(ReadFuncBinaryString, buf + oldLen, aCount,
-                               &bytesRead);
+    nsresult rv =
+      mAsyncStream->ReadSegments(ReadFuncBinaryString, buf + oldLen, aCount,
+                                 &bytesRead);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
     MOZ_ASSERT(bytesRead == aCount, "failed to read data");
   }
   else {
     CheckedInt<uint64_t> size = mDataLen;
     size += aCount;
 
     //Update memory buffer to reflect the contents of the file
     if (!size.isValid() ||
@@ -309,17 +314,21 @@ FileReader::DoReadData(uint64_t aCount)
 
     if (mDataFormat != FILE_AS_ARRAYBUFFER) {
       mFileData = (char *) realloc(mFileData, mDataLen + aCount);
       NS_ENSURE_TRUE(mFileData, NS_ERROR_OUT_OF_MEMORY);
     }
 
     uint32_t bytesRead = 0;
     MOZ_DIAGNOSTIC_ASSERT(mFileData);
-    mAsyncStream->Read(mFileData + mDataLen, aCount, &bytesRead);
+    nsresult rv = mAsyncStream->Read(mFileData + mDataLen, aCount, &bytesRead);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
     MOZ_ASSERT(bytesRead == aCount, "failed to read data");
   }
 
   mDataLen += aCount;
   return NS_OK;
 }
 
 // Helper methods
@@ -608,35 +617,35 @@ FileReader::OnInputStreamReady(nsIAsyncI
     return NS_OK;
   }
 
   // We use this class to decrease the busy counter at the end of this method.
   // In theory we can do it immediatelly but, for debugging reasons, we want to
   // be 100% sure we have a workerHolder when OnLoadEnd() is called.
   FileReaderDecreaseBusyCounter RAII(this);
 
-  uint64_t aCount;
-  nsresult rv = aStream->Available(&aCount);
+  uint64_t count;
+  nsresult rv = aStream->Available(&count);
 
-  if (NS_SUCCEEDED(rv) && aCount) {
-    rv = DoReadData(aCount);
+  if (NS_SUCCEEDED(rv) && count) {
+    rv = DoReadData(count);
   }
 
   if (NS_SUCCEEDED(rv)) {
     rv = DoAsyncWait();
   }
 
-  if (NS_FAILED(rv) || !aCount) {
+  if (NS_FAILED(rv) || !count) {
     if (rv == NS_BASE_STREAM_CLOSED) {
       rv = NS_OK;
     }
     return OnLoadEnd(rv);
   }
 
-  mTransferred += aCount;
+  mTransferred += count;
 
   //Notify the timer is the appropriate timeframe has passed
   if (mTimerIsActive) {
     mProgressEventWasDelayed = true;
   } else {
     rv = DispatchProgressEvent(NS_LITERAL_STRING(PROGRESS_STR));
     NS_ENSURE_SUCCESS(rv, rv);
 
--- a/dom/flyweb/HttpServer.cpp
+++ b/dom/flyweb/HttpServer.cpp
@@ -286,17 +286,19 @@ HttpServer::TransportProvider::SetTransp
 
 void
 HttpServer::TransportProvider::MaybeNotify()
 {
   if (mTransport && mListener) {
     RefPtr<TransportProvider> self = this;
     nsCOMPtr<nsIRunnable> event = NS_NewRunnableFunction([self, this] ()
     {
-      mListener->OnTransportAvailable(mTransport, mInput, mOutput);
+      DebugOnly<nsresult> rv = mListener->OnTransportAvailable(mTransport,
+                                                               mInput, mOutput);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
     });
     NS_DispatchToCurrentThread(event);
   }
 }
 
 NS_IMPL_ISUPPORTS(HttpServer::Connection,
                   nsIInputStreamCallback,
                   nsIOutputStreamCallback)
--- a/dom/geolocation/nsGeolocation.cpp
+++ b/dom/geolocation/nsGeolocation.cpp
@@ -688,25 +688,16 @@ nsresult nsGeolocationService::Init()
   }
 
   obs->AddObserver(this, "xpcom-shutdown", false);
 
 #ifdef MOZ_WIDGET_ANDROID
   mProvider = new AndroidLocationProvider();
 #endif
 
-#ifdef MOZ_WIDGET_GONK
-  // GonkGPSGeolocationProvider can be started at boot up time for initialization reasons.
-  // do_getService gets hold of the already initialized component and starts
-  // processing location requests immediately.
-  // do_Createinstance will create multiple instances of the provider which is not right.
-  // bug 993041
-  mProvider = do_GetService(GONK_GPS_GEOLOCATION_PROVIDER_CONTRACTID);
-#endif
-
 #ifdef MOZ_WIDGET_GTK
 #ifdef MOZ_GPSD
   if (Preferences::GetBool("geo.provider.use_gpsd", false)) {
     mProvider = new GpsdLocationProvider();
   }
 #endif
 #endif
 
@@ -728,21 +719,21 @@ nsresult nsGeolocationService::Init()
   }
 
   // Override platform-specific providers with the default (network)
   // provider while testing. Our tests are currently not meant to exercise
   // the provider, and some tests rely on the network provider being used.
   // "geo.provider.testing" is always set for all plain and browser chrome
   // mochitests, and also for xpcshell tests.
   if (!mProvider || Preferences::GetBool("geo.provider.testing", false)) {
-    nsCOMPtr<nsIGeolocationProvider> geo_net_provider =
+    nsCOMPtr<nsIGeolocationProvider> geoTestProvider =
       do_GetService(NS_GEOLOCATION_PROVIDER_CONTRACTID);
 
-    if (geo_net_provider) {
-      mProvider = geo_net_provider;
+    if (geoTestProvider) {
+      mProvider = geoTestProvider;
     }
   }
 
   return NS_OK;
 }
 
 nsGeolocationService::~nsGeolocationService() = default;
 
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -6610,24 +6610,16 @@ HTMLInputElement::GetFiles(nsIDOMFileLis
 NS_IMETHODIMP
 HTMLInputElement::GetSelectionRange(int32_t* aSelectionStart,
                                     int32_t* aSelectionEnd)
 {
   // Flush frames, because our editor state will want to work with the frame.
   if (IsInComposedDoc()) {
     GetComposedDoc()->FlushPendingNotifications(FlushType::Frames);
   }
-  if (!GetPrimaryFrame()) {
-    // Can we return a selection range anyway here, now that it lives on our
-    // state?  In fact, could we make this behave more like
-    // GetSelectionDirection, in the sense of working even when we have no
-    // frame, by just delegating entirely to mState?  And then, do we really
-    // need the flush?
-    return NS_ERROR_FAILURE;
-  }
 
   nsTextEditorState* state = GetEditorState();
   if (!state) {
     // Not a text control.
     return NS_ERROR_FAILURE;
   }
 
   return state->GetSelectionRange(aSelectionStart, aSelectionEnd);
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -27,17 +27,16 @@
 #include "nsSize.h"
 #include "nsIFrame.h"
 #include "nsIDocument.h"
 #include "nsIDOMDocument.h"
 #include "nsIDocShell.h"
 #include "nsError.h"
 #include "nsNodeInfoManager.h"
 #include "nsNetUtil.h"
-#include "nsXPCOMStrings.h"
 #include "xpcpublic.h"
 #include "nsThreadUtils.h"
 #include "nsIThreadInternal.h"
 #include "nsContentUtils.h"
 #include "nsIRequest.h"
 #include "nsQueryObject.h"
 #include "nsIObserverService.h"
 #include "nsISupportsPrimitives.h"
@@ -512,17 +511,17 @@ HTMLMediaElement::MediaLoadListener::OnS
     return status;
   }
 
   nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(aRequest);
   bool succeeded;
   if (hc && NS_SUCCEEDED(hc->GetRequestSucceeded(&succeeded)) && !succeeded) {
     element->NotifyLoadError();
     uint32_t responseStatus = 0;
-    hc->GetResponseStatus(&responseStatus);
+    Unused << hc->GetResponseStatus(&responseStatus);
     nsAutoString code;
     code.AppendInt(responseStatus);
     nsAutoString src;
     element->GetCurrentSrc(src);
     const char16_t* params[] = { code.get(), src.get() };
     element->ReportLoadError("MediaLoadHttpError", params, ArrayLength(params));
     return NS_BINDING_ABORTED;
   }
@@ -1164,19 +1163,20 @@ public:
 
     channel->SetNotificationCallbacks(loadListener);
 
     nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(channel);
     if (hc) {
       // Use a byte range request from the start of the resource.
       // This enables us to detect if the stream supports byte range
       // requests, and therefore seeking, early.
-      hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"),
-                           NS_LITERAL_CSTRING("bytes=0-"),
-                           false);
+      rv = hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"),
+                                NS_LITERAL_CSTRING("bytes=0-"),
+                                false);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
       aElement->SetRequestHeaders(hc);
     }
 
     rv = channel->AsyncOpen2(loadListener);
     if (NS_FAILED(rv)) {
       // Notify load error so the element will try next resource candidate.
       aElement->NotifyLoadError();
       return;
@@ -6450,22 +6450,25 @@ void HTMLMediaElement::SetRequestHeaders
   // Send Accept header for video and audio types only (Bug 489071)
   SetAcceptHeader(aChannel);
 
   // Apache doesn't send Content-Length when gzip transfer encoding is used,
   // which prevents us from estimating the video length (if explicit Content-Duration
   // and a length spec in the container are not present either) and from seeking.
   // So, disable the standard "Accept-Encoding: gzip,deflate" that we usually send.
   // See bug 614760.
-  aChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept-Encoding"),
-                             EmptyCString(), false);
+  DebugOnly<nsresult> rv =
+    aChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept-Encoding"),
+                               EmptyCString(), false);
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
 
   // Set the Referer header
-  aChannel->SetReferrerWithPolicy(OwnerDoc()->GetDocumentURI(),
-                                  OwnerDoc()->GetReferrerPolicy());
+  rv = aChannel->SetReferrerWithPolicy(OwnerDoc()->GetDocumentURI(),
+                                       OwnerDoc()->GetReferrerPolicy());
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
 }
 
 void HTMLMediaElement::FireTimeUpdate(bool aPeriodic)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
   TimeStamp now = TimeStamp::Now();
   double time = CurrentTime();
--- a/dom/html/HTMLTextAreaElement.cpp
+++ b/dom/html/HTMLTextAreaElement.cpp
@@ -843,24 +843,16 @@ HTMLTextAreaElement::SetSelectionEnd(con
 NS_IMETHODIMP
 HTMLTextAreaElement::GetSelectionRange(int32_t* aSelectionStart,
                                        int32_t* aSelectionEnd)
 {
   // Flush frames, because our editor state will want to work with the frame.
   if (IsInComposedDoc()) {
     GetComposedDoc()->FlushPendingNotifications(FlushType::Frames);
   }
-  if (!GetPrimaryFrame()) {
-    // Can we return a selection range anyway here, now that it lives on our
-    // state?  In fact, could we make this behave more like
-    // GetSelectionDirection, in the sense of working even when we have no
-    // frame, by just delegating entirely to mState?  And then, do we really
-    // need the flush?
-    return NS_ERROR_FAILURE;
-  }
 
   return mState.GetSelectionRange(aSelectionStart, aSelectionEnd);
 }
 
 static void
 DirectionToName(nsITextControlFrame::SelectionDirection dir, nsAString& aDirection)
 {
   if (dir == nsITextControlFrame::eNone) {
--- a/dom/html/HTMLVideoElement.cpp
+++ b/dom/html/HTMLVideoElement.cpp
@@ -8,17 +8,16 @@
 #include "mozilla/dom/HTMLVideoElement.h"
 #include "mozilla/dom/HTMLVideoElementBinding.h"
 #include "nsGenericHTMLElement.h"
 #include "nsGkAtoms.h"
 #include "nsSize.h"
 #include "nsError.h"
 #include "nsNodeInfoManager.h"
 #include "plbase64.h"
-#include "nsXPCOMStrings.h"
 #include "prlock.h"
 #include "nsThreadUtils.h"
 #include "ImageContainer.h"
 #include "VideoFrameContainer.h"
 
 #include "nsIScriptSecurityManager.h"
 #include "nsIXPConnect.h"
 
new file mode 100644
--- /dev/null
+++ b/dom/html/crashtests/1343886-1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <script>
+            document.documentElement.scrollTop = "500";
+            o1 = document.createRange();
+            o2 = document.createElement('input'); 
+            o1.selectNode(document.documentElement);
+            o1.surroundContents(o2);
+            o2.selectionStart;
+        </script>
+    </head>
+    <body></body>
+</html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/html/crashtests/1343886-2.xml
@@ -0,0 +1,3 @@
+<input xmlns="http://www.w3.org/1999/xhtml">
+  <script>document.documentElement.selectionStart</script>
+</input>
new file mode 100644
--- /dev/null
+++ b/dom/html/crashtests/1343886-3.xml
@@ -0,0 +1,3 @@
+<textarea xmlns="http://www.w3.org/1999/xhtml">
+  <script>document.documentElement.selectionStart</script>
+</textarea>
--- a/dom/html/crashtests/crashtests.list
+++ b/dom/html/crashtests/crashtests.list
@@ -73,8 +73,11 @@ load 916322-2.html
 load 1032654.html
 load 1141260.html
 load 1228876.html
 load 1230110.html
 load 1237633.html
 load 1281972-1.html
 load 1282894.html
 load 1290904.html
+load 1343886-1.html
+load 1343886-2.xml
+load 1343886-3.xml
--- a/dom/html/nsHTMLDNSPrefetch.cpp
+++ b/dom/html/nsHTMLDNSPrefetch.cpp
@@ -124,113 +124,131 @@ nsHTMLDNSPrefetch::PrefetchMedium(Link *
 
 nsresult
 nsHTMLDNSPrefetch::PrefetchHigh(Link *aElement)
 {
   return Prefetch(aElement, 0);
 }
 
 nsresult
-nsHTMLDNSPrefetch::Prefetch(const nsAString &hostname, uint16_t flags)
+nsHTMLDNSPrefetch::Prefetch(const nsAString &hostname,
+                            const OriginAttributes &aOriginAttributes,
+                            uint16_t flags)
 {
   if (IsNeckoChild()) {
     // We need to check IsEmpty() because net_IsValidHostName()
     // considers empty strings to be valid hostnames
     if (!hostname.IsEmpty() &&
         net_IsValidHostName(NS_ConvertUTF16toUTF8(hostname))) {
       // during shutdown gNeckoChild might be null
       if (gNeckoChild) {
-        gNeckoChild->SendHTMLDNSPrefetch(nsAutoString(hostname), flags);
+        gNeckoChild->SendHTMLDNSPrefetch(nsString(hostname),
+                                         aOriginAttributes, flags);
       }
     }
     return NS_OK;
   }
 
   if (!(sInitialized && sDNSService && sPrefetches && sDNSListener))
     return NS_ERROR_NOT_AVAILABLE;
 
   nsCOMPtr<nsICancelable> tmpOutstanding;
-  return sDNSService->AsyncResolve(NS_ConvertUTF16toUTF8(hostname),
-                                   flags | nsIDNSService::RESOLVE_SPECULATE,
-                                   sDNSListener, nullptr, 
-                                   getter_AddRefs(tmpOutstanding));
+  return sDNSService->AsyncResolveNative(NS_ConvertUTF16toUTF8(hostname),
+                                         flags | nsIDNSService::RESOLVE_SPECULATE,
+                                         sDNSListener, nullptr, aOriginAttributes,
+                                         getter_AddRefs(tmpOutstanding));
 }
 
 nsresult
-nsHTMLDNSPrefetch::PrefetchLow(const nsAString &hostname)
+nsHTMLDNSPrefetch::PrefetchLow(const nsAString &hostname,
+                               const OriginAttributes &aOriginAttributes)
 {
-  return Prefetch(hostname, nsIDNSService::RESOLVE_PRIORITY_LOW);
+  return Prefetch(hostname, aOriginAttributes, nsIDNSService::RESOLVE_PRIORITY_LOW);
 }
 
 nsresult
-nsHTMLDNSPrefetch::PrefetchMedium(const nsAString &hostname)
+nsHTMLDNSPrefetch::PrefetchMedium(const nsAString &hostname,
+                                  const OriginAttributes &aOriginAttributes)
 {
-  return Prefetch(hostname, nsIDNSService::RESOLVE_PRIORITY_MEDIUM);
+  return Prefetch(hostname, aOriginAttributes, nsIDNSService::RESOLVE_PRIORITY_MEDIUM);
 }
 
 nsresult
-nsHTMLDNSPrefetch::PrefetchHigh(const nsAString &hostname)
+nsHTMLDNSPrefetch::PrefetchHigh(const nsAString &hostname,
+                                const OriginAttributes &aOriginAttributes)
 {
-  return Prefetch(hostname, 0);
+  return Prefetch(hostname, aOriginAttributes, 0);
 }
 
 nsresult
 nsHTMLDNSPrefetch::CancelPrefetch(Link *aElement,
                                   uint16_t flags,
                                   nsresult aReason)
 {
   if (!(sInitialized && sPrefetches && sDNSService && sDNSListener))
     return NS_ERROR_NOT_AVAILABLE;
 
   nsAutoString hostname;
   aElement->GetHostname(hostname);
-  return CancelPrefetch(hostname, flags, aReason);
+
+  Element* element = aElement->GetElement();
+  NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
+
+  return CancelPrefetch(hostname,
+                        element->NodePrincipal()
+                               ->OriginAttributesRef(),
+                        flags, aReason);
 }
 
 nsresult
 nsHTMLDNSPrefetch::CancelPrefetch(const nsAString &hostname,
+                                  const OriginAttributes &aOriginAttributes,
                                   uint16_t flags,
                                   nsresult aReason)
 {
   // Forward this request to Necko Parent if we're a child process
   if (IsNeckoChild()) {
     // We need to check IsEmpty() because net_IsValidHostName()
     // considers empty strings to be valid hostnames
     if (!hostname.IsEmpty() &&
         net_IsValidHostName(NS_ConvertUTF16toUTF8(hostname))) {
       // during shutdown gNeckoChild might be null
       if (gNeckoChild) {
-        gNeckoChild->SendCancelHTMLDNSPrefetch(nsString(hostname), flags,
+        gNeckoChild->SendCancelHTMLDNSPrefetch(nsString(hostname),
+                                               aOriginAttributes,
+                                               flags,
                                                aReason);
       }
     }
     return NS_OK;
   }
 
   if (!(sInitialized && sDNSService && sPrefetches && sDNSListener))
     return NS_ERROR_NOT_AVAILABLE;
 
   // Forward cancellation to DNS service
-  return sDNSService->CancelAsyncResolve(NS_ConvertUTF16toUTF8(hostname),
-                                         flags
-                                         | nsIDNSService::RESOLVE_SPECULATE,
-                                         sDNSListener, aReason);
+  return sDNSService->CancelAsyncResolveNative(NS_ConvertUTF16toUTF8(hostname),
+                                               flags
+                                               | nsIDNSService::RESOLVE_SPECULATE,
+                                               sDNSListener, aReason, aOriginAttributes);
 }
 
 nsresult
 nsHTMLDNSPrefetch::CancelPrefetchLow(Link *aElement, nsresult aReason)
 {
   return CancelPrefetch(aElement, nsIDNSService::RESOLVE_PRIORITY_LOW,
                         aReason);
 }
 
 nsresult
-nsHTMLDNSPrefetch::CancelPrefetchLow(const nsAString &hostname, nsresult aReason)
+nsHTMLDNSPrefetch::CancelPrefetchLow(const nsAString &hostname,
+                                     const OriginAttributes &aOriginAttributes,
+                                     nsresult aReason)
 {
-  return CancelPrefetch(hostname, nsIDNSService::RESOLVE_PRIORITY_LOW,
+  return CancelPrefetch(hostname, aOriginAttributes, nsIDNSService::RESOLVE_PRIORITY_LOW,
                         aReason);
 }
 
 
 /////////////////////////////////////////////////////////////////////////////////////////////////////////
 
 NS_IMPL_ISUPPORTS(nsHTMLDNSPrefetch::nsListener,
                   nsIDNSListener)
@@ -313,52 +331,58 @@ nsHTMLDNSPrefetch::nsDeferrals::SubmitQu
     nsCOMPtr<nsIContent> content = do_QueryReferent(mEntries[mTail].mElement);
     if (content) {
       nsCOMPtr<Link> link = do_QueryInterface(content);
       // Only prefetch here if request was deferred and deferral not cancelled
       if (link && link->HasDeferredDNSPrefetchRequest()) {
         nsCOMPtr<nsIURI> hrefURI(link ? link->GetURI() : nullptr);
         bool isLocalResource = false;
         nsresult rv = NS_OK;
+        Element* element = link->GetElement();
 
         hostName.Truncate();
         if (hrefURI) {
           hrefURI->GetAsciiHost(hostName);
           rv = NS_URIChainHasFlags(hrefURI,
                                    nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
                                    &isLocalResource);
         }
 
-        if (!hostName.IsEmpty() && NS_SUCCEEDED(rv) && !isLocalResource) {
+        if (!hostName.IsEmpty() && NS_SUCCEEDED(rv) && !isLocalResource &&
+            element) {
           if (IsNeckoChild()) {
             // during shutdown gNeckoChild might be null
             if (gNeckoChild) {
               gNeckoChild->SendHTMLDNSPrefetch(NS_ConvertUTF8toUTF16(hostName),
+                                               element->NodePrincipal()
+                                                      ->OriginAttributesRef(),
                                                mEntries[mTail].mFlags);
             }
           } else {
             nsCOMPtr<nsICancelable> tmpOutstanding;
 
-            rv = sDNSService->AsyncResolve(hostName,
-                                           mEntries[mTail].mFlags
-                                           | nsIDNSService::RESOLVE_SPECULATE,
-                                           sDNSListener, nullptr,
-                                           getter_AddRefs(tmpOutstanding));
+            rv = sDNSService->AsyncResolveNative(hostName,
+                                                 mEntries[mTail].mFlags
+                                                 | nsIDNSService::RESOLVE_SPECULATE,
+                                                 sDNSListener, nullptr,
+                                                 element->NodePrincipal()
+                                                        ->OriginAttributesRef(),
+                                                 getter_AddRefs(tmpOutstanding));
             // Tell link that deferred prefetch was requested
             if (NS_SUCCEEDED(rv))
               link->OnDNSPrefetchRequested();
           }
         }
       }
     }
-    
+
     mEntries[mTail].mElement = nullptr;
     mTail = (mTail + 1) & sMaxDeferredMask;
   }
-  
+
   if (mTimerArmed) {
     mTimerArmed = false;
     mTimer->Cancel();
   }
 }
 
 void
 nsHTMLDNSPrefetch::nsDeferrals::Activate()
--- a/dom/html/nsHTMLDNSPrefetch.h
+++ b/dom/html/nsHTMLDNSPrefetch.h
@@ -46,27 +46,35 @@ public:
   // complete, while the string versions submit the lookup to 
   // the DNS system immediately. The URI version is somewhat lighter
   // weight, but its request is also more likely to be dropped due to a 
   // full queue and it may only be used from the main thread.
 
   static nsresult PrefetchHigh(mozilla::dom::Link *aElement);
   static nsresult PrefetchMedium(mozilla::dom::Link *aElement);
   static nsresult PrefetchLow(mozilla::dom::Link *aElement);
-  static nsresult PrefetchHigh(const nsAString &host);
-  static nsresult PrefetchMedium(const nsAString &host);
-  static nsresult PrefetchLow(const nsAString &host);
-  static nsresult CancelPrefetchLow(const nsAString &host, nsresult aReason);
+  static nsresult PrefetchHigh(const nsAString &host,
+                               const mozilla::OriginAttributes &aOriginAttributes);
+  static nsresult PrefetchMedium(const nsAString &host,
+                                 const mozilla::OriginAttributes &aOriginAttributes);
+  static nsresult PrefetchLow(const nsAString &host,
+                              const mozilla::OriginAttributes &aOriginAttributes);
+  static nsresult CancelPrefetchLow(const nsAString &host,
+                                    const mozilla::OriginAttributes &aOriginAttributes,
+                                    nsresult aReason);
   static nsresult CancelPrefetchLow(mozilla::dom::Link *aElement,
                                     nsresult aReason);
 
 private:
-  static nsresult Prefetch(const nsAString &host, uint16_t flags);
+  static nsresult Prefetch(const nsAString &host,
+                           const mozilla::OriginAttributes &aOriginAttributes,
+                           uint16_t flags);
   static nsresult Prefetch(mozilla::dom::Link *aElement, uint16_t flags);
   static nsresult CancelPrefetch(const nsAString &hostname,
+                                 const mozilla::OriginAttributes &aOriginAttributes,
                                  uint16_t flags,
                                  nsresult aReason);
   static nsresult CancelPrefetch(mozilla::dom::Link *aElement,
                                  uint16_t flags,
                                  nsresult aReason);
   
 public:
   class nsListener final : public nsIDNSListener
--- a/dom/html/nsTextEditorState.cpp
+++ b/dom/html/nsTextEditorState.cpp
@@ -1548,21 +1548,23 @@ nsTextEditorState::SetSelectionPropertie
     mSelectionProperties = aProps;
   }
 }
 
 nsresult
 nsTextEditorState::GetSelectionRange(int32_t* aSelectionStart,
                                      int32_t* aSelectionEnd)
 {
-  MOZ_ASSERT(mBoundFrame,
-             "Caller didn't flush out frames and check for a frame?");
   MOZ_ASSERT(aSelectionStart);
   MOZ_ASSERT(aSelectionEnd);
 
+  if (!mBoundFrame) {
+    return NS_ERROR_FAILURE;
+  }
+
   // It's not clear that all the checks here are needed, but the previous
   // version of this code in nsTextControlFrame was doing them, so we keep them
   // for now.
 
   nsresult rv = mBoundFrame->EnsureEditorInitialized();
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsISelectionController* selCon = GetSelectionController();
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -161,17 +161,17 @@ class VersionChangeTransaction;
  ******************************************************************************/
 
 // If JS_STRUCTURED_CLONE_VERSION changes then we need to update our major
 // schema version.
 static_assert(JS_STRUCTURED_CLONE_VERSION == 8,
               "Need to update the major schema version.");
 
 // Major schema version. Bump for almost everything.
-const uint32_t kMajorSchemaVersion = 25;
+const uint32_t kMajorSchemaVersion = 26;
 
 // Minor schema version. Should almost always be 0 (maybe bump on release
 // branches if we have to).
 const uint32_t kMinorSchemaVersion = 0;
 
 // The schema version we store in the SQLite database is a (signed) 32-bit
 // integer. The major version is left-shifted 4 bits so the max value is
 // 0xFFFFFFF. The minor version occupies the lower 4 bits and its max is 0xF.
@@ -239,16 +239,17 @@ const uint32_t kConnectionThreadIdleMS =
 
 #define SAVEPOINT_CLAUSE "SAVEPOINT sp;"
 
 const uint32_t kFileCopyBufferSize = 32768;
 
 #define JOURNAL_DIRECTORY_NAME "journals"
 
 const char kFileManagerDirectoryNameSuffix[] = ".files";
+const char kSQLiteSuffix[] = ".sqlite";
 const char kSQLiteJournalSuffix[] = ".sqlite-journal";
 const char kSQLiteSHMSuffix[] = ".sqlite-shm";
 const char kSQLiteWALSuffix[] = ".sqlite-wal";
 
 const char kPrefIndexedDBEnabled[] = "dom.indexedDB.enabled";
 
 const char kPrefFileHandleEnabled[] = "dom.fileHandle.enabled";
 
@@ -4129,16 +4130,120 @@ UpgradeSchemaFrom24_0To25_0(mozIStorageC
   nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(25, 0));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
+class StripObsoleteOriginAttributesFunction final
+  : public mozIStorageFunction
+{
+public:
+  NS_DECL_ISUPPORTS
+
+private:
+  ~StripObsoleteOriginAttributesFunction()
+  { }
+
+  NS_IMETHOD
+  OnFunctionCall(mozIStorageValueArray* aArguments,
+                 nsIVariant** aResult) override
+  {
+    MOZ_ASSERT(aArguments);
+    MOZ_ASSERT(aResult);
+
+    PROFILER_LABEL("IndexedDB",
+                   "StripObsoleteOriginAttributesFunction::OnFunctionCall",
+                   js::ProfileEntry::Category::STORAGE);
+
+#ifdef DEBUG
+  {
+    uint32_t argCount;
+    MOZ_ALWAYS_SUCCEEDS(aArguments->GetNumEntries(&argCount));
+    MOZ_ASSERT(argCount == 1);
+
+    int32_t type;
+    MOZ_ALWAYS_SUCCEEDS(aArguments->GetTypeOfIndex(0, &type));
+    MOZ_ASSERT(type == mozIStorageValueArray::VALUE_TYPE_TEXT);
+  }
+#endif
+
+    nsCString origin;
+    nsresult rv = aArguments->GetUTF8String(0, origin);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    // Deserialize and re-serialize to automatically drop any obsolete origin
+    // attributes.
+    OriginAttributes oa;
+
+    nsCString originNoSuffix;
+    bool ok = oa.PopulateFromOrigin(origin, originNoSuffix);
+    if (NS_WARN_IF(!ok)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    nsCString suffix;
+    oa.CreateSuffix(suffix);
+
+    nsCOMPtr<nsIVariant> result =
+      new mozilla::storage::UTF8TextVariant(originNoSuffix + suffix);
+
+    result.forget(aResult);
+    return NS_OK;
+  }
+};
+
+nsresult
+UpgradeSchemaFrom25_0To26_0(mozIStorageConnection* aConnection)
+{
+  AssertIsOnIOThread();
+  MOZ_ASSERT(aConnection);
+
+  PROFILER_LABEL("IndexedDB",
+                 "UpgradeSchemaFrom25_0To26_0",
+                 js::ProfileEntry::Category::STORAGE);
+
+  NS_NAMED_LITERAL_CSTRING(functionName, "strip_obsolete_attributes");
+
+  nsCOMPtr<mozIStorageFunction> stripObsoleteAttributes =
+    new StripObsoleteOriginAttributesFunction();
+
+  nsresult rv = aConnection->CreateFunction(functionName,
+                                            /* aNumArguments */ 1,
+                                            stripObsoleteAttributes);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "UPDATE DATABASE "
+      "SET origin = strip_obsolete_attributes(origin) "
+      "WHERE origin LIKE '%^%';"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->RemoveFunction(functionName);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->SetSchemaVersion(MakeSchemaVersion(26, 0));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
 nsresult
 GetDatabaseFileURL(nsIFile* aDatabaseFile,
                    PersistenceType aPersistenceType,
                    const nsACString& aGroup,
                    const nsACString& aOrigin,
                    uint32_t aTelemetryId,
                    nsIFileURL** aResult)
 {
@@ -4635,17 +4740,17 @@ CreateStorageConnection(nsIFile* aDBFile
       }
 
       rv = stmt->Execute();
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     } else  {
       // This logic needs to change next time we change the schema!
-      static_assert(kSQLiteSchemaVersion == int32_t((25 << 4) + 0),
+      static_assert(kSQLiteSchemaVersion == int32_t((26 << 4) + 0),
                     "Upgrade function needed due to schema version increase.");
 
       while (schemaVersion != kSQLiteSchemaVersion) {
         if (schemaVersion == 4) {
           rv = UpgradeSchemaFrom4To5(connection);
         } else if (schemaVersion == 5) {
           rv = UpgradeSchemaFrom5To6(connection);
         } else if (schemaVersion == 6) {
@@ -4683,16 +4788,18 @@ CreateStorageConnection(nsIFile* aDBFile
         } else if (schemaVersion == MakeSchemaVersion(21, 0)) {
           rv = UpgradeSchemaFrom21_0To22_0(connection);
         } else if (schemaVersion == MakeSchemaVersion(22, 0)) {
           rv = UpgradeSchemaFrom22_0To23_0(connection, aOrigin);
         } else if (schemaVersion == MakeSchemaVersion(23, 0)) {
           rv = UpgradeSchemaFrom23_0To24_0(connection);
         } else if (schemaVersion == MakeSchemaVersion(24, 0)) {
           rv = UpgradeSchemaFrom24_0To25_0(connection);
+        } else if (schemaVersion == MakeSchemaVersion(25, 0)) {
+          rv = UpgradeSchemaFrom25_0To26_0(connection);
         } else {
           IDB_WARNING("Unable to open IndexedDB database, no upgrade path is "
                       "available!");
           return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
         }
 
         if (NS_WARN_IF(NS_FAILED(rv))) {
           return rv;
@@ -7477,17 +7584,16 @@ protected:
 
   const CommonFactoryRequestParams mCommonParams;
   nsCString mSuffix;
   nsCString mGroup;
   nsCString mOrigin;
   nsCString mDatabaseId;
   nsString mDatabaseFilePath;
   State mState;
-  bool mIsApp;
   bool mEnforcingQuota;
   const bool mDeleting;
   bool mBlockedDatabaseOpen;
   bool mChromeWriteAccessAllowed;
   bool mFileHandleDisabled;
 
 public:
   void
@@ -9230,16 +9336,19 @@ public:
   GetOrCreateThreadPool();
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(QuotaClient, override)
 
   mozilla::dom::quota::Client::Type
   GetType() override;
 
   nsresult
+  UpgradeStorageFrom1_0To2_0(nsIFile* aDirectory) override;
+
+  nsresult
   InitOrigin(PersistenceType aPersistenceType,
              const nsACString& aGroup,
              const nsACString& aOrigin,
              UsageInfo* aUsageInfo) override;
 
   nsresult
   GetUsageForOrigin(PersistenceType aPersistenceType,
                     const nsACString& aGroup,
@@ -9279,16 +9388,23 @@ private:
   ~QuotaClient() override;
 
   nsresult
   GetDirectory(PersistenceType aPersistenceType,
                const nsACString& aOrigin,
                nsIFile** aDirectory);
 
   nsresult
+  GetDatabaseFilenames(nsIFile* aDirectory,
+                       UsageInfo* aUsageInfo,
+                       bool aForUpgrade,
+                       nsTArray<nsString>& aSubdirsToProcess,
+                       nsTHashtable<nsStringHashKey>& aDatabaseFilename);
+
+  nsresult
   GetUsageForDirectoryInternal(nsIFile* aDirectory,
                                UsageInfo* aUsageInfo,
                                bool aDatabaseFiles);
 
   // Runs on the PBackground thread. Checks to see if there's a queued
   // Maintenance to run.
   void
   ProcessMaintenanceQueue();
@@ -10036,34 +10152,31 @@ DeserializeStructuredCloneFiles(FileMana
       *aHasPreprocessInfo = true;
     }
   }
 
   return NS_OK;
 }
 
 bool
-GetDatabaseBaseFilename(const nsAString& aFilename,
-                        nsDependentSubstring& aDatabaseBaseFilename)
+GetBaseFilename(const nsAString& aFilename,
+                const nsAString& aSuffix,
+                nsDependentSubstring& aBaseFilename)
 {
   MOZ_ASSERT(!aFilename.IsEmpty());
-  MOZ_ASSERT(aDatabaseBaseFilename.IsEmpty());
-
-  NS_NAMED_LITERAL_STRING(sqlite, ".sqlite");