Merge autoland to mozilla-central r=merge a=merge
authorarthur.iakab <aiakab@mozilla.com>
Fri, 05 Jan 2018 23:51:43 +0200
changeset 398043 2a4362ce2d9fa82f62909532a1b534d214761341
parent 398042 3fd1f23aa20477d3c0b6fed59a3d5f336786bb84 (current diff)
parent 398001 c592bcac2149084ca3ad5a72f77b8cf3c60db970 (diff)
child 398072 56c1eb9c065a52490d1f3438c882e8f90aef9cb8
push id57618
push userarchaeopteryx@coole-files.de
push dateSat, 06 Jan 2018 00:01:01 +0000
treeherderautoland@a0497350d871 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone59.0a1
first release with
nightly linux32
2a4362ce2d9f / 59.0a1 / 20180105220204 / files
nightly linux64
2a4362ce2d9f / 59.0a1 / 20180105220204 / files
nightly mac
2a4362ce2d9f / 59.0a1 / 20180105220204 / files
nightly win32
2a4362ce2d9f / 59.0a1 / 20180105220204 / files
nightly win64
2a4362ce2d9f / 59.0a1 / 20180105220204 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to mozilla-central r=merge a=merge
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1518,16 +1518,18 @@ pref("toolkit.telemetry.shutdownPingSend
 // Enables sending a duplicate of the first shutdown ping from the first session.
 pref("toolkit.telemetry.firstShutdownPing.enabled", true);
 // Enables sending the 'new-profile' ping on new profiles.
 pref("toolkit.telemetry.newProfilePing.enabled", true);
 // Enables sending 'update' pings on Firefox updates.
 pref("toolkit.telemetry.updatePing.enabled", true);
 // Enables sending 'bhr' pings when the browser hangs.
 pref("toolkit.telemetry.bhrPing.enabled", true);
+// Enables using Hybrid Content Telemetry from Mozilla privileged pages.
+pref("toolkit.telemetry.hybridContent.enabled", true);
 
 // Telemetry experiments settings.
 pref("experiments.enabled", true);
 pref("experiments.manifest.fetchIntervalSeconds", 86400);
 pref("experiments.manifest.uri", "https://telemetry-experiment.cdn.mozilla.net/manifest/v1/firefox/%VERSION%/%CHANNEL%");
 // Whether experiments are supported by the current application profile.
 pref("experiments.supported", true);
 
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1258,16 +1258,17 @@ var gBrowserInit = {
     TrackingProtection.init();
     CaptivePortalWatcher.init();
     ZoomUI.init(window);
 
     let mm = window.getGroupMessageManager("browsers");
     mm.loadFrameScript("chrome://browser/content/tab-content.js", true);
     mm.loadFrameScript("chrome://browser/content/content.js", true);
     mm.loadFrameScript("chrome://browser/content/content-UITour.js", true);
+    mm.loadFrameScript("chrome://global/content/content-HybridContentTelemetry.js", true);
     mm.loadFrameScript("chrome://global/content/manifestMessages.js", true);
 
     window.messageManager.addMessageListener("Browser:LoadURI", RedirectLoad);
 
     if (!gMultiProcessBrowser) {
       // There is a Content:Click message manually sent from content.
       Services.els.addSystemEventListener(gBrowser, "click", contentAreaClick, true);
     }
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -34,16 +34,17 @@ XPCOMUtils.defineLazyModuleGetters(this,
   ContextualIdentityService: "resource://gre/modules/ContextualIdentityService.jsm",
   DateTimePickerHelper: "resource://gre/modules/DateTimePickerHelper.jsm",
   DirectoryLinksProvider: "resource:///modules/DirectoryLinksProvider.jsm",
   ExtensionsUI: "resource:///modules/ExtensionsUI.jsm",
   Feeds: "resource:///modules/Feeds.jsm",
   FileUtils: "resource://gre/modules/FileUtils.jsm",
   FileSource: "resource://gre/modules/L10nRegistry.jsm",
   FormValidationHandler: "resource:///modules/FormValidationHandler.jsm",
+  HybridContentTelemetry: "resource://gre/modules/HybridContentTelemetry.jsm",
   Integration: "resource://gre/modules/Integration.jsm",
   L10nRegistry: "resource://gre/modules/L10nRegistry.jsm",
   LanguagePrompt: "resource://gre/modules/LanguagePrompt.jsm",
   LightweightThemeManager: "resource://gre/modules/LightweightThemeManager.jsm",
   LoginHelper: "resource://gre/modules/LoginHelper.jsm",
   LoginManagerParent: "resource://gre/modules/LoginManagerParent.jsm",
   NetUtil: "resource://gre/modules/NetUtil.jsm",
   NewTabUtils: "resource://gre/modules/NewTabUtils.jsm",
@@ -3067,8 +3068,15 @@ this.NSGetFactory = XPCOMUtils.generateN
 
 // Listen for UITour messages.
 // Do it here instead of the UITour module itself so that the UITour module is lazy loaded
 // when the first message is received.
 var globalMM = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
 globalMM.addMessageListener("UITour:onPageEvent", function(aMessage) {
   UITour.onPageEvent(aMessage, aMessage.data);
 });
+
+// Listen for HybridContentTelemetry messages.
+// Do it here instead of HybridContentTelemetry.init() so that
+// the module can be lazily loaded on the first message.
+globalMM.addMessageListener("HybridContentTelemetry:onTelemetryMessage", aMessage => {
+  HybridContentTelemetry.onTelemetryMessage(aMessage, aMessage.data);
+});
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -365,29 +365,37 @@ var gPrivacyPane = {
       bundlePrefs.getString("blockliststext"),
     ]);
     appendSearchKeywords("popupPolicyButton", [
       bundlePrefs.getString("popuppermissionstitle2"),
       bundlePrefs.getString("popuppermissionstext"),
     ]);
     appendSearchKeywords("notificationSettingsButton", [
       bundlePrefs.getString("notificationspermissionstitle2"),
-      bundlePrefs.getString("notificationspermissionstext5"),
+      bundlePrefs.getString("notificationspermissionstext6"),
+      bundlePrefs.getString("notificationspermissionsdisablelabel"),
+      bundlePrefs.getString("notificationspermissionsdisabledescription"),
     ]);
     appendSearchKeywords("locationSettingsButton", [
       bundlePrefs.getString("locationpermissionstitle"),
-      bundlePrefs.getString("locationpermissionstext"),
+      bundlePrefs.getString("locationpermissionstext2"),
+      bundlePrefs.getString("locationpermissionsdisablelabel"),
+      bundlePrefs.getString("locationpermissionsdisabledescription"),
     ]);
     appendSearchKeywords("cameraSettingsButton", [
       bundlePrefs.getString("camerapermissionstitle"),
-      bundlePrefs.getString("camerapermissionstext"),
+      bundlePrefs.getString("camerapermissionstext2"),
+      bundlePrefs.getString("camerapermissionsdisablelabel"),
+      bundlePrefs.getString("camerapermissionsdisabledescription"),
     ]);
     appendSearchKeywords("microphoneSettingsButton", [
       bundlePrefs.getString("microphonepermissionstitle"),
-      bundlePrefs.getString("microphonepermissionstext"),
+      bundlePrefs.getString("microphonepermissionstext2"),
+      bundlePrefs.getString("microphonepermissionsdisablelabel"),
+      bundlePrefs.getString("microphonepermissionsdisabledescription"),
     ]);
     appendSearchKeywords("addonExceptions", [
       bundlePrefs.getString("addons_permissions_title2"),
       bundlePrefs.getString("addonspermissionstext"),
     ]);
     appendSearchKeywords("viewSecurityDevicesButton", [
       pkiBundle.getString("enable_fips"),
     ]);
@@ -889,65 +897,73 @@ var gPrivacyPane = {
   /**
    * Displays the location exceptions dialog where specific site location
    * preferences can be set.
    */
   showLocationExceptions() {
     let bundlePreferences = document.getElementById("bundlePreferences");
     let params = { permissionType: "geo" };
     params.windowTitle = bundlePreferences.getString("locationpermissionstitle");
-    params.introText = bundlePreferences.getString("locationpermissionstext");
+    params.introText = bundlePreferences.getString("locationpermissionstext2");
+    params.disablePermissionsLabel = bundlePreferences.getString("locationpermissionsdisablelabel");
+    params.disablePermissionsDescription = bundlePreferences.getString("locationpermissionsdisabledescription");
 
     gSubDialog.open("chrome://browser/content/preferences/sitePermissions.xul",
       "resizable=yes", params);
   },
 
   // CAMERA
 
   /**
    * Displays the camera exceptions dialog where specific site camera
    * preferences can be set.
    */
   showCameraExceptions() {
     let bundlePreferences = document.getElementById("bundlePreferences");
     let params = { permissionType: "camera" };
     params.windowTitle = bundlePreferences.getString("camerapermissionstitle");
-    params.introText = bundlePreferences.getString("camerapermissionstext");
+    params.introText = bundlePreferences.getString("camerapermissionstext2");
+    params.disablePermissionsLabel = bundlePreferences.getString("camerapermissionsdisablelabel");
+    params.disablePermissionsDescription = bundlePreferences.getString("camerapermissionsdisabledescription");
 
     gSubDialog.open("chrome://browser/content/preferences/sitePermissions.xul",
       "resizable=yes", params);
   },
 
   // MICROPHONE
 
   /**
    * Displays the microphone exceptions dialog where specific site microphone
    * preferences can be set.
    */
   showMicrophoneExceptions() {
     let bundlePreferences = document.getElementById("bundlePreferences");
     let params = { permissionType: "microphone" };
     params.windowTitle = bundlePreferences.getString("microphonepermissionstitle");
-    params.introText = bundlePreferences.getString("microphonepermissionstext");
+    params.introText = bundlePreferences.getString("microphonepermissionstext2");
+    params.disablePermissionsLabel = bundlePreferences.getString("microphonepermissionsdisablelabel");
+    params.disablePermissionsDescription = bundlePreferences.getString("microphonepermissionsdisabledescription");
 
     gSubDialog.open("chrome://browser/content/preferences/sitePermissions.xul",
       "resizable=yes", params);
   },
 
   // NOTIFICATIONS
 
   /**
    * Displays the notifications exceptions dialog where specific site notification
    * preferences can be set.
    */
   showNotificationExceptions() {
     let bundlePreferences = document.getElementById("bundlePreferences");
     let params = { permissionType: "desktop-notification" };
     params.windowTitle = bundlePreferences.getString("notificationspermissionstitle2");
-    params.introText = bundlePreferences.getString("notificationspermissionstext5");
+    params.introText = bundlePreferences.getString("notificationspermissionstext6");
+    params.disablePermissionsLabel = bundlePreferences.getString("notificationspermissionsdisablelabel");
+    params.disablePermissionsDescription = bundlePreferences.getString("notificationspermissionsdisabledescription");
 
     gSubDialog.open("chrome://browser/content/preferences/sitePermissions.xul",
       "resizable=yes", params);
 
     try {
       Services.telemetry
         .getHistogramById("WEB_NOTIFICATION_EXCEPTIONS_OPENED").add();
     } catch (e) { }
--- a/browser/components/preferences/in-content/sync.xul
+++ b/browser/components/preferences/in-content/sync.xul
@@ -101,18 +101,18 @@
                   <image class="fxaLoginRejectedWarning"/>
                   <description flex="1">
                     &signedInUnverified.beforename.label;
                     <label class="fxaEmailAddress"/>
                     &signedInUnverified.aftername.label;
                   </description>
                 </hbox>
                 <hbox class="fxaAccountBoxButtons">
-                  <button id="verifyFxaAccount" label="&verify.label;" accesskey="&verify.accesskey;"></button>
-                  <button id="unverifiedUnlinkFxaAccount" label="&forget.label;" accesskey="&forget.accesskey;"></button>
+                  <button id="verifyFxaAccount" label="&resendVerification.label;" accesskey="&resendVerification.accesskey;"></button>
+                  <button id="unverifiedUnlinkFxaAccount" label="&cancelSetup.label;" accesskey="&cancelSetup.accesskey;"></button>
                 </hbox>
               </vbox>
             </hbox>
 
             <!-- logged in locally but server rejected credentials -->
             <hbox id="fxaLoginRejected">
               <vbox>
                 <image class="fxaProfileImage"/>
@@ -123,17 +123,17 @@
                   <description flex="1">
                     &signedInLoginFailure.beforename.label;
                     <label class="fxaEmailAddress"/>
                     &signedInLoginFailure.aftername.label;
                   </description>
                 </hbox>
                 <hbox class="fxaAccountBoxButtons">
                   <button id="rejectReSignIn" label="&signIn.label;" accesskey="&signIn.accesskey;"></button>
-                  <button id="rejectUnlinkFxaAccount" label="&forget.label;" accesskey="&forget.accesskey;"></button>
+                  <button id="rejectUnlinkFxaAccount" label="&cancelSetup.label;" accesskey="&cancelSetup.accesskey;"></button>
                 </hbox>
               </vbox>
             </hbox>
           </deck>
         </groupbox>
         <groupbox id="syncOptions">
           <caption><label>&signedIn.settings.label;</label></caption>
           <description>&signedIn.settings.description;</description>
--- a/browser/components/preferences/in-content/tests/browser_permissions_dialog.js
+++ b/browser/components/preferences/in-content/tests/browser_permissions_dialog.js
@@ -266,11 +266,76 @@ add_task(async function onPermissionsSor
   Assert.equal(richlistbox.getItemAtIndex(1).getAttribute("origin"), "http://www.example.com");
 
   SitePermissions.remove(URI, "desktop-notification");
   SitePermissions.remove(u, "desktop-notification");
 
   doc.getElementById("cancel").click();
 });
 
+add_task(async function onPermissionDisable() {
+  // Enable desktop-notification permission prompts.
+  Services.prefs.setIntPref("permissions.default.desktop-notification", SitePermissions.UNKNOWN);
+
+  await openPermissionsDialog();
+  let doc = sitePermissionsDialog.document;
+
+  // Check if the enabled state is reflected in the checkbox.
+  let checkbox = doc.getElementById("permissionsDisableCheckbox");
+  Assert.equal(checkbox.checked, false);
+
+  // Disable permission and click on "Cancel".
+  checkbox.checked = true;
+  doc.getElementById("cancel").click();
+
+  // Check that the permission is not disabled yet.
+  let perm = Services.prefs.getIntPref("permissions.default.desktop-notification");
+  Assert.equal(perm, SitePermissions.UNKNOWN);
+
+  // Open the dialog once again.
+  await openPermissionsDialog();
+  doc = sitePermissionsDialog.document;
+
+  // Disable permission and save changes.
+  checkbox = doc.getElementById("permissionsDisableCheckbox");
+  checkbox.checked = true;
+  doc.getElementById("btnApplyChanges").click();
+
+  // Check if the permission is now disabled.
+  perm = Services.prefs.getIntPref("permissions.default.desktop-notification");
+  Assert.equal(perm, SitePermissions.BLOCK);
+
+  // Open the dialog once again and check if the disabled state is still reflected in the checkbox.
+  await openPermissionsDialog();
+  doc = sitePermissionsDialog.document;
+  checkbox = doc.getElementById("permissionsDisableCheckbox");
+  Assert.equal(checkbox.checked, true);
+
+  // Close the dialog.
+  doc.getElementById("cancel").click();
+});
+
+add_task(async function checkDefaultPermissionState() {
+  // Set default permission state to ALLOW.
+  Services.prefs.setIntPref("permissions.default.desktop-notification", SitePermissions.ALLOW);
+
+  await openPermissionsDialog();
+  let doc = sitePermissionsDialog.document;
+
+  // Check if the enabled state is reflected in the checkbox.
+  let checkbox = doc.getElementById("permissionsDisableCheckbox");
+  Assert.equal(checkbox.checked, false);
+
+  // Check the checkbox and then uncheck it.
+  checkbox.checked = true;
+  checkbox.checked = false;
+
+  // Save changes.
+  doc.getElementById("btnApplyChanges").click();
+
+  // Check if the default permission state is retained (and not automatically set to SitePermissions.UNKNOWN).
+  let state = Services.prefs.getIntPref("permissions.default.desktop-notification");
+  Assert.equal(state, SitePermissions.ALLOW);
+});
+
 add_task(async function removeTab() {
   gBrowser.removeCurrentTab();
 });
--- a/browser/components/preferences/in-content/tests/browser_search_within_preferences_2.js
+++ b/browser/components/preferences/in-content/tests/browser_search_within_preferences_2.js
@@ -9,18 +9,18 @@
  * Enabling searching functionality. Will display search bar from this testcase forward.
  */
 add_task(async function() {
   await SpecialPowers.pushPrefEnv({"set": [["browser.preferences.search", true]]});
 });
 
 /**
  * Test that we only search the selected child of a XUL deck.
- * When we search "Forget this Email",
- * it should not show the "Forget this Email" button if the Firefox account is not logged in yet.
+ * When we search "Cancel Setup",
+ * it should not show the "Cancel Setup" button if the Firefox account is not logged in yet.
  */
 add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
 
   // Ensure the "Sign Up" button in the hidden child of the <xul:deck>
   // is selected and displayed on the screen.
   let weavePrefsDeck = gBrowser.contentDocument.getElementById("weavePrefsDeck");
   is(weavePrefsDeck.selectedIndex, 0, "Should select the #noFxaAccount child node");
@@ -45,23 +45,23 @@ add_task(async function() {
     if (child.id == "header-searchResults" ||
         child.id == "weavePrefsDeck") {
       is_element_visible(child, "Should be in search results");
     } else if (child.id) {
       is_element_hidden(child, "Should not be in search results");
     }
   }
 
-  // Ensure the "Forget this email" button exists in the hidden child of the <xul:deck>.
+  // Ensure the "Cancel Setup" button exists in the hidden child of the <xul:deck>.
   let unlinkFxaAccount = weavePrefsDeck.childNodes[1].querySelector("#unverifiedUnlinkFxaAccount");
-  is(unlinkFxaAccount.label, "Forget this email", "The Forget this email button should exist");
+  is(unlinkFxaAccount.label, "Cancel Setup", "The Cancel Setup button should exist");
 
   // Performs search.
   searchInput.focus();
-  query = "Forget this Email";
+  query = "Cancel Setup";
   searchCompletedPromise = BrowserTestUtils.waitForEvent(
       gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == query);
   EventUtils.sendString(query);
   await searchCompletedPromise;
 
   let noResultsEl = gBrowser.contentDocument.querySelector(".no-results-message");
   is_element_visible(noResultsEl, "Should be reporting no results");
 
--- a/browser/components/preferences/sitePermissions.css
+++ b/browser/components/preferences/sitePermissions.css
@@ -16,8 +16,24 @@
 #permissionsBox > richlistitem {
   min-height: 35px;
 }
 
 .website-status {
   margin: 1px;
   margin-inline-end: 5px;
 }
+
+#permissionsDisableLabel {
+  padding-top: 14px;
+  font-weight: bold;
+}
+
+#permissionsDisableDescription {
+  margin-inline-start: 37px;
+  color: #737373;
+  line-height: 110%;
+}
+
+#permissionsDisableCheckbox {
+  margin-inline-start: 4px;
+  padding-top: 10px;
+}
--- a/browser/components/preferences/sitePermissions.js
+++ b/browser/components/preferences/sitePermissions.js
@@ -22,16 +22,19 @@ var gSitePermissionsManager = {
   _permissions: new Map(),
   _permissionsToChange: new Map(),
   _permissionsToDelete: new Map(),
   _list: null,
   _bundle: null,
   _removeButton: null,
   _removeAllButton: null,
   _searchBox: null,
+  _checkbox: null,
+  _currentDefaultPermissionsState: null,
+  _defaultPermissionStatePrefName: null,
 
   onLoad() {
     let params = window.arguments[0];
     this.init(params);
   },
 
   init(params) {
     if (!this._isObserving) {
@@ -40,24 +43,48 @@ var gSitePermissionsManager = {
     }
 
     this._bundle = document.getElementById("bundlePreferences");
     this._type = params.permissionType;
     this._list = document.getElementById("permissionsBox");
     this._removeButton = document.getElementById("removePermission");
     this._removeAllButton = document.getElementById("removeAllPermissions");
     this._searchBox = document.getElementById("searchBox");
+    this._checkbox = document.getElementById("permissionsDisableCheckbox");
 
     let permissionsText = document.getElementById("permissionsText");
     while (permissionsText.hasChildNodes())
       permissionsText.firstChild.remove();
     permissionsText.appendChild(document.createTextNode(params.introText));
 
+    let permissionsDisableLabel = document.getElementById("permissionsDisableLabel");
+    permissionsDisableLabel.value = params.disablePermissionsLabel;
+
+    let permissionsDisableDescription = document.getElementById("permissionsDisableDescription");
+    permissionsDisableDescription.appendChild(document.createTextNode(params.disablePermissionsDescription));
+
     document.title = params.windowTitle;
 
+    // Initialize the checkbox state.
+    this._defaultPermissionStatePrefName = "permissions.default." + this._type;
+    let pref = Services.prefs.getPrefType(this._defaultPermissionStatePrefName);
+    if (pref != Services.prefs.PREF_INVALID) {
+      this._currentDefaultPermissionsState = Services.prefs.getIntPref(this._defaultPermissionStatePrefName);
+    }
+
+    if (this._currentDefaultPermissionsState === null) {
+      this._checkbox.setAttribute("hidden", true);
+      permissionsDisableLabel.setAttribute("hidden", true);
+      permissionsDisableDescription.setAttribute("hidden", true);
+    } else if (this._currentDefaultPermissionsState == SitePermissions.BLOCK) {
+      this._checkbox.checked = true;
+    } else {
+      this._checkbox.checked = false;
+    }
+
     this._loadPermissions();
     this.buildPermissionsList();
 
     this._searchBox.focus();
   },
 
   uninit() {
     if (this._isObserving) {
@@ -259,16 +286,23 @@ var gSitePermissionsManager = {
       let uri = Services.io.newURI(p.origin);
       SitePermissions.set(uri, p.type, p.capability);
     }
 
     for (let p of this._permissionsToDelete.values()) {
       let uri = Services.io.newURI(p.origin);
       SitePermissions.remove(uri, p.type);
     }
+
+    if (this._checkbox.checked) {
+      Services.prefs.setIntPref(this._defaultPermissionStatePrefName, SitePermissions.BLOCK);
+    } else if (this._currentDefaultPermissionsState == SitePermissions.BLOCK) {
+      Services.prefs.setIntPref(this._defaultPermissionStatePrefName, SitePermissions.UNKNOWN);
+    }
+
     window.close();
   },
 
   buildPermissionsList(sortCol) {
     // Clear old entries.
     let oldItems = this._list.querySelectorAll("richlistitem");
     for (let item of oldItems) {
       item.remove();
--- a/browser/components/preferences/sitePermissions.xul
+++ b/browser/components/preferences/sitePermissions.xul
@@ -57,16 +57,24 @@
               icon="remove" label="&removepermission2.label;"
               oncommand="gSitePermissionsManager.onPermissionDelete();"/>
       <button id="removeAllPermissions"
               icon="clear" label="&removeallpermissions2.label;"
               accesskey="&removeallpermissions2.accesskey;"
               oncommand="gSitePermissionsManager.onAllPermissionsDelete();"/>
     </hbox>
     <spacer flex="1"/>
+    <vbox align="start">
+      <hbox align="start">
+        <checkbox id="permissionsDisableCheckbox"/>
+        <label for="permissionsDisableCheckbox" id="permissionsDisableLabel"/>
+      </hbox>
+      <description id="permissionsDisableDescription"/>
+    </vbox>
+    <spacer flex="1"/>
     <hbox class="actionButtons" align="right" flex="1">
       <button oncommand="close();" icon="close" id="cancel"
               label="&button.cancel.label;" accesskey="&button.cancel.accesskey;" />
       <button id="btnApplyChanges" oncommand="gSitePermissionsManager.onApplyChanges();" icon="save"
               label="&button.ok.label;" accesskey="&button.ok.accesskey;"/>
     </hbox>
   </vbox>
 </window>
--- a/browser/locales/en-US/chrome/browser/preferences/preferences.properties
+++ b/browser/locales/en-US/chrome/browser/preferences/preferences.properties
@@ -23,24 +23,32 @@ acceptVeryLargeMinimumFont=Keep my chang
 trackingprotectionpermissionstext2=You have disabled Tracking Protection on these websites.
 trackingprotectionpermissionstitle=Exceptions - Tracking Protection
 cookiepermissionstext=You can specify which websites are always or never allowed to use cookies.  Type the exact address of the site you want to manage and then click Block, Allow for Session, or Allow.
 cookiepermissionstitle=Exceptions - Cookies
 addonspermissionstext=You can specify which websites are allowed to install add-ons. Type the exact address of the site you want to allow and then click Allow.
 addons_permissions_title2=Allowed Websites - Add-ons Installation
 popuppermissionstext=You can specify which websites are allowed to open pop-up windows. Type the exact address of the site you want to allow and then click Allow.
 popuppermissionstitle2=Allowed Websites - Pop-ups
-notificationspermissionstext5=The following websites have requested to send you notifications. You can specify which websites are allowed to send you notifications.
+notificationspermissionstext6=The following websites have requested to send you notifications. You can specify which websites are allowed to send you notifications. You can also block new requests asking to allow notifications.
 notificationspermissionstitle2=Settings - Notification Permissions
-locationpermissionstext=The following websites have requested to access your location. You can specify which websites are allowed to access your location.
+notificationspermissionsdisablelabel=Block new requests asking to allow notifications
+notificationspermissionsdisabledescription=This will prevent any websites not listed above from requesting permission to send notifications. Blocking notifications may break some website features.
+locationpermissionstext2=The following websites have requested to access your location. You can specify which websites are allowed to access your location. You can also block new requests asking to access your location.
 locationpermissionstitle=Settings - Location Permissions
-camerapermissionstext=The following websites have requested to access your camera. You can specify which websites are allowed to access your camera.
+locationpermissionsdisablelabel=Block new requests asking to access your location
+locationpermissionsdisabledescription=This will prevent any websites not listed above from requesting permission to access your location. Blocking access to your location may break some website features.
+camerapermissionstext2=The following websites have requested to access your camera. You can specify which websites are allowed to access your camera. You can also block new requests asking to access your camera.
 camerapermissionstitle=Settings - Camera Permissions
-microphonepermissionstext=The following websites have requested to access your microphone. You can specify which websites are allowed to access your microphone.
+camerapermissionsdisablelabel=Block new requests asking to access your camera
+camerapermissionsdisabledescription=This will prevent any websites not listed above from requesting permission to access your camera. Blocking access to your camera may break some website features.
+microphonepermissionstext2=The following websites have requested to access your microphone. You can specify which websites are allowed to access your microphone. You can also block new requests asking to access your microphone.
 microphonepermissionstitle=Settings - Microphone Permissions
+microphonepermissionsdisablelabel=Block new requests asking to access your microphone
+microphonepermissionsdisabledescription=This will prevent any websites not listed above from requesting permission to access your microphone. Blocking access to your microphone may break some website features.
 invalidURI=Please enter a valid hostname
 invalidURITitle=Invalid Hostname Entered
 savedLoginsExceptions_title=Exceptions - Saved Logins
 savedLoginsExceptions_desc3=Logins for the following websites will not be saved
 
 # LOCALIZATION NOTE(pauseNotifications.label): %S is replaced with the
 # brandShortName of the application.
 pauseNotifications.label=Pause notifications until %S restarts
--- a/browser/locales/en-US/chrome/browser/preferences/sync.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/sync.dtd
@@ -57,28 +57,28 @@ both, to better adapt this sentence to t
 <!-- LOCALIZATION NOTE (signedInLoginFailure.beforename.label,
 signedInLoginFailure.aftername.label): these two string are used respectively
 before and after the account email address. Localizers can use one of them, or
 both, to better adapt this sentence to their language.
 -->
 <!ENTITY signedInLoginFailure.beforename.label "Please sign in to reconnect">
 <!ENTITY signedInLoginFailure.aftername.label "">
 
-<!ENTITY notSignedIn.label           "You are not signed in.">
-<!ENTITY signIn.label                "Sign in">
-<!ENTITY signIn.accesskey            "g">
-<!ENTITY profilePicture.tooltip      "Change profile picture">
-<!ENTITY verifiedManage.label        "Manage account">
-<!ENTITY verifiedManage.accesskey    "o">
-<!ENTITY disconnect3.label           "Disconnect…">
-<!ENTITY disconnect3.accesskey       "D">
-<!ENTITY verify.label                "Verify email">
-<!ENTITY verify.accesskey            "V">
-<!ENTITY forget.label                "Forget this email">
-<!ENTITY forget.accesskey            "F">
+<!ENTITY notSignedIn.label            "You are not signed in.">
+<!ENTITY signIn.label                 "Sign in">
+<!ENTITY signIn.accesskey             "g">
+<!ENTITY profilePicture.tooltip       "Change profile picture">
+<!ENTITY verifiedManage.label         "Manage account">
+<!ENTITY verifiedManage.accesskey     "o">
+<!ENTITY disconnect3.label            "Disconnect…">
+<!ENTITY disconnect3.accesskey        "D">
+<!ENTITY resendVerification.label     "Resend Verification">
+<!ENTITY resendVerification.accesskey "d">
+<!ENTITY cancelSetup.label            "Cancel Setup">
+<!ENTITY cancelSetup.accesskey        "p">
 
 <!ENTITY signedOut.caption            "Take Your Web With You">
 <!ENTITY signedOut.description        "Synchronize your bookmarks, history, tabs, passwords, add-ons, and preferences across all your devices.">
 <!ENTITY signedOut.accountBox.title   "Connect with a &syncBrand.fxAccount.label;">
 <!ENTITY signedOut.accountBox.create2 "Don’t have an account? Get started">
 <!ENTITY signedOut.accountBox.create2.accesskey "C">
 <!ENTITY signedOut.accountBox.signin2 "Sign In…">
 <!ENTITY signedOut.accountBox.signin2.accesskey "I">
--- a/devtools/client/netmonitor/src/assets/styles/NetworkDetailsPanel.css
+++ b/devtools/client/netmonitor/src/assets/styles/NetworkDetailsPanel.css
@@ -153,16 +153,17 @@
 
 /* If there is a source editor shows up in the last row of TreeView,
  * it should occupy the available vertical space.
  */
 .network-monitor .tree-container .treeTable .editor-row-container,
 .network-monitor .tree-container .treeTable tr:last-child td[colspan="2"] {
   display: block;
   height: 100%;
+  flex: 1;
 }
 
 .network-monitor .source-editor-mount {
   width: 100%;
   height: 100%;
 }
 
 .network-monitor .headers-summary-label,
--- a/devtools/client/netmonitor/src/components/SourceEditor.js
+++ b/devtools/client/netmonitor/src/components/SourceEditor.js
@@ -3,66 +3,81 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { Component } = require("devtools/client/shared/vendor/react");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const Editor = require("devtools/client/sourceeditor/editor");
-const { SOURCE_EDITOR_SYNTAX_HIGHLIGHT_MAX_SIZE } = require("../constants");
 
 const { div } = dom;
 
 /**
  * CodeMirror editor as a React component
  */
 class SourceEditor extends Component {
   static get propTypes() {
     return {
-      // Source editor syntax hightligh mode, which is a mime type defined in CodeMirror
+      // Source editor syntax highlight mode, which is a mime type defined in CodeMirror
       mode: PropTypes.string,
       // Source editor content
       text: PropTypes.string,
     };
   }
 
   componentDidMount() {
     const { mode, text } = this.props;
 
     this.editor = new Editor({
       lineNumbers: true,
       lineWrapping: false,
-      mode: text.length < SOURCE_EDITOR_SYNTAX_HIGHLIGHT_MAX_SIZE ? mode : null,
+      mode: null, // Disable auto syntax detection, but then we set mode asynchronously
       readOnly: true,
       theme: "mozilla",
       value: text,
     });
 
-    // Delay to CodeMirror initialization content to prevent UI freezed
+    // Delay to CodeMirror initialization content to prevent UI freezing
     this.editorTimeout = setTimeout(() => {
       this.editor.appendToLocalElement(this.refs.editorElement);
+      // CodeMirror's setMode() (syntax highlight) is the performance bottleneck when
+      // processing large content, so we enable it asynchronously within the setTimeout
+      // to avoid UI blocking. (rendering source code -> drawing syntax highlight)
+      this.editorSetModeTimeout = setTimeout(() => {
+        this.editor.setMode(mode);
+      });
     });
   }
 
+  shouldComponentUpdate(nextProps) {
+    return nextProps.mode !== this.props.mode || nextProps.text !== this.props.text;
+  }
+
   componentDidUpdate(prevProps) {
     const { mode, text } = this.props;
 
-    if (prevProps.mode !== mode &&
-        text.length < SOURCE_EDITOR_SYNTAX_HIGHLIGHT_MAX_SIZE) {
-      this.editor.setMode(mode);
-    }
+    if (prevProps.text !== text) {
+      // Reset the existed 'mode' attribute in order to make setText() process faster
+      // to prevent drawing unnecessary syntax highlight.
+      this.editor.setMode(null);
+      this.editor.setText(text);
 
-    if (prevProps.text !== text) {
-      this.editor.setText(text);
+      // CodeMirror's setMode() (syntax highlight) is the performance bottleneck when
+      // processing large content, so we enable it asynchronously within the setTimeout
+      // to avoid UI blocking. (rendering source code -> drawing syntax highlight)
+      this.editorSetModeTimeout = setTimeout(() => {
+        this.editor.setMode(mode);
+      });
     }
   }
 
   componentWillUnmount() {
     clearTimeout(this.editorTimeout);
+    clearTimeout(this.editorSetModeTimeout);
     this.editor.destroy();
   }
 
   render() {
     return (
       div({
         ref: "editorElement",
         className: "source-editor-mount devtools-monospace",
--- a/devtools/client/netmonitor/src/constants.js
+++ b/devtools/client/netmonitor/src/constants.js
@@ -312,15 +312,14 @@ const general = {
   ACTIVITY_TYPE,
   EVENTS,
   FILTER_SEARCH_DELAY: 200,
   UPDATE_PROPS,
   HEADERS,
   RESPONSE_HEADERS,
   FILTER_FLAGS,
   FILTER_TAGS,
-  SOURCE_EDITOR_SYNTAX_HIGHLIGHT_MAX_SIZE: 51200, // 50 KB in bytes
   REQUESTS_WATERFALL,
   PANELS,
 };
 
 // flatten constants
 module.exports = Object.assign({}, general, actionTypes);
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_websocket.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_websocket.js
@@ -12,12 +12,12 @@ const TEST_URI = "http://example.com/bro
 const TEST_URI2 = "data:text/html;charset=utf-8,Web Console test for " +
                   "bug 603750: Web Socket errors";
 
 add_task(async function () {
   const hud = await openNewTabAndConsole(TEST_URI2);
 
   BrowserTestUtils.loadURI(gBrowser.selectedBrowser, TEST_URI);
 
-  await waitFor(() => findMessage(hud, "ws://0.0.0.0:81"));
-  await waitFor(() => findMessage(hud, "ws://0.0.0.0:82"));
+  await waitFor(() => findMessage(hud, "ws://0.0.0.0:81"), null, 100);
+  await waitFor(() => findMessage(hud, "ws://0.0.0.0:82"), null, 100);
   ok(true, "WebSocket error messages are displayed in the console");
 });
--- a/devtools/client/webide/themes/addons.css
+++ b/devtools/client/webide/themes/addons.css
@@ -12,20 +12,16 @@ button {
   color: #737980;
   border: 1px solid rgba(23,50,77,.4);
   border-radius: 5px;
   background-color: #f1f1f1;
   background-image: linear-gradient(#fff, rgba(255,255,255,.1));
   box-shadow: 0 1px 1px 0 #fff, inset 0 2px 2px 0 #fff;
   text-shadow: 0 1px 1px #fefffe;
   -moz-appearance: none;
-  -moz-border-top-colors: none !important;
-  -moz-border-right-colors: none !important;
-  -moz-border-bottom-colors: none !important;
-  -moz-border-left-colors: none !important;
 }
 
 button:hover {
   background-image: linear-gradient(#fff, rgba(255,255,255,.6));
   cursor: pointer;
 }
 
 button:hover:active {
--- a/devtools/server/actors/object.js
+++ b/devtools/server/actors/object.js
@@ -2255,17 +2255,17 @@ var stringifiers = {
 function makeDebuggeeValueIfNeeded(obj, value) {
   if (value && (typeof value == "object" || typeof value == "function")) {
     return obj.makeDebuggeeValue(value);
   }
   return value;
 }
 
 /**
- * Creates an actor for the specied "very long" string. "Very long" is specified
+ * Creates an actor for the specified "very long" string. "Very long" is specified
  * at the server's discretion.
  *
  * @param string String
  *        The string.
  */
 function LongStringActor(string) {
   this.string = string;
   this.stringLength = string.length;
@@ -2311,17 +2311,17 @@ LongStringActor.prototype = {
     };
   },
 
   /**
    * Handle a request to release this LongStringActor instance.
    */
   onRelease: function () {
     // TODO: also check if registeredPool === threadActor.threadLifetimePool
-    // when the web console moves aray from manually releasing pause-scoped
+    // when the web console moves away from manually releasing pause-scoped
     // actors.
     this._releaseActor();
     this.registeredPool.removeActor(this);
     return {};
   },
 
   _releaseActor: function () {
     if (this.registeredPool && this.registeredPool.longStringActors) {
@@ -2331,17 +2331,80 @@ LongStringActor.prototype = {
 };
 
 LongStringActor.prototype.requestTypes = {
   "substring": LongStringActor.prototype.onSubstring,
   "release": LongStringActor.prototype.onRelease
 };
 
 /**
- * Creates an actor for the specied ArrayBuffer.
+ * Creates an actor for the specified symbol.
+ *
+ * @param symbol Symbol
+ *        The symbol.
+ */
+function SymbolActor(symbol) {
+  this.symbol = symbol;
+}
+
+SymbolActor.prototype = {
+  actorPrefix: "symbol",
+
+  rawValue: function () {
+    return this.symbol;
+  },
+
+  destroy: function () {
+    // Because symbolActors is not a weak map, we won't automatically leave
+    // it so we need to manually leave on destroy so that we don't leak
+    // memory.
+    this._releaseActor();
+  },
+
+  /**
+   * Returns a grip for this actor for returning in a protocol message.
+   */
+  grip: function () {
+    let form = {
+      type: "symbol",
+      actor: this.actorID,
+    };
+    let name = getSymbolName(this.symbol);
+    if (name !== undefined) {
+      // Create a grip for the name because it might be a longString.
+      form.name = createValueGrip(name, this.registeredPool);
+    }
+    return form;
+  },
+
+  /**
+   * Handle a request to release this SymbolActor instance.
+   */
+  onRelease: function () {
+    // TODO: also check if registeredPool === threadActor.threadLifetimePool
+    // when the web console moves away from manually releasing pause-scoped
+    // actors.
+    this._releaseActor();
+    this.registeredPool.removeActor(this);
+    return {};
+  },
+
+  _releaseActor: function () {
+    if (this.registeredPool && this.registeredPool.symbolActors) {
+      delete this.registeredPool.symbolActors[this.symbol];
+    }
+  }
+};
+
+SymbolActor.prototype.requestTypes = {
+  "release": SymbolActor.prototype.onRelease
+};
+
+/**
+ * Creates an actor for the specified ArrayBuffer.
  *
  * @param buffer ArrayBuffer
  *        The buffer.
  */
 function ArrayBufferActor(buffer) {
   this.buffer = buffer;
   this.bufferLength = buffer.byteLength;
 }
@@ -2428,24 +2491,17 @@ function createValueGrip(value, pool, ma
           optimizedOut: value.optimizedOut,
           uninitialized: value.uninitialized,
           missingArguments: value.missingArguments
         };
       }
       return makeObjectGrip(value, pool);
 
     case "symbol":
-      let form = {
-        type: "symbol"
-      };
-      let name = getSymbolName(value);
-      if (name !== undefined) {
-        form.name = createValueGrip(name, pool, makeObjectGrip);
-      }
-      return form;
+      return symbolGrip(value, pool);
 
     default:
       assert(false, "Failed to provide a grip for: " + value);
       return null;
   }
 }
 
 const symbolProtoToString = Symbol.prototype.toString;
@@ -2485,16 +2541,39 @@ function longStringGrip(str, pool) {
 
   let actor = new LongStringActor(str);
   pool.addActor(actor);
   pool.longStringActors[str] = actor;
   return actor.grip();
 }
 
 /**
+ * Create a grip for the given symbol.
+ *
+ * @param sym Symbol
+ *        The symbol we are creating a grip for.
+ * @param pool ActorPool
+ *        The actor pool where the new actor will be added.
+ */
+function symbolGrip(sym, pool) {
+  if (!pool.symbolActors) {
+    pool.symbolActors = Object.create(null);
+  }
+
+  if (sym in pool.symbolActors) {
+    return pool.symbolActors[sym].grip();
+  }
+
+  let actor = new SymbolActor(sym);
+  pool.addActor(actor);
+  pool.symbolActors[sym] = actor;
+  return actor.grip();
+}
+
+/**
  * Create a grip for the given ArrayBuffer.
  *
  * @param buffer ArrayBuffer
  *        The ArrayBuffer we are creating a grip for.
  * @param pool ActorPool
  *        The actor pool where the new actor will be added.
  */
 function arrayBufferGrip(buffer, pool) {
@@ -2589,12 +2668,13 @@ function isArrayIndex(str) {
   return num + "" === str &&
     // Array indices cannot attain the maximum Uint32 value.
     num != -1 >>> 0;
 }
 
 exports.ObjectActor = ObjectActor;
 exports.PropertyIteratorActor = PropertyIteratorActor;
 exports.LongStringActor = LongStringActor;
+exports.SymbolActor = SymbolActor;
 exports.createValueGrip = createValueGrip;
 exports.stringIsLong = stringIsLong;
 exports.longStringGrip = longStringGrip;
 exports.arrayBufferGrip = arrayBufferGrip;
--- a/devtools/server/actors/root.js
+++ b/devtools/server/actors/root.js
@@ -163,19 +163,16 @@ RootActor.prototype = {
     noBlackBoxing: false,
     noPrettyPrinting: false,
     // Whether the page style actor implements the getUsedFontFaces method
     // that returns the font faces used on a node
     getUsedFontFaces: true,
     // Trait added in Gecko 38, indicating that all features necessary for
     // grabbing allocations from the MemoryActor are available for the performance tool
     memoryActorAllocations: true,
-    // Added in Gecko 40, indicating that the backend isn't stupid about
-    // sending resumption packets on tab navigation.
-    noNeedToFakeResumptionOnNavigation: true,
     // Added in Firefox 40. Indicates that the backend supports registering custom
     // commands through the WebConsoleCommands API.
     webConsoleCommands: true,
     // Whether root actor exposes tab actors and access to any window.
     // If allowChromeProcess is true, you can:
     // * get a ChromeActor instance to debug chrome and any non-content
     //   resource via getProcess requests
     // * get a WindowActor instance to debug windows which could be chrome,
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/unit/test_symbolactor.js
@@ -0,0 +1,49 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { SymbolActor } = require("devtools/server/actors/object");
+
+function run_test() {
+  test_SA_destroy();
+  test_SA_grip();
+  test_SA_raw();
+}
+
+const SYMBOL_NAME = "abc";
+const TEST_SYMBOL = Symbol(SYMBOL_NAME);
+
+function makeMockSymbolActor() {
+  let symbol = TEST_SYMBOL;
+  let actor = new SymbolActor(symbol);
+  actor.actorID = "symbol1";
+  actor.registeredPool = {
+    symbolActors: {
+      [symbol]: actor
+    }
+  };
+  return actor;
+}
+
+function test_SA_destroy() {
+  let actor = makeMockSymbolActor();
+  strictEqual(actor.registeredPool.symbolActors[TEST_SYMBOL], actor);
+
+  actor.destroy();
+  strictEqual(TEST_SYMBOL in actor.registeredPool.symbolActors, false);
+}
+
+function test_SA_grip() {
+  let actor = makeMockSymbolActor();
+  let grip = actor.grip();
+  strictEqual(grip.type, "symbol");
+  strictEqual(grip.actor, actor.actorID);
+  strictEqual(grip.name, SYMBOL_NAME);
+}
+
+function test_SA_raw() {
+  let actor = makeMockSymbolActor();
+  strictEqual(actor.rawValue(), TEST_SYMBOL);
+}
--- a/devtools/server/tests/unit/xpcshell.ini
+++ b/devtools/server/tests/unit/xpcshell.ini
@@ -230,8 +230,9 @@ support-files = xpcshell_debugging_scrip
 [test_setBreakpoint-on-line-in-gcd-script.js]
 [test_setBreakpoint-on-line-with-multiple-offsets.js]
 [test_setBreakpoint-on-line-with-multiple-statements.js]
 [test_setBreakpoint-on-line-with-no-offsets.js]
 [test_setBreakpoint-on-line-with-no-offsets-in-gcd-script.js]
 [test_safe-getter.js]
 [test_client_close.js]
 [test_shapes_highlighter_helpers.js]
+[test_symbolactor.js]
--- a/devtools/shared/client/debugger-client.js
+++ b/devtools/shared/client/debugger-client.js
@@ -905,30 +905,16 @@ DebuggerClient.prototype = {
 
     // Packets that indicate thread state changes get special treatment.
     if (packet.type in ThreadStateTypes &&
         this._clients.has(packet.from) &&
         typeof this._clients.get(packet.from)._onThreadState == "function") {
       this._clients.get(packet.from)._onThreadState(packet);
     }
 
-    // TODO: Bug 1151156 - Remove once Gecko 40 is on b2g-stable.
-    if (!this.traits.noNeedToFakeResumptionOnNavigation) {
-      // On navigation the server resumes, so the client must resume as well.
-      // We achieve that by generating a fake resumption packet that triggers
-      // the client's thread state change listeners.
-      if (packet.type == UnsolicitedNotifications.tabNavigated &&
-          this._clients.has(packet.from) &&
-          this._clients.get(packet.from).thread) {
-        let thread = this._clients.get(packet.from).thread;
-        let resumption = { from: thread._actor, type: "resumed" };
-        thread._onThreadState(resumption);
-      }
-    }
-
     // Only try to notify listeners on events, not responses to requests
     // that lack a packet type.
     if (packet.type) {
       this.emit(packet.type, packet);
     }
 
     if (activeRequest) {
       let emitReply = () => activeRequest.emit("json-reply", packet);
--- a/devtools/shared/discovery/discovery.js
+++ b/devtools/shared/discovery/discovery.js
@@ -52,21 +52,16 @@ XPCOMUtils.defineLazyGetter(this, "conve
   conv.charset = "utf8";
   return conv;
 });
 
 XPCOMUtils.defineLazyGetter(this, "sysInfo", () => {
   return Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2);
 });
 
-XPCOMUtils.defineLazyGetter(this, "libcutils", function () {
-  let { libcutils } = Cu.import("resource://gre/modules/systemlibs.js", {});
-  return libcutils;
-});
-
 var logging = Services.prefs.getBoolPref("devtools.discovery.log");
 function log(msg) {
   if (logging) {
     console.log("DISCOVERY: " + msg);
   }
 }
 
 /**
--- a/docshell/base/moz.build
+++ b/docshell/base/moz.build
@@ -90,16 +90,18 @@ UNIFIED_SOURCES += [
     'nsDefaultURIFixup.cpp',
     'nsDocShell.cpp',
     'nsDocShellEditorData.cpp',
     'nsDocShellEnumerator.cpp',
     'nsDocShellLoadInfo.cpp',
     'nsDocShellTransferableHooks.cpp',
     'nsDocShellTreeOwner.cpp',
     'nsDSURIContentListener.cpp',
+    'nsPingListener.cpp',
+    'nsRefreshTimer.cpp',
     'nsWebNavigationInfo.cpp',
     'PendingGlobalHistoryEntry.cpp',
     'SerializedLoadContext.cpp',
 ]
 
 if not CONFIG['MOZ_PLACES']:
     UNIFIED_SOURCES += ['nsDownloadHistory.cpp']
 
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -3,745 +3,290 @@
 /* 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 "nsDocShell.h"
 
 #include <algorithm>
 
+#ifdef XP_WIN
+#include <process.h>
+#define getpid _getpid
+#else
+#include <unistd.h> // for getpid()
+#endif
+
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/Casting.h"
-#include "mozilla/dom/ClientChannelHelper.h"
-#include "mozilla/dom/ClientHandle.h"
-#include "mozilla/dom/ClientInfo.h"
-#include "mozilla/dom/ClientManager.h"
-#include "mozilla/dom/ClientSource.h"
-#include "mozilla/dom/ContentChild.h"
-#include "mozilla/dom/Element.h"
-#include "mozilla/dom/HTMLAnchorElement.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"
+#include "mozilla/Encoding.h"
 #include "mozilla/EventStateManager.h"
+#include "mozilla/HTMLEditor.h"
 #include "mozilla/LoadInfo.h"
-#include "mozilla/HTMLEditor.h"
+#include "mozilla/Logging.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ResultExtensions.h"
 #include "mozilla/Services.h"
 #include "mozilla/StartupTimeline.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/Unused.h"
-#include "Navigator.h"
-#include "URIUtils.h"
+
+#include "mozilla/dom/ClientChannelHelper.h"
+#include "mozilla/dom/ClientHandle.h"
+#include "mozilla/dom/ClientInfo.h"
+#include "mozilla/dom/ClientManager.h"
+#include "mozilla/dom/ClientSource.h"
+#include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLAnchorElement.h"
+#include "mozilla/dom/PendingGlobalHistoryEntry.h"
+#include "mozilla/dom/PerformanceNavigation.h"
+#include "mozilla/dom/PermissionMessageUtils.h"
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+#include "mozilla/dom/ScreenOrientation.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/TabGroup.h"
-
+#include "mozilla/dom/ToJSValue.h"
+
+#include "mozilla/dom/workers/ServiceWorkerManager.h"
+
+#include "mozilla/net/ReferrerPolicy.h"
+
+#include "nsIApplicationCacheChannel.h"
+#include "nsIApplicationCacheContainer.h"
+#include "nsIAppShell.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIAuthPrompt.h"
+#include "nsIAuthPrompt2.h"
+#include "nsICachingChannel.h"
+#include "nsICaptivePortalService.h"
+#include "nsIChannel.h"
+#include "nsIChannelEventSink.h"
+#include "nsIClassOfService.h"
+#include "nsICommandManager.h"
+#include "nsIConsoleReportCollector.h"
 #include "nsIContent.h"
 #include "nsIContentInlines.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIContentViewer.h"
+#include "nsIController.h"
+#include "nsICookieService.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDocShellTreeOwner.h"
 #include "nsIDocument.h"
+#include "nsIDocumentLoaderFactory.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMElement.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMStorage.h"
+#include "nsIDOMWindow.h"
+#include "nsIEditingSession.h"
+#include "nsIExternalProtocolService.h"
+#include "nsIFormPOSTActionChannel.h"
+#include "nsIFrame.h"
+#include "nsIGlobalHistory2.h"
+#include "nsIGlobalObject.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIIDNService.h"
+#include "nsIInputStreamChannel.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIJARChannel.h"
+#include "nsILayoutHistoryState.h"
+#include "nsILoadInfo.h"
+#include "nsIMultiPartChannel.h"
+#include "nsINestedURI.h"
+#include "nsINetworkPredictor.h"
+#include "nsINode.h"
+#include "nsINSSErrorsService.h"
+#include "nsIObserverService.h"
+#include "nsIOService.h"
+#include "nsIPrincipal.h"
+#include "nsIPrivacyTransitionObserver.h"
+#include "nsIPrompt.h"
+#include "nsIPromptFactory.h"
+#include "nsIReflowObserver.h"
+#include "nsIScriptChannel.h"
+#include "nsIScriptError.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIScrollableFrame.h"
+#include "nsIScrollObserver.h"
+#include "nsISecureBrowserUI.h"
+#include "nsISecurityUITelemetry.h"
+#include "nsISeekableStream.h"
+#include "nsISelectionDisplay.h"
+#include "nsISHContainer.h"
+#include "nsISHEntry.h"
+#include "nsISHistory.h"
+#include "nsISHistoryInternal.h"
+#include "nsISiteSecurityService.h"
+#include "nsISocketProvider.h"
+#include "nsIStringBundle.h"
+#include "nsIStructuredCloneContainer.h"
+#include "nsISupportsPrimitives.h"
+#include "nsITabChild.h"
+#include "nsITextToSubURI.h"
+#include "nsITimedChannel.h"
+#include "nsITimer.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsIUploadChannel.h"
+#include "nsIURIFixup.h"
+#include "nsIURILoader.h"
+#include "nsIURL.h"
+#include "nsIViewSourceChannel.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsIWebBrowserChrome3.h"
+#include "nsIWebBrowserChromeFocus.h"
+#include "nsIWebBrowserFind.h"
+#include "nsIWebProgress.h"
+#include "nsIWidget.h"
+#include "nsIWindowWatcher.h"
+#include "nsIWritablePropertyBag2.h"
+#include "nsIWyciwygChannel.h"
+
+#include "nsPICommandUpdater.h"
+#include "nsPIDOMWindow.h"
+#include "nsPILoadGroupInternal.h"
+#include "nsPIWindowRoot.h"
+
+#include "IHistory.h"
+#include "IUrlClassifierUITelemetry.h"
+
+#include "mozIThirdPartyUtil.h"
 
 #include "nsArray.h"
 #include "nsArrayUtils.h"
+#include "nsAutoPtr.h"
+#include "nsCDefaultURIFixup.h"
+#include "nsCExternalHandlerService.h"
+#include "nsContentDLF.h"
+#include "nsContentPolicyUtils.h" // NS_CheckContentLoadPolicy(...)
 #include "nsContentSecurityManager.h"
-#include "nsICaptivePortalService.h"
-#include "nsIDOMStorage.h"
-#include "nsIContentViewer.h"
-#include "nsIDocumentLoaderFactory.h"
+#include "nsContentUtils.h"
 #include "nsCURILoader.h"
-#include "nsContentDLF.h"
 #include "nsDocShellCID.h"
+#include "nsDocShellEditorData.h"
+#include "nsDocShellEnumerator.h"
+#include "nsDocShellLoadInfo.h"
+#include "nsDocShellLoadTypes.h"
+#include "nsDocShellTransferableHooks.h"
 #include "nsDOMCID.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsDSURIContentListener.h"
+#include "nsError.h"
+#include "nsEscape.h"
+#include "nsFocusManager.h"
+#include "nsGlobalWindow.h"
+#include "nsJSEnvironment.h"
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
-#include "mozilla/net/ReferrerPolicy.h"
-#include "nsRect.h"
-#include "prenv.h"
-#include "nsIDOMWindow.h"
-#include "nsIGlobalObject.h"
-#include "nsIViewSourceChannel.h"
-#include "nsIWebBrowserChrome.h"
+#include "nsObjectLoadingContent.h"
+#include "nsPingListener.h"
 #include "nsPoint.h"
-#include "nsIObserverService.h"
-#include "nsIPrompt.h"
-#include "nsIAuthPrompt.h"
-#include "nsIAuthPrompt2.h"
-#include "nsIChannelEventSink.h"
-#include "nsIAsyncVerifyRedirectCallback.h"
-#include "nsIScriptSecurityManager.h"
-#include "nsIScriptObjectPrincipal.h"
-#include "nsIScrollableFrame.h"
-#include "nsContentPolicyUtils.h" // NS_CheckContentLoadPolicy(...)
-#include "nsISeekableStream.h"
-#include "nsAutoPtr.h"
 #include "nsQueryObject.h"
-#include "nsIWritablePropertyBag2.h"
-#include "nsIAppShell.h"
-#include "nsWidgetsCID.h"
-#include "nsIInterfaceRequestorUtils.h"
+#include "nsRect.h"
+#include "nsRefreshTimer.h"
+#include "nsSandboxFlags.h"
+#include "nsSHistory.h"
+#include "nsStructuredCloneContainer.h"
+#include "nsSubDocumentFrame.h"
 #include "nsView.h"
 #include "nsViewManager.h"
-#include "nsIScriptChannel.h"
-#include "nsITimedChannel.h"
-#include "nsIPrivacyTransitionObserver.h"
-#include "nsIReflowObserver.h"
-#include "nsIScrollObserver.h"
-#include "nsIDocShellTreeItem.h"
-#include "nsIChannel.h"
-#include "IHistory.h"
 #include "nsViewSourceHandler.h"
 #include "nsWhitespaceTokenizer.h"
-#include "nsICookieService.h"
-#include "nsIConsoleReportCollector.h"
-#include "nsObjectLoadingContent.h"
-#include "nsStringStream.h"
-
-// we want to explore making the document own the load group
-// so we can associate the document URI with the load group.
-// until this point, we have an evil hack:
-#include "nsIHttpChannelInternal.h"
-#include "nsPILoadGroupInternal.h"
-
-// Local Includes
-#include "nsDocShellLoadInfo.h"
-#include "nsCDefaultURIFixup.h"
-#include "nsDocShellEnumerator.h"
-#include "nsSHistory.h"
-#include "nsDocShellEditorData.h"
+#include "nsWidgetsCID.h"
+#include "nsXULAppAPI.h"
+
 #include "GeckoProfiler.h"
+#include "Navigator.h"
+#include "NullPrincipal.h"
+#include "prenv.h"
+#include "URIUtils.h"
+
 #include "timeline/JavascriptTimelineMarker.h"
 
-// Helper Classes
-#include "nsError.h"
-#include "nsEscape.h"
-
-// Interfaces Needed
-#include "nsIFormPOSTActionChannel.h"
-#include "nsIUploadChannel.h"
-#include "nsIUploadChannel2.h"
-#include "nsIWebProgress.h"
-#include "nsILayoutHistoryState.h"
-#include "nsITimer.h"
-#include "nsISHistoryInternal.h"
-#include "nsIPrincipal.h"
-#include "NullPrincipal.h"
-#include "nsISHEntry.h"
-#include "nsIWindowWatcher.h"
-#include "nsIPromptFactory.h"
-#include "nsITransportSecurityInfo.h"
-#include "nsINode.h"
-#include "nsINSSErrorsService.h"
-#include "nsIApplicationCacheChannel.h"
-#include "nsIApplicationCacheContainer.h"
-#include "nsStreamUtils.h"
-#include "nsIController.h"
-#include "nsPICommandUpdater.h"
-#include "nsIWebBrowserChrome3.h"
-#include "nsITabChild.h"
-#include "nsISiteSecurityService.h"
-#include "nsStructuredCloneContainer.h"
-#include "nsIStructuredCloneContainer.h"
-#include "nsISupportsPrimitives.h"
 #ifdef MOZ_PLACES
 #include "nsIFaviconService.h"
 #include "mozIPlacesPendingOperation.h"
 #include "mozIAsyncFavicons.h"
 #endif
-#include "nsINetworkPredictor.h"
-
-// Editor-related
-#include "nsIEditingSession.h"
-
-#include "nsPIDOMWindow.h"
-#include "nsGlobalWindow.h"
-#include "nsPIWindowRoot.h"
-#include "nsICachingChannel.h"
-#include "nsIMultiPartChannel.h"
-#include "nsIWyciwygChannel.h"
-
-// For reporting errors with the console service.
-// These can go away if error reporting is propagated up past nsDocShell.
-#include "nsIScriptError.h"
-
-// used to dispatch urls to default protocol handlers
-#include "nsCExternalHandlerService.h"
-#include "nsIExternalProtocolService.h"
-
-#include "nsFocusManager.h"
-
-#include "nsITextToSubURI.h"
-
-#include "nsIJARChannel.h"
-
-#include "mozilla/Logging.h"
-
-#include "nsISelectionDisplay.h"
-
-#include "nsIGlobalHistory2.h"
-
-#include "nsIFrame.h"
-#include "nsSubDocumentFrame.h"
-
-// for embedding
-#include "nsIWebBrowserChromeFocus.h"
 
 #if NS_PRINT_PREVIEW
 #include "nsIDocumentViewerPrint.h"
 #include "nsIWebBrowserPrint.h"
 #endif
 
-#include "nsContentUtils.h"
-#include "nsIContentSecurityPolicy.h"
-#include "nsILoadInfo.h"
-#include "nsSandboxFlags.h"
-#include "nsXULAppAPI.h"
-#include "nsDOMNavigationTiming.h"
-#include "nsISecurityUITelemetry.h"
-#include "nsDSURIContentListener.h"
-#include "nsDocShellLoadTypes.h"
-#include "nsDocShellTransferableHooks.h"
-#include "nsICommandManager.h"
-#include "nsIDOMNode.h"
-#include "nsIClassOfService.h"
-#include "nsIDocShellTreeOwner.h"
-#include "nsIHttpChannel.h"
-#include "nsIIDNService.h"
-#include "nsIInputStreamChannel.h"
-#include "nsINestedURI.h"
-#include "nsIOService.h"
-#include "nsISHContainer.h"
-#include "nsISHistory.h"
-#include "nsISecureBrowserUI.h"
-#include "nsISocketProvider.h"
-#include "nsIStringBundle.h"
-#include "nsIURIFixup.h"
-#include "nsIURILoader.h"
-#include "nsIURL.h"
-#include "nsIWebBrowserFind.h"
-#include "nsIWidget.h"
-#include "mozilla/dom/PerformanceNavigation.h"
-#include "mozilla/dom/ScriptSettings.h"
-#include "mozilla/Encoding.h"
-#include "nsJSEnvironment.h"
-#include "IUrlClassifierUITelemetry.h"
-
 #ifdef MOZ_TOOLKIT_SEARCH
 #include "nsIBrowserSearchService.h"
 #endif
 
-#include "mozIThirdPartyUtil.h"
-
-static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
-
-#if defined(DEBUG_bryner) || defined(DEBUG_chb)
-//#define DEBUG_DOCSHELL_FOCUS
-#define DEBUG_PAGE_CACHE
-#endif
-
-#ifdef XP_WIN
-#include <process.h>
-#define getpid _getpid
-#else
-#include <unistd.h> // for getpid()
-#endif
-
 using namespace mozilla;
 using namespace mozilla::dom;
 using mozilla::dom::workers::ServiceWorkerManager;
 
+// Threshold value in ms for META refresh based redirects
+#define REFRESH_REDIRECT_TIMER 15000
+
+// Hint for native dispatch of events on how long to delay after
+// all documents have loaded in milliseconds before favoring normal
+// native event dispatch priorites over performance
+// Can be overridden with docshell.event_starvation_delay_hint pref.
+#define NS_EVENT_STARVATION_DELAY_HINT 2000
+
+static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
+
 // True means sUseErrorPages has been added to
 // preferences var cache.
 static bool gAddedPreferencesVarCache = false;
 
-bool nsDocShell::sUseErrorPages = false;
-
 // Number of documents currently loading
 static int32_t gNumberOfDocumentsLoading = 0;
 
 // Global count of existing docshells.
 static int32_t gDocShellCount = 0;
 
 // Global count of docshells with the private attribute set
 static uint32_t gNumberOfPrivateDocShells = 0;
 
-// Global reference to the URI fixup service.
-nsIURIFixup* nsDocShell::sURIFixup = 0;
-
 // True means we validate window targets to prevent frameset
 // spoofing. Initialize this to a non-bolean value so we know to check
 // the pref on the creation of the first docshell.
 static uint32_t gValidateOrigin = 0xffffffff;
 
-// Hint for native dispatch of events on how long to delay after
-// all documents have loaded in milliseconds before favoring normal
-// native event dispatch priorites over performance
-// Can be overridden with docshell.event_starvation_delay_hint pref.
-#define NS_EVENT_STARVATION_DELAY_HINT 2000
-
 #ifdef DEBUG
 static mozilla::LazyLogModule gDocShellLog("nsDocShell");
 #endif
 static mozilla::LazyLogModule gDocShellLeakLog("nsDocShellLeak");;
 
 const char kBrandBundleURL[]      = "chrome://branding/locale/brand.properties";
 const char kAppstringsBundleURL[] = "chrome://global/locale/appstrings.properties";
 
+bool nsDocShell::sUseErrorPages = false;
+
+// Global reference to the URI fixup service.
+nsIURIFixup* nsDocShell::sURIFixup = nullptr;
+
 static void
 FavorPerformanceHint(bool aPerfOverStarvation)
 {
   nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
   if (appShell) {
     appShell->FavorPerformanceHint(
       aPerfOverStarvation,
       Preferences::GetUint("docshell.event_starvation_delay_hint",
                            NS_EVENT_STARVATION_DELAY_HINT));
   }
 }
 
-//*****************************************************************************
-// <a ping> support
-//*****************************************************************************
-
-#define PREF_PINGS_ENABLED           "browser.send_pings"
-#define PREF_PINGS_MAX_PER_LINK      "browser.send_pings.max_per_link"
-#define PREF_PINGS_REQUIRE_SAME_HOST "browser.send_pings.require_same_host"
-
-// Check prefs to see if pings are enabled and if so what restrictions might
-// be applied.
-//
-// @param maxPerLink
-//   This parameter returns the number of pings that are allowed per link click
-//
-// @param requireSameHost
-//   This parameter returns true if pings are restricted to the same host as
-//   the document in which the click occurs.  If the same host restriction is
-//   imposed, then we still allow for pings to cross over to different
-//   protocols and ports for flexibility and because it is not possible to send
-//   a ping via FTP.
-//
-// @returns
-//   true if pings are enabled and false otherwise.
-//
-static bool
-PingsEnabled(int32_t* aMaxPerLink, bool* aRequireSameHost)
-{
-  bool allow = Preferences::GetBool(PREF_PINGS_ENABLED, false);
-
-  *aMaxPerLink = 1;
-  *aRequireSameHost = true;
-
-  if (allow) {
-    Preferences::GetInt(PREF_PINGS_MAX_PER_LINK, aMaxPerLink);
-    Preferences::GetBool(PREF_PINGS_REQUIRE_SAME_HOST, aRequireSameHost);
-  }
-
-  return allow;
-}
-
-typedef void (*ForEachPingCallback)(void* closure, nsIContent* content,
-                                    nsIURI* uri, nsIIOService* ios);
-
-static bool
-IsElementAnchor(nsIContent* aContent)
-{
-  // Make sure we are dealing with either an <A> or <AREA> element in the HTML
-  // or XHTML namespace.
-  return aContent->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area);
-}
-
-static void
-ForEachPing(nsIContent* aContent, ForEachPingCallback aCallback, void* aClosure)
-{
-  // NOTE: Using nsIDOMHTMLAnchorElement::GetPing isn't really worth it here
-  //       since we'd still need to parse the resulting string.  Instead, we
-  //       just parse the raw attribute.  It might be nice if the content node
-  //       implemented an interface that exposed an enumeration of nsIURIs.
-
-  // Make sure we are dealing with either an <A> or <AREA> element in the HTML
-  // or XHTML namespace.
-  if (!IsElementAnchor(aContent)) {
-    return;
-  }
-
-  nsAutoString value;
-  aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::ping, value);
-  if (value.IsEmpty()) {
-    return;
-  }
-
-  nsCOMPtr<nsIIOService> ios = do_GetIOService();
-  if (!ios) {
-    return;
-  }
-
-  nsIDocument* doc = aContent->OwnerDoc();
-  nsAutoCString charset;
-  doc->GetDocumentCharacterSet()->Name(charset);
-
-  nsWhitespaceTokenizer tokenizer(value);
-
-  while (tokenizer.hasMoreTokens()) {
-    nsCOMPtr<nsIURI> uri, baseURI = aContent->GetBaseURI();
-    ios->NewURI(NS_ConvertUTF16toUTF8(tokenizer.nextToken()),
-                charset.get(), baseURI, getter_AddRefs(uri));
-    // if we can't generate a valid URI, then there is nothing to do
-    if (!uri) {
-      continue;
-    }
-    // Explicitly not allow loading data: URIs
-    bool isDataScheme =
-      (NS_SUCCEEDED(uri->SchemeIs("data", &isDataScheme)) && isDataScheme);
-
-    if (!isDataScheme) {
-      aCallback(aClosure, aContent, uri, ios);
-    }
-  }
-}
-
-//----------------------------------------------------------------------
-
-// We wait this many milliseconds before killing the ping channel...
-#define PING_TIMEOUT 10000
-
-static void
-OnPingTimeout(nsITimer* aTimer, void* aClosure)
-{
-  nsILoadGroup* loadGroup = static_cast<nsILoadGroup*>(aClosure);
-  if (loadGroup) {
-    loadGroup->Cancel(NS_ERROR_ABORT);
-  }
-}
-
-class nsPingListener final
-  : public nsIStreamListener
-{
-public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSIREQUESTOBSERVER
-  NS_DECL_NSISTREAMLISTENER
-
-  nsPingListener()
-  {
-  }
-
-  void SetLoadGroup(nsILoadGroup* aLoadGroup) {
-    mLoadGroup = aLoadGroup;
-  }
-
-  nsresult StartTimeout(DocGroup* aDocGroup);
-
-private:
-  ~nsPingListener();
-
-  nsCOMPtr<nsILoadGroup> mLoadGroup;
-  nsCOMPtr<nsITimer> mTimer;
-};
-
-NS_IMPL_ISUPPORTS(nsPingListener, nsIStreamListener, nsIRequestObserver)
-
-nsPingListener::~nsPingListener()
-{
-  if (mTimer) {
-    mTimer->Cancel();
-    mTimer = nullptr;
-  }
-}
-
-nsresult
-nsPingListener::StartTimeout(DocGroup* aDocGroup)
-{
-  NS_ENSURE_ARG(aDocGroup);
-
-  return NS_NewTimerWithFuncCallback(getter_AddRefs(mTimer),
-                                     OnPingTimeout,
-                                     mLoadGroup,
-                                     PING_TIMEOUT,
-                                     nsITimer::TYPE_ONE_SHOT,
-                                     "nsPingListener::StartTimeout",
-                                     aDocGroup->EventTargetFor(TaskCategory::Network));
-}
-
-NS_IMETHODIMP
-nsPingListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
-{
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsPingListener::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
-                                nsIInputStream* aStream, uint64_t aOffset,
-                                uint32_t aCount)
-{
-  uint32_t result;
-  return aStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &result);
-}
-
-NS_IMETHODIMP
-nsPingListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
-                              nsresult aStatus)
-{
-  mLoadGroup = nullptr;
-
-  if (mTimer) {
-    mTimer->Cancel();
-    mTimer = nullptr;
-  }
-
-  return NS_OK;
-}
-
-struct MOZ_STACK_CLASS SendPingInfo
-{
-  int32_t numPings;
-  int32_t maxPings;
-  bool requireSameHost;
-  nsIURI* target;
-  nsIURI* referrer;
-  nsIDocShell* docShell;
-  uint32_t referrerPolicy;
-};
-
-static void
-SendPing(void* aClosure, nsIContent* aContent, nsIURI* aURI,
-         nsIIOService* aIOService)
-{
-  SendPingInfo* info = static_cast<SendPingInfo*>(aClosure);
-  if (info->maxPings > -1 && info->numPings >= info->maxPings) {
-    return;
-  }
-
-  nsIDocument* doc = aContent->OwnerDoc();
-
-  nsCOMPtr<nsIChannel> chan;
-  NS_NewChannel(getter_AddRefs(chan),
-                aURI,
-                doc,
-                info->requireSameHost
-                  ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
-                  : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
-                nsIContentPolicy::TYPE_PING,
-                nullptr, // aLoadGroup
-                nullptr, // aCallbacks
-                nsIRequest::LOAD_NORMAL, // aLoadFlags,
-                aIOService);
-
-  if (!chan) {
-    return;
-  }
-
-  // Don't bother caching the result of this URI load, but do not exempt
-  // it from Safe Browsing.
-  chan->SetLoadFlags(nsIRequest::INHIBIT_CACHING | nsIChannel::LOAD_CLASSIFY_URI);
-
-  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) {
-    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)
-  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))) {
-    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;
-    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))) {
-        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) {
-      rv = httpChan->SetReferrerWithPolicy(info->referrer, info->referrerPolicy);
-      MOZ_ASSERT(NS_SUCCEEDED(rv));
-    }
-  }
-
-  nsCOMPtr<nsIUploadChannel2> uploadChan = do_QueryInterface(httpChan);
-  if (!uploadChan) {
-    return;
-  }
-
-  NS_NAMED_LITERAL_CSTRING(uploadData, "PING");
-
-  nsCOMPtr<nsIInputStream> uploadStream;
-  rv = NS_NewCStringInputStream(getter_AddRefs(uploadStream), uploadData);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return;
-  }
-
-  uploadChan->ExplicitSetUploadStream(uploadStream,
-                                      NS_LITERAL_CSTRING("text/ping"),
-                                      uploadData.Length(),
-                                      NS_LITERAL_CSTRING("POST"), false);
-
-  // The channel needs to have a loadgroup associated with it, so that we can
-  // cancel the channel and any redirected channels it may create.
-  nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
-  if (!loadGroup) {
-    return;
-  }
-  nsCOMPtr<nsIInterfaceRequestor> callbacks = do_QueryInterface(info->docShell);
-  loadGroup->SetNotificationCallbacks(callbacks);
-  chan->SetLoadGroup(loadGroup);
-
-  RefPtr<nsPingListener> pingListener = new nsPingListener();
-  chan->AsyncOpen2(pingListener);
-
-  // Even if AsyncOpen failed, we still count this as a successful ping.  It's
-  // possible that AsyncOpen may have failed after triggering some background
-  // process that may have written something to the network.
-  info->numPings++;
-
-  // Prevent ping requests from stalling and never being garbage collected...
-  if (NS_FAILED(pingListener->StartTimeout(doc->GetDocGroup()))) {
-    // If we failed to setup the timer, then we should just cancel the channel
-    // because we won't be able to ensure that it goes away in a timely manner.
-    chan->Cancel(NS_ERROR_ABORT);
-    return;
-  }
-  // if the channel openend successfully, then make the pingListener hold
-  // a strong reference to the loadgroup which is released in ::OnStopRequest
-  pingListener->SetLoadGroup(loadGroup);
-}
-
-// Spec: http://whatwg.org/specs/web-apps/current-work/#ping
-static void
-DispatchPings(nsIDocShell* aDocShell,
-              nsIContent* aContent,
-              nsIURI* aTarget,
-              nsIURI* aReferrer,
-              uint32_t aReferrerPolicy)
-{
-  SendPingInfo info;
-
-  if (!PingsEnabled(&info.maxPings, &info.requireSameHost)) {
-    return;
-  }
-  if (info.maxPings == 0) {
-    return;
-  }
-
-  info.numPings = 0;
-  info.target = aTarget;
-  info.referrer = aReferrer;
-  info.referrerPolicy = aReferrerPolicy;
-  info.docShell = aDocShell;
-
-  ForEachPing(aContent, SendPing, &info);
-}
-
-static nsDOMNavigationTiming::Type
-ConvertLoadTypeToNavigationType(uint32_t aLoadType)
-{
-  // Not initialized, assume it's normal load.
-  if (aLoadType == 0) {
-    aLoadType = LOAD_NORMAL;
-  }
-
-  auto result = nsDOMNavigationTiming::TYPE_RESERVED;
-  switch (aLoadType) {
-    case LOAD_NORMAL:
-    case LOAD_NORMAL_EXTERNAL:
-    case LOAD_NORMAL_BYPASS_CACHE:
-    case LOAD_NORMAL_BYPASS_PROXY:
-    case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE:
-    case LOAD_NORMAL_REPLACE:
-    case LOAD_NORMAL_ALLOW_MIXED_CONTENT:
-    case LOAD_LINK:
-    case LOAD_STOP_CONTENT:
-    case LOAD_REPLACE_BYPASS_CACHE:
-      result = nsDOMNavigationTiming::TYPE_NAVIGATE;
-      break;
-    case LOAD_HISTORY:
-      result = nsDOMNavigationTiming::TYPE_BACK_FORWARD;
-      break;
-    case LOAD_RELOAD_NORMAL:
-    case LOAD_RELOAD_CHARSET_CHANGE:
-    case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE:
-    case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE:
-    case LOAD_RELOAD_BYPASS_CACHE:
-    case LOAD_RELOAD_BYPASS_PROXY:
-    case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
-    case LOAD_RELOAD_ALLOW_MIXED_CONTENT:
-      result = nsDOMNavigationTiming::TYPE_RELOAD;
-      break;
-    case LOAD_STOP_CONTENT_AND_REPLACE:
-    case LOAD_REFRESH:
-    case LOAD_BYPASS_HISTORY:
-    case LOAD_ERROR_PAGE:
-    case LOAD_PUSHSTATE:
-      result = nsDOMNavigationTiming::TYPE_RESERVED;
-      break;
-    default:
-      // NS_NOTREACHED("Unexpected load type value");
-      result = nsDOMNavigationTiming::TYPE_RESERVED;
-      break;
-  }
-
-  return result;
-}
-
-static nsISHEntry* GetRootSHEntry(nsISHEntry* aEntry);
-
 static void
 IncreasePrivateDocShellCount()
 {
   gNumberOfPrivateDocShells++;
   if (gNumberOfPrivateDocShells > 1 ||
       !XRE_IsContentProcess()) {
     return;
   }
@@ -766,34 +311,47 @@ DecreasePrivateDocShellCount()
     if (obsvc) {
       obsvc->NotifyObservers(nullptr, "last-pb-context-exited", nullptr);
     }
   }
 }
 
 nsDocShell::nsDocShell()
   : nsDocLoader()
-  , mDefaultScrollbarPref(Scrollbar_Auto, Scrollbar_Auto)
-  , mReferrerPolicy(0)
-  , mFailedLoadType(0)
+  , mForcedCharset(nullptr)
+  , mParentCharset(nullptr)
   , mTreeOwner(nullptr)
   , mChromeEventHandler(nullptr)
+  , mDefaultScrollbarPref(Scrollbar_Auto, Scrollbar_Auto)
   , mCharsetReloadState(eCharsetReloadInit)
-  , mChildOffset(0)
-  , mBusyFlags(BUSY_FLAGS_NONE)
-  , mAppType(nsIDocShell::APP_TYPE_UNKNOWN)
-  , mLoadType(0)
+  , mOrientationLock(eScreenOrientation_None)
+  , mParentCharsetSource(0)
   , mMarginWidth(-1)
   , mMarginHeight(-1)
   , mItemType(typeContent)
   , mPreviousTransIndex(-1)
   , mLoadedTransIndex(-1)
+  , mChildOffset(0)
   , mSandboxFlags(0)
-  , mOrientationLock(eScreenOrientation_None)
+  , mBusyFlags(BUSY_FLAGS_NONE)
+  , mAppType(nsIDocShell::APP_TYPE_UNKNOWN)
+  , mLoadType(0)
+  , mDefaultLoadFlags(nsIRequest::LOAD_NORMAL)
+  , mReferrerPolicy(0)
+  , mFailedLoadType(0)
+  , mFrameType(FRAME_TYPE_REGULAR)
+  , mPrivateBrowsingId(0)
+  , mDisplayMode(nsIDocShell::DISPLAY_MODE_BROWSER)
+  , mJSRunToCompletionDepth(0)
+  , mTouchEventsOverride(nsIDocShell::TOUCHEVENTS_OVERRIDE_NONE)
   , mFullscreenAllowed(CHECK_ATTRIBUTES)
+  , mCreatingDocument(false)
+#ifdef DEBUG
+  , mInEnsureScriptEnv(false)
+#endif
   , mCreated(false)
   , mAllowSubframes(true)
   , mAllowPlugins(true)
   , mAllowJavascript(true)
   , mAllowMetaRedirects(true)
   , mAllowImages(true)
   , mAllowMedia(true)
   , mAllowDNSPrefetch(true)
@@ -824,29 +382,16 @@ nsDocShell::nsDocShell()
   , mIsExecutingOnLoadHandler(false)
   , mIsPrintingOrPP(false)
   , mSavingOldViewer(false)
   , mDynamicallyCreated(false)
   , mAffectPrivateSessionLifetime(true)
   , mInvisible(false)
   , mHasLoadedNonBlankURI(false)
   , mBlankTiming(false)
-  , mCreatingDocument(false)
-#ifdef DEBUG
-  , mInEnsureScriptEnv(false)
-#endif
-  , mDefaultLoadFlags(nsIRequest::LOAD_NORMAL)
-  , mFrameType(FRAME_TYPE_REGULAR)
-  , mPrivateBrowsingId(0)
-  , mDisplayMode(nsIDocShell::DISPLAY_MODE_BROWSER)
-  , mForcedCharset(nullptr)
-  , mParentCharset(nullptr)
-  , mParentCharsetSource(0)
-  , mJSRunToCompletionDepth(0)
-  , mTouchEventsOverride(nsIDocShell::TOUCHEVENTS_OVERRIDE_NONE)
 {
   AssertOriginAttributesMatchPrivateBrowsing();
 
   nsContentUtils::GenerateUUIDInPlace(mHistoryID);
 
   if (gDocShellCount++ == 0) {
     NS_ASSERTION(sURIFixup == nullptr,
                  "Huh, sURIFixup not null in first nsDocShell ctor!");
@@ -1107,181 +652,16 @@ nsDocShell::GetInterface(const nsIID& aI
   } else {
     return nsDocLoader::GetInterface(aIID, aSink);
   }
 
   NS_IF_ADDREF(((nsISupports*)*aSink));
   return *aSink ? NS_OK : NS_NOINTERFACE;
 }
 
-uint32_t
-nsDocShell::ConvertDocShellLoadInfoToLoadType(
-    nsDocShellInfoLoadType aDocShellLoadType)
-{
-  uint32_t loadType = LOAD_NORMAL;
-
-  switch (aDocShellLoadType) {
-    case nsIDocShellLoadInfo::loadNormal:
-      loadType = LOAD_NORMAL;
-      break;
-    case nsIDocShellLoadInfo::loadNormalReplace:
-      loadType = LOAD_NORMAL_REPLACE;
-      break;
-    case nsIDocShellLoadInfo::loadNormalExternal:
-      loadType = LOAD_NORMAL_EXTERNAL;
-      break;
-    case nsIDocShellLoadInfo::loadHistory:
-      loadType = LOAD_HISTORY;
-      break;
-    case nsIDocShellLoadInfo::loadNormalBypassCache:
-      loadType = LOAD_NORMAL_BYPASS_CACHE;
-      break;
-    case nsIDocShellLoadInfo::loadNormalBypassProxy:
-      loadType = LOAD_NORMAL_BYPASS_PROXY;
-      break;
-    case nsIDocShellLoadInfo::loadNormalBypassProxyAndCache:
-      loadType = LOAD_NORMAL_BYPASS_PROXY_AND_CACHE;
-      break;
-    case nsIDocShellLoadInfo::loadNormalAllowMixedContent:
-      loadType = LOAD_NORMAL_ALLOW_MIXED_CONTENT;
-      break;
-    case nsIDocShellLoadInfo::loadReloadNormal:
-      loadType = LOAD_RELOAD_NORMAL;
-      break;
-    case nsIDocShellLoadInfo::loadReloadCharsetChange:
-      loadType = LOAD_RELOAD_CHARSET_CHANGE;
-      break;
-    case nsIDocShellLoadInfo::loadReloadCharsetChangeBypassCache:
-      loadType = LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE;
-      break;
-    case nsIDocShellLoadInfo::loadReloadCharsetChangeBypassProxyAndCache:
-      loadType = LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE;
-      break;
-    case nsIDocShellLoadInfo::loadReloadBypassCache:
-      loadType = LOAD_RELOAD_BYPASS_CACHE;
-      break;
-    case nsIDocShellLoadInfo::loadReloadBypassProxy:
-      loadType = LOAD_RELOAD_BYPASS_PROXY;
-      break;
-    case nsIDocShellLoadInfo::loadReloadBypassProxyAndCache:
-      loadType = LOAD_RELOAD_BYPASS_PROXY_AND_CACHE;
-      break;
-    case nsIDocShellLoadInfo::loadLink:
-      loadType = LOAD_LINK;
-      break;
-    case nsIDocShellLoadInfo::loadRefresh:
-      loadType = LOAD_REFRESH;
-      break;
-    case nsIDocShellLoadInfo::loadBypassHistory:
-      loadType = LOAD_BYPASS_HISTORY;
-      break;
-    case nsIDocShellLoadInfo::loadStopContent:
-      loadType = LOAD_STOP_CONTENT;
-      break;
-    case nsIDocShellLoadInfo::loadStopContentAndReplace:
-      loadType = LOAD_STOP_CONTENT_AND_REPLACE;
-      break;
-    case nsIDocShellLoadInfo::loadPushState:
-      loadType = LOAD_PUSHSTATE;
-      break;
-    case nsIDocShellLoadInfo::loadReplaceBypassCache:
-      loadType = LOAD_REPLACE_BYPASS_CACHE;
-      break;
-    case nsIDocShellLoadInfo::loadReloadMixedContent:
-      loadType = LOAD_RELOAD_ALLOW_MIXED_CONTENT;
-      break;
-    default:
-      NS_NOTREACHED("Unexpected nsDocShellInfoLoadType value");
-  }
-
-  return loadType;
-}
-
-nsDocShellInfoLoadType
-nsDocShell::ConvertLoadTypeToDocShellLoadInfo(uint32_t aLoadType)
-{
-  nsDocShellInfoLoadType docShellLoadType = nsIDocShellLoadInfo::loadNormal;
-  switch (aLoadType) {
-    case LOAD_NORMAL:
-      docShellLoadType = nsIDocShellLoadInfo::loadNormal;
-      break;
-    case LOAD_NORMAL_REPLACE:
-      docShellLoadType = nsIDocShellLoadInfo::loadNormalReplace;
-      break;
-    case LOAD_NORMAL_EXTERNAL:
-      docShellLoadType = nsIDocShellLoadInfo::loadNormalExternal;
-      break;
-    case LOAD_NORMAL_BYPASS_CACHE:
-      docShellLoadType = nsIDocShellLoadInfo::loadNormalBypassCache;
-      break;
-    case LOAD_NORMAL_BYPASS_PROXY:
-      docShellLoadType = nsIDocShellLoadInfo::loadNormalBypassProxy;
-      break;
-    case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE:
-      docShellLoadType = nsIDocShellLoadInfo::loadNormalBypassProxyAndCache;
-      break;
-    case LOAD_NORMAL_ALLOW_MIXED_CONTENT:
-      docShellLoadType = nsIDocShellLoadInfo::loadNormalAllowMixedContent;
-      break;
-    case LOAD_HISTORY:
-      docShellLoadType = nsIDocShellLoadInfo::loadHistory;
-      break;
-    case LOAD_RELOAD_NORMAL:
-      docShellLoadType = nsIDocShellLoadInfo::loadReloadNormal;
-      break;
-    case LOAD_RELOAD_CHARSET_CHANGE:
-      docShellLoadType = nsIDocShellLoadInfo::loadReloadCharsetChange;
-      break;
-    case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE:
-      docShellLoadType = nsIDocShellLoadInfo::loadReloadCharsetChangeBypassCache;
-      break;
-    case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE:
-      docShellLoadType = nsIDocShellLoadInfo::loadReloadCharsetChangeBypassProxyAndCache;
-      break;
-    case LOAD_RELOAD_BYPASS_CACHE:
-      docShellLoadType = nsIDocShellLoadInfo::loadReloadBypassCache;
-      break;
-    case LOAD_RELOAD_BYPASS_PROXY:
-      docShellLoadType = nsIDocShellLoadInfo::loadReloadBypassProxy;
-      break;
-    case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
-      docShellLoadType = nsIDocShellLoadInfo::loadReloadBypassProxyAndCache;
-      break;
-    case LOAD_LINK:
-      docShellLoadType = nsIDocShellLoadInfo::loadLink;
-      break;
-    case LOAD_REFRESH:
-      docShellLoadType = nsIDocShellLoadInfo::loadRefresh;
-      break;
-    case LOAD_BYPASS_HISTORY:
-    case LOAD_ERROR_PAGE:
-      docShellLoadType = nsIDocShellLoadInfo::loadBypassHistory;
-      break;
-    case LOAD_STOP_CONTENT:
-      docShellLoadType = nsIDocShellLoadInfo::loadStopContent;
-      break;
-    case LOAD_STOP_CONTENT_AND_REPLACE:
-      docShellLoadType = nsIDocShellLoadInfo::loadStopContentAndReplace;
-      break;
-    case LOAD_PUSHSTATE:
-      docShellLoadType = nsIDocShellLoadInfo::loadPushState;
-      break;
-    case LOAD_REPLACE_BYPASS_CACHE:
-      docShellLoadType = nsIDocShellLoadInfo::loadReplaceBypassCache;
-      break;
-    case LOAD_RELOAD_ALLOW_MIXED_CONTENT:
-      docShellLoadType = nsIDocShellLoadInfo::loadReloadMixedContent;
-      break;
-    default:
-      NS_NOTREACHED("Unexpected load type value");
-  }
-
-  return docShellLoadType;
-}
-
 NS_IMETHODIMP
 nsDocShell::LoadURI(nsIURI* aURI,
                     nsIDocShellLoadInfo* aLoadInfo,
                     uint32_t aLoadFlags,
                     bool aFirstParty)
 {
   NS_PRECONDITION(aLoadInfo || (aLoadFlags & EXTRA_LOAD_FLAGS) == 0,
                   "Unexpected flags");
@@ -1329,17 +709,17 @@ nsDocShell::LoadURI(nsIURI* aURI,
   if (aLoadInfo) {
     aLoadInfo->GetReferrer(getter_AddRefs(referrer));
     aLoadInfo->GetOriginalURI(getter_AddRefs(originalURI));
     GetMaybeResultPrincipalURI(aLoadInfo, resultPrincipalURI);
     aLoadInfo->GetLoadReplace(&loadReplace);
     nsDocShellInfoLoadType lt = nsIDocShellLoadInfo::loadNormal;
     aLoadInfo->GetLoadType(&lt);
     // Get the appropriate loadType from nsIDocShellLoadInfo type
-    loadType = ConvertDocShellLoadInfoToLoadType(lt);
+    loadType = ConvertDocShellInfoLoadTypeToLoadType(lt);
 
     aLoadInfo->GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal));
     aLoadInfo->GetInheritPrincipal(&inheritPrincipal);
     aLoadInfo->GetPrincipalIsExplicit(&principalIsExplicit);
     aLoadInfo->GetSHEntry(getter_AddRefs(shEntry));
     aLoadInfo->GetTarget(getter_Copies(target));
     aLoadInfo->GetPostDataStream(getter_AddRefs(postStream));
     aLoadInfo->GetHeadersStream(getter_AddRefs(headersStream));
@@ -1673,17 +1053,17 @@ nsDocShell::LoadStream(nsIInputStream* a
   }
 
   uint32_t loadType = LOAD_NORMAL;
   nsCOMPtr<nsIPrincipal> triggeringPrincipal;
   if (aLoadInfo) {
     nsDocShellInfoLoadType lt = nsIDocShellLoadInfo::loadNormal;
     (void)aLoadInfo->GetLoadType(&lt);
     // Get the appropriate LoadType from nsIDocShellLoadInfo type
-    loadType = ConvertDocShellLoadInfoToLoadType(lt);
+    loadType = ConvertDocShellInfoLoadTypeToLoadType(lt);
     aLoadInfo->GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal));
   }
 
   NS_ENSURE_SUCCESS(Stop(nsIWebNavigation::STOP_NETWORK), NS_ERROR_FAILURE);
 
   mLoadType = loadType;
 
   if (!triggeringPrincipal) {
@@ -4590,18 +3970,18 @@ nsDocShell::AddChildSHEntryInternal(nsIS
                                           getter_AddRefs(currentHE));
     NS_ENSURE_TRUE(currentHE, NS_ERROR_FAILURE);
 
     nsCOMPtr<nsISHEntry> currentEntry(do_QueryInterface(currentHE));
     if (currentEntry) {
       uint32_t cloneID = 0;
       nsCOMPtr<nsISHEntry> nextEntry;
       aCloneRef->GetID(&cloneID);
-      rv = CloneAndReplace(currentEntry, this, cloneID, aNewEntry,
-                           aCloneChildren, getter_AddRefs(nextEntry));
+      rv = nsSHistory::CloneAndReplace(currentEntry, this, cloneID,
+        aNewEntry, aCloneChildren, getter_AddRefs(nextEntry));
 
       if (NS_SUCCEEDED(rv)) {
         nsCOMPtr<nsISHistoryInternal> shPrivate =
           do_QueryInterface(mSessionHistory);
         NS_ENSURE_TRUE(shPrivate, NS_ERROR_FAILURE);
         rv = shPrivate->AddEntry(nextEntry, true);
       }
     }
@@ -5028,17 +4408,17 @@ nsDocShell::LoadURIWithOptions(const cha
     popupState = openOverridden;
   }
   nsAutoPopupStatePusher statePusher(popupState);
 
   bool forceAllowDataURI =
     aLoadFlags & LOAD_FLAGS_FORCE_ALLOW_DATA_URI;
 
   // Don't pass certain flags that aren't needed and end up confusing
-  // ConvertLoadTypeToDocShellLoadInfo.  We do need to ensure that they are
+  // ConvertLoadTypeToDocShellInfoLoadType.  We do need to ensure that they are
   // passed to LoadURI though, since it uses them.
   uint32_t extraFlags = (aLoadFlags & EXTRA_LOAD_FLAGS);
   aLoadFlags &= ~EXTRA_LOAD_FLAGS;
 
   nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
   rv = CreateLoadInfo(getter_AddRefs(loadInfo));
   if (NS_FAILED(rv)) {
     return rv;
@@ -5050,17 +4430,17 @@ nsDocShell::LoadURIWithOptions(const cha
    */
   uint32_t loadType;
   if (aLoadFlags & LOAD_FLAGS_ALLOW_MIXED_CONTENT) {
     loadType = MAKE_LOAD_TYPE(LOAD_NORMAL_ALLOW_MIXED_CONTENT, aLoadFlags);
   } else {
     loadType = MAKE_LOAD_TYPE(LOAD_NORMAL, aLoadFlags);
   }
 
-  loadInfo->SetLoadType(ConvertLoadTypeToDocShellLoadInfo(loadType));
+  loadInfo->SetLoadType(ConvertLoadTypeToDocShellInfoLoadType(loadType));
   loadInfo->SetPostDataStream(postStream);
   loadInfo->SetReferrer(aReferringURI);
   loadInfo->SetReferrerPolicy(aReferrerPolicy);
   loadInfo->SetHeadersStream(aHeaderStream);
   loadInfo->SetBaseURI(aBaseURI);
   loadInfo->SetTriggeringPrincipal(aTriggeringPrincipal);
   loadInfo->SetForceAllowDataURI(forceAllowDataURI);
 
@@ -5507,17 +4887,17 @@ nsDocShell::DisplayLoadError(nsresult aE
     prompter->Alert(nullptr, messageStr.get());
   }
 
   return NS_OK;
 }
 
 #define PREF_SAFEBROWSING_ALLOWOVERRIDE "browser.safebrowsing.allowOverride"
 
-NS_IMETHODIMP
+nsresult
 nsDocShell::LoadErrorPage(nsIURI* aURI, const char16_t* aURL,
                           const char* aErrorPage,
                           const char* aErrorType,
                           const char16_t* aDescription,
                           const char* aCSSClass,
                           nsIChannel* aFailedChannel)
 {
 #if defined(DEBUG)
@@ -10326,17 +9706,17 @@ nsDocShell::InternalLoad(nsIURI* aURI,
         SetMaybeResultPrincipalURI(loadInfo, aResultPrincipalURI);
         loadInfo->SetLoadReplace(aLoadReplace);
         loadInfo->SetTriggeringPrincipal(aTriggeringPrincipal);
         loadInfo->SetInheritPrincipal(
           aFlags & INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL);
         // Explicit principal because we do not want any guesses as to what the
         // principal to inherit is: it should be aTriggeringPrincipal.
         loadInfo->SetPrincipalIsExplicit(true);
-        loadInfo->SetLoadType(ConvertLoadTypeToDocShellLoadInfo(LOAD_LINK));
+        loadInfo->SetLoadType(ConvertLoadTypeToDocShellInfoLoadType(LOAD_LINK));
         loadInfo->SetForceAllowDataURI(aFlags & INTERNAL_LOAD_FLAGS_FORCE_ALLOW_DATA_URI);
 
         rv = win->Open(NS_ConvertUTF8toUTF16(spec),
                        aWindowTarget, // window name
                        EmptyString(), // Features
                        loadInfo,
                        true, // aForceNoOpener
                        getter_AddRefs(newWin));
@@ -12579,17 +11959,17 @@ nsDocShell::AddState(JS::Handle<JS::Valu
 
   if (!aReplace) {
     int32_t curIndex = -1;
     rv = rootSH->GetIndex(&curIndex);
     if (NS_SUCCEEDED(rv) && curIndex > -1) {
       internalSH->EvictOutOfRangeContentViewers(curIndex);
     }
   } else {
-    nsCOMPtr<nsISHEntry> rootSHEntry = GetRootSHEntry(newSHEntry);
+    nsCOMPtr<nsISHEntry> rootSHEntry = nsSHistory::GetRootSHEntry(newSHEntry);
 
     int32_t index = -1;
     rv = rootSH->GetIndexOfEntry(rootSHEntry, &index);
     if (NS_SUCCEEDED(rv) && index > -1) {
       internalSH->ReplaceEntry(index, rootSHEntry);
     }
   }
 
@@ -12879,18 +12259,18 @@ nsDocShell::AddToSessionHistory(nsIURI* 
 
   if (root == static_cast<nsIDocShellTreeItem*>(this) && mSessionHistory) {
     // If we need to clone our children onto the new session
     // history entry, do so now.
     if (aCloneChildren && mOSHE) {
       uint32_t cloneID;
       mOSHE->GetID(&cloneID);
       nsCOMPtr<nsISHEntry> newEntry;
-      CloneAndReplace(mOSHE, this, cloneID, entry, true,
-                      getter_AddRefs(newEntry));
+      nsSHistory::CloneAndReplace(mOSHE, this, cloneID, entry, true,
+                                  getter_AddRefs(newEntry));
       NS_ASSERTION(entry == newEntry,
                    "The new session history should be in the new entry");
     }
 
     // This is the root docshell
     bool addToSHistory = !LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY);
     if (!addToSHistory) {
       // Replace current entry in session history; If the requested index is
@@ -13125,275 +12505,59 @@ nsDocShell::PersistLayoutHistoryState()
     if (scrollRestorationIsManual && layoutState) {
       layoutState->ResetScrollState();
     }
   }
 
   return rv;
 }
 
-/* static */ nsresult
-nsDocShell::WalkHistoryEntries(nsISHEntry* aRootEntry,
-                               nsDocShell* aRootShell,
-                               WalkHistoryEntriesFunc aCallback,
-                               void* aData)
-{
-  NS_ENSURE_TRUE(aRootEntry, NS_ERROR_FAILURE);
-
-  nsCOMPtr<nsISHContainer> container(do_QueryInterface(aRootEntry));
-  if (!container) {
-    return NS_ERROR_FAILURE;
-  }
-
-  int32_t childCount;
-  container->GetChildCount(&childCount);
-  for (int32_t i = 0; i < childCount; i++) {
-    nsCOMPtr<nsISHEntry> childEntry;
-    container->GetChildAt(i, getter_AddRefs(childEntry));
-    if (!childEntry) {
-      // childEntry can be null for valid reasons, for example if the
-      // docshell at index i never loaded anything useful.
-      // Remember to clone also nulls in the child array (bug 464064).
-      aCallback(nullptr, nullptr, i, aData);
-      continue;
-    }
-
-    nsDocShell* childShell = nullptr;
-    if (aRootShell) {
-      // Walk the children of aRootShell and see if one of them
-      // has srcChild as a SHEntry.
-      nsTObserverArray<nsDocLoader*>::ForwardIterator iter(
-        aRootShell->mChildList);
-      while (iter.HasMore()) {
-        nsDocShell* child = static_cast<nsDocShell*>(iter.GetNext());
-
-        if (child->HasHistoryEntry(childEntry)) {
-          childShell = child;
-          break;
-        }
-      }
-    }
-    nsresult rv = aCallback(childEntry, childShell, i, aData);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  return NS_OK;
-}
-
-// callback data for WalkHistoryEntries
-struct MOZ_STACK_CLASS CloneAndReplaceData
-{
-  CloneAndReplaceData(uint32_t aCloneID, nsISHEntry* aReplaceEntry,
-                      bool aCloneChildren, nsISHEntry* aDestTreeParent)
-    : cloneID(aCloneID)
-    , cloneChildren(aCloneChildren)
-    , replaceEntry(aReplaceEntry)
-    , destTreeParent(aDestTreeParent)
-  {
-  }
-
-  uint32_t cloneID;
-  bool cloneChildren;
-  nsISHEntry* replaceEntry;
-  nsISHEntry* destTreeParent;
-  nsCOMPtr<nsISHEntry> resultEntry;
-};
-
-/* static */ nsresult
-nsDocShell::CloneAndReplaceChild(nsISHEntry* aEntry, nsDocShell* aShell,
-                                 int32_t aEntryIndex, void* aData)
-{
-  nsCOMPtr<nsISHEntry> dest;
-
-  CloneAndReplaceData* data = static_cast<CloneAndReplaceData*>(aData);
-  uint32_t cloneID = data->cloneID;
-  nsISHEntry* replaceEntry = data->replaceEntry;
-
-  nsCOMPtr<nsISHContainer> container = do_QueryInterface(data->destTreeParent);
-  if (!aEntry) {
-    if (container) {
-      container->AddChild(nullptr, aEntryIndex);
-    }
-    return NS_OK;
-  }
-
-  uint32_t srcID;
-  aEntry->GetID(&srcID);
-
-  nsresult rv = NS_OK;
-  if (srcID == cloneID) {
-    // Replace the entry
-    dest = replaceEntry;
-  } else {
-    // Clone the SHEntry...
-    rv = aEntry->Clone(getter_AddRefs(dest));
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-  dest->SetIsSubFrame(true);
-
-  if (srcID != cloneID || data->cloneChildren) {
-    // Walk the children
-    CloneAndReplaceData childData(cloneID, replaceEntry,
-                                  data->cloneChildren, dest);
-    rv = WalkHistoryEntries(aEntry, aShell,
-                            CloneAndReplaceChild, &childData);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  if (srcID != cloneID && aShell) {
-    aShell->SwapHistoryEntries(aEntry, dest);
-  }
-
-  if (container) {
-    container->AddChild(dest, aEntryIndex);
-  }
-
-  data->resultEntry = dest;
-  return rv;
-}
-
-/* static */ nsresult
-nsDocShell::CloneAndReplace(nsISHEntry* aSrcEntry,
-                            nsDocShell* aSrcShell,
-                            uint32_t aCloneID,
-                            nsISHEntry* aReplaceEntry,
-                            bool aCloneChildren,
-                            nsISHEntry** aResultEntry)
-{
-  NS_ENSURE_ARG_POINTER(aResultEntry);
-  NS_ENSURE_TRUE(aReplaceEntry, NS_ERROR_FAILURE);
-
-  CloneAndReplaceData data(aCloneID, aReplaceEntry, aCloneChildren, nullptr);
-  nsresult rv = CloneAndReplaceChild(aSrcEntry, aSrcShell, 0, &data);
-
-  data.resultEntry.swap(*aResultEntry);
-  return rv;
-}
-
 void
 nsDocShell::SwapHistoryEntries(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry)
 {
   if (aOldEntry == mOSHE) {
     mOSHE = aNewEntry;
   }
 
   if (aOldEntry == mLSHE) {
     mLSHE = aNewEntry;
   }
 }
 
-struct SwapEntriesData
-{
-  nsDocShell* ignoreShell;     // constant; the shell to ignore
-  nsISHEntry* destTreeRoot;    // constant; the root of the dest tree
-  nsISHEntry* destTreeParent;  // constant; the node under destTreeRoot
-                               // whose children will correspond to aEntry
-};
-
-nsresult
-nsDocShell::SetChildHistoryEntry(nsISHEntry* aEntry, nsDocShell* aShell,
-                                 int32_t aEntryIndex, void* aData)
-{
-  SwapEntriesData* data = static_cast<SwapEntriesData*>(aData);
-  nsDocShell* ignoreShell = data->ignoreShell;
-
-  if (!aShell || aShell == ignoreShell) {
-    return NS_OK;
-  }
-
-  nsISHEntry* destTreeRoot = data->destTreeRoot;
-
-  nsCOMPtr<nsISHEntry> destEntry;
-  nsCOMPtr<nsISHContainer> container = do_QueryInterface(data->destTreeParent);
-
-  if (container) {
-    // aEntry is a clone of some child of destTreeParent, but since the
-    // trees aren't necessarily in sync, we'll have to locate it.
-    // Note that we could set aShell's entry to null if we don't find a
-    // corresponding entry under destTreeParent.
-
-    uint32_t targetID, id;
-    aEntry->GetID(&targetID);
-
-    // First look at the given index, since this is the common case.
-    nsCOMPtr<nsISHEntry> entry;
-    container->GetChildAt(aEntryIndex, getter_AddRefs(entry));
-    if (entry && NS_SUCCEEDED(entry->GetID(&id)) && id == targetID) {
-      destEntry.swap(entry);
-    } else {
-      int32_t childCount;
-      container->GetChildCount(&childCount);
-      for (int32_t i = 0; i < childCount; ++i) {
-        container->GetChildAt(i, getter_AddRefs(entry));
-        if (!entry) {
-          continue;
-        }
-
-        entry->GetID(&id);
-        if (id == targetID) {
-          destEntry.swap(entry);
-          break;
-        }
-      }
-    }
-  } else {
-    destEntry = destTreeRoot;
-  }
-
-  aShell->SwapHistoryEntries(aEntry, destEntry);
-
-  // Now handle the children of aEntry.
-  SwapEntriesData childData = { ignoreShell, destTreeRoot, destEntry };
-  return WalkHistoryEntries(aEntry, aShell, SetChildHistoryEntry, &childData);
-}
-
-static nsISHEntry*
-GetRootSHEntry(nsISHEntry* aEntry)
-{
-  nsCOMPtr<nsISHEntry> rootEntry = aEntry;
-  nsISHEntry* result = nullptr;
-  while (rootEntry) {
-    result = rootEntry;
-    result->GetParent(getter_AddRefs(rootEntry));
-  }
-
-  return result;
-}
-
 void
 nsDocShell::SetHistoryEntry(nsCOMPtr<nsISHEntry>* aPtr, nsISHEntry* aEntry)
 {
   // We need to sync up the docshell and session history trees for
   // subframe navigation.  If the load was in a subframe, we forward up to
   // the root docshell, which will then recursively sync up all docshells
   // to their corresponding entries in the new session history tree.
   // If we don't do this, then we can cache a content viewer on the wrong
   // cloned entry, and subsequently restore it at the wrong time.
 
-  nsISHEntry* newRootEntry = GetRootSHEntry(aEntry);
+  nsISHEntry* newRootEntry = nsSHistory::GetRootSHEntry(aEntry);
   if (newRootEntry) {
     // newRootEntry is now the new root entry.
     // Find the old root entry as well.
 
     // Need a strong ref. on |oldRootEntry| so it isn't destroyed when
     // SetChildHistoryEntry() does SwapHistoryEntries() (bug 304639).
-    nsCOMPtr<nsISHEntry> oldRootEntry = GetRootSHEntry(*aPtr);
+    nsCOMPtr<nsISHEntry> oldRootEntry = nsSHistory::GetRootSHEntry(*aPtr);
     if (oldRootEntry) {
       nsCOMPtr<nsIDocShellTreeItem> rootAsItem;
       GetSameTypeRootTreeItem(getter_AddRefs(rootAsItem));
       nsCOMPtr<nsIDocShell> rootShell = do_QueryInterface(rootAsItem);
       if (rootShell) { // if we're the root just set it, nothing to swap
-        SwapEntriesData data = { this, newRootEntry };
+        nsSHistory::SwapEntriesData data = { this, newRootEntry };
         nsIDocShell* rootIDocShell = static_cast<nsIDocShell*>(rootShell);
         nsDocShell* rootDocShell = static_cast<nsDocShell*>(rootIDocShell);
 
 #ifdef DEBUG
         nsresult rv =
 #endif
-        SetChildHistoryEntry(oldRootEntry, rootDocShell, 0, &data);
+        nsSHistory::SetChildHistoryEntry(oldRootEntry, rootDocShell, 0, &data);
         NS_ASSERTION(NS_SUCCEEDED(rv), "SetChildHistoryEntry failed");
       }
     }
   }
 
   *aPtr = aEntry;
 }
 
@@ -13738,17 +12902,17 @@ nsDocShell::ConfirmRepost(bool* aRepost)
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   *aRepost = (buttonPressed == 0);
   return NS_OK;
 }
 
-NS_IMETHODIMP
+nsresult
 nsDocShell::GetPromptAndStringBundle(nsIPrompt** aPrompt,
                                      nsIStringBundle** aStringBundle)
 {
   NS_ENSURE_SUCCESS(GetInterface(NS_GET_IID(nsIPrompt), (void**)aPrompt),
                     NS_ERROR_FAILURE);
 
   nsCOMPtr<nsIStringBundleService> stringBundleService =
     mozilla::services::GetStringBundleService();
@@ -13756,54 +12920,26 @@ nsDocShell::GetPromptAndStringBundle(nsI
 
   NS_ENSURE_SUCCESS(
     stringBundleService->CreateBundle(kAppstringsBundleURL, aStringBundle),
     NS_ERROR_FAILURE);
 
   return NS_OK;
 }
 
-NS_IMETHODIMP
-nsDocShell::GetChildOffset(nsIDOMNode* aChild, nsIDOMNode* aParent,
-                           int32_t* aOffset)
-{
-  NS_ENSURE_ARG_POINTER(aChild || aParent);
-
-  nsCOMPtr<nsIDOMNodeList> childNodes;
-  NS_ENSURE_SUCCESS(aParent->GetChildNodes(getter_AddRefs(childNodes)),
-                    NS_ERROR_FAILURE);
-  NS_ENSURE_TRUE(childNodes, NS_ERROR_FAILURE);
-
-  int32_t i = 0;
-
-  for (; true; i++) {
-    nsCOMPtr<nsIDOMNode> childNode;
-    NS_ENSURE_SUCCESS(childNodes->Item(i, getter_AddRefs(childNode)),
-                      NS_ERROR_FAILURE);
-    NS_ENSURE_TRUE(childNode, NS_ERROR_FAILURE);
-
-    if (childNode.get() == aChild) {
-      *aOffset = i;
-      return NS_OK;
-    }
-  }
-
-  return NS_ERROR_FAILURE;
-}
-
 nsIScrollableFrame*
 nsDocShell::GetRootScrollFrame()
 {
   nsCOMPtr<nsIPresShell> shell = GetPresShell();
   NS_ENSURE_TRUE(shell, nullptr);
 
   return shell->GetRootScrollFrameAsScrollable();
 }
 
-NS_IMETHODIMP
+nsresult
 nsDocShell::EnsureScriptEnvironment()
 {
   if (mScriptGlobal) {
     return NS_OK;
   }
 
   if (mIsBeingDestroyed) {
     return NS_ERROR_NOT_AVAILABLE;
@@ -13832,17 +12968,17 @@ nsDocShell::EnsureScriptEnvironment()
   MOZ_ASSERT(mScriptGlobal);
 
   mScriptGlobal->SetDocShell(this);
 
   // Ensure the script object is set up to run script.
   return mScriptGlobal->EnsureScriptEnvironment();
 }
 
-NS_IMETHODIMP
+nsresult
 nsDocShell::EnsureEditorData()
 {
   bool openDocHasDetachedEditor = mOSHE && mOSHE->HasDetachedEditor();
   if (!mEditorData && !mIsBeingDestroyed && !openDocHasDetachedEditor) {
     // We shouldn't recreate the editor data if it already exists, or
     // we're shutting down, or we already have a detached editor data
     // stored in the session history. We should only have one editordata
     // per docshell.
@@ -13857,17 +12993,17 @@ nsDocShell::EnsureTransferableHookData()
 {
   if (!mTransferableHookData) {
     mTransferableHookData = new nsTransferableHookData();
   }
 
   return NS_OK;
 }
 
-NS_IMETHODIMP
+nsresult
 nsDocShell::EnsureFind()
 {
   nsresult rv;
   if (!mFind) {
     mFind = do_CreateInstance("@mozilla.org/embedcomp/find;1", &rv);
     if (NS_FAILED(rv)) {
       return rv;
     }
@@ -13941,60 +13077,16 @@ NS_IMETHODIMP
 nsDocShell::SetLayoutHistoryState(nsILayoutHistoryState* aLayoutHistoryState)
 {
   if (mOSHE) {
     mOSHE->SetLayoutHistoryState(aLayoutHistoryState);
   }
   return NS_OK;
 }
 
-nsRefreshTimer::nsRefreshTimer(nsDocShell* aDocShell,
-                               nsIURI* aURI,
-                               nsIPrincipal* aPrincipal,
-                               int32_t aDelay, bool aRepeat, bool aMetaRefresh)
-  : mDocShell(aDocShell), mURI(aURI), mPrincipal(aPrincipal),
-    mDelay(aDelay), mRepeat(aRepeat),
-    mMetaRefresh(aMetaRefresh)
-{
-}
-
-nsRefreshTimer::~nsRefreshTimer()
-{
-}
-
-NS_IMPL_ADDREF(nsRefreshTimer)
-NS_IMPL_RELEASE(nsRefreshTimer)
-
-NS_INTERFACE_MAP_BEGIN(nsRefreshTimer)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITimerCallback)
-  NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
-  NS_INTERFACE_MAP_ENTRY(nsINamed)
-NS_INTERFACE_MAP_END_THREADSAFE
-
-NS_IMETHODIMP
-nsRefreshTimer::Notify(nsITimer* aTimer)
-{
-  NS_ASSERTION(mDocShell, "DocShell is somehow null");
-
-  if (mDocShell && aTimer) {
-    // Get the delay count to determine load type
-    uint32_t delay = 0;
-    aTimer->GetDelay(&delay);
-    mDocShell->ForceRefreshURIFromTimer(mURI, mPrincipal, delay, mMetaRefresh, aTimer);
-  }
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsRefreshTimer::GetName(nsACString& aName)
-{
-  aName.AssignLiteral("nsRefreshTimer");
-  return NS_OK;
-}
-
 nsDocShell::InterfaceRequestorProxy::InterfaceRequestorProxy(
     nsIInterfaceRequestor* aRequestor)
 {
   if (aRequestor) {
     mWeakPtr = do_GetWeakReference(aRequestor);
   }
 }
 
@@ -14496,16 +13588,24 @@ nsDocShell::OnLinkClick(nsIContent* aCon
   nsCOMPtr<nsIRunnable> ev =
     new OnLinkClickEvent(this, aContent, aURI, target.get(), aFileName,
                          aPostDataStream, aPostDataStreamLength,
                          aHeadersDataStream, noOpenerImplied,
                          aIsTrusted, aTriggeringPrincipal);
   return DispatchToTabGroup(TaskCategory::UI, ev.forget());
 }
 
+static bool
+IsElementAnchorOrArea(nsIContent* aContent)
+{
+  // Make sure we are dealing with either an <A> or <AREA> element in the HTML
+  // or XHTML namespace.
+  return aContent->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area);
+}
+
 NS_IMETHODIMP
 nsDocShell::OnLinkClickSync(nsIContent* aContent,
                             nsIURI* aURI,
                             const char16_t* aTargetSpec,
                             const nsAString& aFileName,
                             nsIInputStream* aPostDataStream,
                             int64_t aPostDataStreamLength,
                             nsIInputStream* aHeadersDataStream,
@@ -14554,17 +13654,17 @@ nsDocShell::OnLinkClickSync(nsIContent* 
         if (NS_SUCCEEDED(rv) && !isExposed) {
           return extProtService->LoadURI(aURI, this);
         }
       }
     }
   }
 
   uint32_t flags = INTERNAL_LOAD_FLAGS_NONE;
-  if (IsElementAnchor(aContent)) {
+  if (IsElementAnchorOrArea(aContent)) {
     MOZ_ASSERT(aContent->IsHTMLElement());
     nsAutoString referrer;
     aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::rel, referrer);
     nsWhitespaceTokenizerTemplate<nsContentUtils::IsHTMLWhitespace> tok(referrer);
     while (tok.hasMoreTokens()) {
       const nsAString& token = tok.nextToken();
       if (token.LowerCaseEqualsLiteral("noreferrer")) {
         flags |= INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER |
@@ -14601,17 +13701,17 @@ nsDocShell::OnLinkClickSync(nsIContent* 
   }
 
   nsCOMPtr<nsIURI> referer = refererDoc->GetDocumentURI();
   uint32_t refererPolicy = refererDoc->GetReferrerPolicy();
 
   // get referrer attribute from clicked link and parse it
   // if per element referrer is enabled, the element referrer overrules
   // the document wide referrer
-  if (IsElementAnchor(aContent)) {
+  if (IsElementAnchorOrArea(aContent)) {
     net::ReferrerPolicy refPolEnum = aContent->AsElement()->GetReferrerPolicyAsEnum();
     if (refPolEnum != net::RP_Unset) {
       refererPolicy = refPolEnum;
     }
   }
 
   // referer could be null here in some odd cases, but that's ok,
   // we'll just load the link w/o sending a referer in those cases.
@@ -14670,17 +13770,17 @@ nsDocShell::OnLinkClickSync(nsIContent* 
                              true,                      // first party site
                              VoidString(),              // No srcdoc
                              this,                      // We are the source
                              nullptr,                   // baseURI not needed
                              true,                      // Check for prerendered doc
                              aDocShell,                 // DocShell out-param
                              aRequest);                 // Request out-param
   if (NS_SUCCEEDED(rv)) {
-    DispatchPings(this, aContent, aURI, referer, refererPolicy);
+    nsPingListener::DispatchPings(this, aContent, aURI, referer, refererPolicy);
   }
   return rv;
 }
 
 NS_IMETHODIMP
 nsDocShell::OnOverLink(nsIContent* aContent,
                        nsIURI* aURI,
                        const char16_t* aTargetSpec)
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -2,150 +2,122 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsDocShell_h__
 #define nsDocShell_h__
 
-#include "nsITimer.h"
-#include "nsContentPolicyUtils.h"
-#include "nsIDocShell.h"
-#include "nsIDocShellTreeItem.h"
-#include "nsIBaseWindow.h"
-#include "nsINetworkInterceptController.h"
-#include "nsIScrollable.h"
-#include "nsITextScroll.h"
-#include "nsIContentViewerContainer.h"
-#include "nsIDOMStorageManager.h"
-#include "nsDocLoader.h"
 #include "mozilla/BasePrincipal.h"
+#include "mozilla/LinkedList.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/Move.h"
+#include "mozilla/TimeStamp.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/WeakPtr.h"
-#include "mozilla/TimeStamp.h"
-#include "GeckoProfiler.h"
+
 #include "mozilla/dom/ProfileTimelineMarkerBinding.h"
-#include "mozilla/LinkedList.h"
-#include "jsapi.h"
 
-// Helper Classes
+#include "nsIAuthPromptProvider.h"
+#include "nsIBaseWindow.h"
+#include "nsIClipboardCommands.h"
+#include "nsIContentViewerContainer.h"
+#include "nsIDeprecationWarner.h"
+#include "nsIDocCharset.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellLoadInfo.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDOMStorageManager.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsILinkHandler.h"
+#include "nsILoadContext.h"
+#include "nsILoadURIDelegate.h"
+#include "nsINetworkInterceptController.h"
+#include "nsIRefreshURI.h"
+#include "nsIScrollable.h"
+#include "nsITabParent.h"
+#include "nsITextScroll.h"
+#include "nsIWebNavigation.h"
+#include "nsIWebPageDescriptor.h"
+#include "nsIWebProgressListener.h"
+#include "nsIWebShellServices.h"
+
+#include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
+#include "nsContentPolicyUtils.h"
+#include "nsContentUtils.h"
+#include "nsCRT.h"
+#include "nsDocLoader.h"
 #include "nsPoint.h" // mCurrent/mDefaultScrollbarPreferences
+#include "nsRect.h"
 #include "nsString.h"
-#include "nsAutoPtr.h"
 #include "nsThreadUtils.h"
-#include "nsContentUtils.h"
+
+#include "GeckoProfiler.h"
+#include "jsapi.h"
+#include "prtime.h"
+#include "Units.h"
+
 #include "timeline/ObservedDocShell.h"
 #include "timeline/TimelineConsumers.h"
 #include "timeline/TimelineMarker.h"
 
-// Threshold value in ms for META refresh based redirects
-#define REFRESH_REDIRECT_TIMER 15000
-
 // Interfaces Needed
-#include "nsIDocCharset.h"
-#include "nsIInterfaceRequestor.h"
-#include "nsINamed.h"
-#include "nsIRefreshURI.h"
-#include "nsIWebNavigation.h"
-#include "nsIWebPageDescriptor.h"
-#include "nsIWebProgressListener.h"
-#include "nsIDocShellLoadInfo.h"
-#include "nsIAuthPromptProvider.h"
-#include "nsILoadContext.h"
-#include "nsIWebShellServices.h"
-#include "nsILinkHandler.h"
-#include "nsIClipboardCommands.h"
-#include "nsITabParent.h"
-#include "nsCRT.h"
-#include "prtime.h"
-#include "nsRect.h"
-#include "Units.h"
-#include "nsIDeprecationWarner.h"
-#include "nsILoadURIDelegate.h"
 
 namespace mozilla {
 class Encoding;
 class HTMLEditor;
 enum class TaskCategory;
 namespace dom {
 class ClientInfo;
 class ClientSource;
 class EventTarget;
 class PendingGlobalHistoryEntry;
 typedef uint32_t ScreenOrientationInternal;
 } // namespace dom
 } // namespace mozilla
 
-class nsDocShell;
-class nsDOMNavigationTiming;
-class nsGlobalWindowOuter;
-class nsGlobalWindowInner;
-class nsIController;
-class nsIScrollableFrame;
-class OnLinkClickEvent;
-class nsDSURIContentListener;
-class nsDocShellEditorData;
 class nsIClipboardDragDropHookList;
 class nsICommandManager;
 class nsIContentViewer;
+class nsIController;
+class nsIDocShellTreeOwner;
 class nsIDocument;
 class nsIDOMNode;
-class nsIDocShellTreeOwner;
 class nsIGlobalHistory2;
 class nsIHttpChannel;
 class nsIMutableArray;
 class nsIPrompt;
+class nsIScrollableFrame;
+class nsISecureBrowserUI;
 class nsISHistory;
-class nsISecureBrowserUI;
 class nsIStringBundle;
 class nsIURIFixup;
 class nsIURILoader;
 class nsIWebBrowserFind;
 class nsIWidget;
+
+class nsDocShell;
+class nsDocShellEditorData;
+class nsDOMNavigationTiming;
+class nsDSURIContentListener;
+class nsGlobalWindowInner;
+class nsGlobalWindowOuter;
+
 class FramingChecker;
+class OnLinkClickEvent;
 
 /* internally used ViewMode types */
 enum ViewMode
 {
   viewNormal = 0x0,
   viewSource = 0x1
 };
 
-class nsRefreshTimer : public nsITimerCallback
-                     , public nsINamed
-{
-public:
-  nsRefreshTimer(nsDocShell* aDocShell,
-                 nsIURI* aURI,
-                 nsIPrincipal* aPrincipal,
-                 int32_t aDelay,
-                 bool aRepeat,
-                 bool aMetaRefresh);
-
-  NS_DECL_THREADSAFE_ISUPPORTS
-  NS_DECL_NSITIMERCALLBACK
-  NS_DECL_NSINAMED
-
-  int32_t GetDelay() { return mDelay ;}
-
-  RefPtr<nsDocShell> mDocShell;
-  nsCOMPtr<nsIURI> mURI;
-  nsCOMPtr<nsIPrincipal> mPrincipal;
-  int32_t mDelay;
-  bool mRepeat;
-  bool mMetaRefresh;
-
-protected:
-  virtual ~nsRefreshTimer();
-};
-
 enum eCharsetReloadState
 {
   eCharsetReloadInit,
   eCharsetReloadRequested,
   eCharsetReloadStopOrigional
 };
 
 class nsDocShell final
@@ -165,30 +137,48 @@ class nsDocShell final
   , public nsIWebShellServices
   , public nsILinkHandler
   , public nsIClipboardCommands
   , public nsIDOMStorageManager
   , public nsINetworkInterceptController
   , public nsIDeprecationWarner
   , public mozilla::SupportsWeakPtr<nsDocShell>
 {
-  friend class nsDSURIContentListener;
-  friend class FramingChecker;
-  using Encoding = mozilla::Encoding;
+public:
+  // Event type dispatched by RestorePresentation
+  class RestorePresentationEvent : public mozilla::Runnable
+  {
+  public:
+    NS_DECL_NSIRUNNABLE
+    explicit RestorePresentationEvent(nsDocShell* aDs)
+      : mozilla::Runnable("nsDocShell::RestorePresentationEvent")
+      , mDocShell(aDs)
+    {
+    }
+    void Revoke() { mDocShell = nullptr; }
+  private:
+    RefPtr<nsDocShell> mDocShell;
+  };
 
-public:
+  class InterfaceRequestorProxy : public nsIInterfaceRequestor
+  {
+  public:
+    explicit InterfaceRequestorProxy(nsIInterfaceRequestor* aRequestor);
+    NS_DECL_THREADSAFE_ISUPPORTS
+    NS_DECL_NSIINTERFACEREQUESTOR
+
+  private:
+    virtual ~InterfaceRequestorProxy();
+    InterfaceRequestorProxy() {}
+    nsWeakPtr mWeakPtr;
+  };
+
   MOZ_DECLARE_WEAKREFERENCE_TYPENAME(nsDocShell)
-
-  nsDocShell();
-
-  virtual nsresult Init() override;
-
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsDocShell, nsDocLoader)
-
   NS_DECL_NSIDOCSHELL
   NS_DECL_NSIDOCSHELLTREEITEM
   NS_DECL_NSIWEBNAVIGATION
   NS_DECL_NSIBASEWINDOW
   NS_DECL_NSISCROLLABLE
   NS_DECL_NSITEXTSCROLL
   NS_DECL_NSIDOCCHARSET
   NS_DECL_NSIINTERFACEREQUESTOR
@@ -196,29 +186,33 @@ public:
   NS_DECL_NSIREFRESHURI
   NS_DECL_NSICONTENTVIEWERCONTAINER
   NS_DECL_NSIWEBPAGEDESCRIPTOR
   NS_DECL_NSIAUTHPROMPTPROVIDER
   NS_DECL_NSICLIPBOARDCOMMANDS
   NS_DECL_NSIWEBSHELLSERVICES
   NS_DECL_NSINETWORKINTERCEPTCONTROLLER
   NS_DECL_NSIDEPRECATIONWARNER
+
   NS_FORWARD_SAFE_NSIDOMSTORAGEMANAGER(TopSessionStorageManager())
 
+  // Need to implement (and forward) nsISecurityEventSink, because
+  // nsIWebProgressListener has methods with identical names...
+  NS_FORWARD_NSISECURITYEVENTSINK(nsDocLoader::)
+
+  nsDocShell();
+  virtual nsresult Init() override;
+
   NS_IMETHOD Stop() override
   {
     // Need this here because otherwise nsIWebNavigation::Stop
     // overrides the docloader's Stop()
     return nsDocLoader::Stop();
   }
 
-  // Need to implement (and forward) nsISecurityEventSink, because
-  // nsIWebProgressListener has methods with identical names...
-  NS_FORWARD_NSISECURITYEVENTSINK(nsDocLoader::)
-
   // nsILinkHandler
   NS_IMETHOD OnLinkClick(nsIContent* aContent,
                          nsIURI* aURI,
                          const char16_t* aTargetSpec,
                          const nsAString& aFileName,
                          nsIInputStream* aPostDataStream,
                          int64_t aPostDataStreamLength,
                          nsIInputStream* aHeadersDataStream,
@@ -235,81 +229,77 @@ public:
                              nsIDocShell** aDocShell = 0,
                              nsIRequest** aRequest = 0,
                              nsIPrincipal* aTriggeringPrincipal = nullptr) override;
   NS_IMETHOD OnOverLink(nsIContent* aContent,
                         nsIURI* aURI,
                         const char16_t* aTargetSpec) override;
   NS_IMETHOD OnLeaveLink() override;
 
-  nsDocShellInfoLoadType ConvertLoadTypeToDocShellLoadInfo(uint32_t aLoadType);
-  uint32_t ConvertDocShellLoadInfoToLoadType(
-    nsDocShellInfoLoadType aDocShellLoadType);
-
   // Don't use NS_DECL_NSILOADCONTEXT because some of nsILoadContext's methods
   // are shared with nsIDocShell (appID, etc.) and can't be declared twice.
   NS_IMETHOD GetAssociatedWindow(mozIDOMWindowProxy**) override;
   NS_IMETHOD GetTopWindow(mozIDOMWindowProxy**) override;
   NS_IMETHOD GetTopFrameElement(nsIDOMElement**) override;
   NS_IMETHOD GetNestedFrameId(uint64_t*) override;
   NS_IMETHOD GetIsContent(bool*) override;
   NS_IMETHOD GetUsePrivateBrowsing(bool*) override;
   NS_IMETHOD SetUsePrivateBrowsing(bool) override;
   NS_IMETHOD SetPrivateBrowsing(bool) override;
   NS_IMETHOD GetUseRemoteTabs(bool*) override;
   NS_IMETHOD SetRemoteTabs(bool) override;
   NS_IMETHOD GetScriptableOriginAttributes(JS::MutableHandle<JS::Value>) override;
+  NS_IMETHOD_(void) GetOriginAttributes(mozilla::OriginAttributes& aAttrs) override;
 
   // Restores a cached presentation from history (mLSHE).
   // This method swaps out the content viewer and simulates loads for
   // subframes. It then simulates the completion of the toplevel load.
   nsresult RestoreFromHistory();
 
   // Perform a URI load from a refresh timer. This is just like the
   // ForceRefreshURI method on nsIRefreshURI, but makes sure to take
   // the timer involved out of mRefreshURIList if it's there.
   // aTimer must not be null.
   nsresult ForceRefreshURIFromTimer(nsIURI* aURI, nsIPrincipal* aPrincipal,
                                     int32_t aDelay,
                                     bool aMetaRefresh, nsITimer* aTimer);
 
-  friend class OnLinkClickEvent;
-
-  static bool SandboxFlagsImplyCookies(const uint32_t &aSandboxFlags);
-
   // We need dummy OnLocationChange in some cases to update the UI without
   // updating security info.
   void FireDummyOnLocationChange()
   {
     FireOnLocationChange(this, nullptr, mCurrentURI,
                          LOCATION_CHANGE_SAME_DOCUMENT);
   }
 
   nsresult HistoryTransactionRemoved(int32_t aIndex);
 
   // Notify Scroll observers when an async panning/zooming transform
   // has started being applied
   void NotifyAsyncPanZoomStarted();
+
   // Notify Scroll observers when an async panning/zooming transform
   // is no longer applied
   void NotifyAsyncPanZoomStopped();
 
   void SetInFrameSwap(bool aInSwap)
   {
     mInFrameSwap = aInSwap;
   }
   bool InFrameSwap();
 
-  const Encoding* GetForcedCharset() { return mForcedCharset; }
+  const mozilla::Encoding* GetForcedCharset() { return mForcedCharset; }
 
   mozilla::HTMLEditor* GetHTMLEditorInternal();
   nsresult SetHTMLEditorInternal(mozilla::HTMLEditor* aHTMLEditor);
 
   nsDOMNavigationTiming* GetNavigationTiming() const;
 
+  nsresult SetOriginAttributes(const mozilla::OriginAttributes& aAttrs);
+
   /**
    * Get the list of ancestor principals for this docshell.  The list is meant
    * to be the list of principals of the documents this docshell is "nested
    * through" in the sense of
    * <https://html.spec.whatwg.org/multipage/browsers.html#browsing-context-nested-through>.
    * In practice, it is defined as follows:
    *
    * If this is an <iframe mozbrowser> or a toplevel content docshell
@@ -357,31 +347,48 @@ public:
    *
    * This method steals the data from the passed-in array.
    */
   void SetAncestorOuterWindowIDs(nsTArray<uint64_t>&& aAncestorOuterWindowIDs)
   {
     mAncestorOuterWindowIDs = mozilla::Move(aAncestorOuterWindowIDs);
   }
 
-private:
-  bool CanSetOriginAttributes();
-
-public:
-  const mozilla::OriginAttributes&
-  GetOriginAttributes()
+  const mozilla::OriginAttributes& GetOriginAttributes()
   {
     return mOriginAttributes;
   }
 
-  nsresult SetOriginAttributes(const mozilla::OriginAttributes& aAttrs);
+  // Determine whether this docshell corresponds to the given history entry,
+  // via having a pointer to it in mOSHE or mLSHE.
+  bool HasHistoryEntry(nsISHEntry* aEntry) const
+  {
+    return aEntry && (aEntry == mOSHE || aEntry == mLSHE);
+  }
+
+  // Update any pointers (mOSHE or mLSHE) to aOldEntry to point to aNewEntry
+  void SwapHistoryEntries(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry);
+
+  static bool SandboxFlagsImplyCookies(const uint32_t &aSandboxFlags);
 
-private:
-  // An observed docshell wrapper is created when recording markers is enabled.
-  mozilla::UniquePtr<mozilla::ObservedDocShell> mObserved;
+  // Tell the favicon service that aNewURI has the same favicon as aOldURI.
+  static void CopyFavicon(nsIURI* aOldURI,
+                          nsIURI* aNewURI,
+                          nsIPrincipal* aLoadingPrincipal,
+                          bool aInPrivateBrowsing);
+
+  static nsDocShell* Cast(nsIDocShell* aDocShell)
+  {
+    return static_cast<nsDocShell*>(aDocShell);
+  }
+
+private: // member functions
+  friend class nsDSURIContentListener;
+  friend class FramingChecker;
+  friend class OnLinkClickEvent;
 
   // It is necessary to allow adding a timeline marker wherever a docshell
   // instance is available. This operation happens frequently and needs to
   // be very fast, so instead of using a Map or having to search for some
   // docshell-specific markers storage, a pointer to an `ObservedDocShell` is
   // is stored on docshells directly.
   friend void mozilla::TimelineConsumers::AddConsumer(nsDocShell*);
   friend void mozilla::TimelineConsumers::RemoveConsumer(nsDocShell*);
@@ -390,60 +397,119 @@ private:
   friend void mozilla::TimelineConsumers::AddMarkerForDocShell(
     nsDocShell*, const char*, const TimeStamp&, MarkerTracingType,
     MarkerStackRequest);
   friend void mozilla::TimelineConsumers::AddMarkerForDocShell(
     nsDocShell*, UniquePtr<AbstractTimelineMarker>&&);
   friend void mozilla::TimelineConsumers::PopMarkers(nsDocShell*,
     JSContext*, nsTArray<dom::ProfileTimelineMarker>&);
 
-public:
-  // Tell the favicon service that aNewURI has the same favicon as aOldURI.
-  static void CopyFavicon(nsIURI* aOldURI,
-                          nsIURI* aNewURI,
-                          nsIPrincipal* aLoadingPrincipal,
-                          bool aInPrivateBrowsing);
+  // Security checks to prevent frameset spoofing. See comments at
+  // implementation sites.
+  static bool CanAccessItem(nsIDocShellTreeItem* aTargetItem,
+                            nsIDocShellTreeItem* aAccessingItem,
+                            bool aConsiderOpener = true);
+  static bool ValidateOrigin(nsIDocShellTreeItem* aOriginTreeItem,
+                             nsIDocShellTreeItem* aTargetTreeItem);
 
-  static nsDocShell* Cast(nsIDocShell* aDocShell)
+  static inline uint32_t PRTimeToSeconds(PRTime aTimeUsec)
   {
-    return static_cast<nsDocShell*>(aDocShell);
+    PRTime usecPerSec = PR_USEC_PER_SEC;
+    return uint32_t(aTimeUsec /= usecPerSec);
   }
 
-protected:
+  static const nsCString FrameTypeToString(uint32_t aFrameType)
+  {
+    switch (aFrameType) {
+      case FRAME_TYPE_BROWSER:
+        return NS_LITERAL_CSTRING("browser");
+      case FRAME_TYPE_REGULAR:
+        return NS_LITERAL_CSTRING("regular");
+      default:
+        NS_ERROR("Unknown frame type");
+        return EmptyCString();
+    }
+  }
+
   virtual ~nsDocShell();
+
+  //
+  // nsDocLoader
+  //
+
   virtual void DestroyChildren() override;
 
+  // Overridden from nsDocLoader, this provides more information than the
+  // normal OnStateChange with flags STATE_REDIRECTING
+  virtual void OnRedirectStateChange(nsIChannel* aOldChannel,
+                                     nsIChannel* aNewChannel,
+                                     uint32_t aRedirectFlags,
+                                     uint32_t aStateFlags) override;
+
+  // Override the parent setter from nsDocLoader
+  virtual nsresult SetDocLoaderParent(nsDocLoader* aLoader) override;
+
+  //
   // Content Viewer Management
+  //
+
   nsresult EnsureContentViewer();
+
   // aPrincipal can be passed in if the caller wants. If null is
   // passed in, the about:blank principal will end up being used.
   nsresult CreateAboutBlankContentViewer(nsIPrincipal* aPrincipal,
                                          nsIURI* aBaseURI,
                                          bool aTryToSaveOldPresentation = true,
                                          bool aCheckPermitUnload = true);
+
   nsresult CreateContentViewer(const nsACString& aContentType,
                                nsIRequest* aRequest,
                                nsIStreamListener** aContentHandler);
+
   nsresult NewContentViewerObj(const nsACString& aContentType,
                                nsIRequest* aRequest, nsILoadGroup* aLoadGroup,
                                nsIStreamListener** aContentHandler,
                                nsIContentViewer** aViewer);
+
   nsresult SetupNewViewer(nsIContentViewer* aNewViewer);
 
-  void SetupReferrerFromChannel(nsIChannel* aChannel);
+  //
+  // Session History
+  //
+
+  bool ShouldAddToSessionHistory(nsIURI* aURI, nsIChannel* aChannel);
 
-  nsresult GetEldestPresContext(nsPresContext** aPresContext);
+  // Either aChannel or aOwner must be null. If aChannel is
+  // present, the owner should be gotten from it.
+  // If aCloneChildren is true, then our current session history's
+  // children will be cloned onto the new entry. This should be
+  // used when we aren't actually changing the document while adding
+  // the new session history entry.
 
-  // Get the principal that we'll set on the channel if we're inheriting. If
-  // aConsiderCurrentDocument is true, we try to use the current document if
-  // at all possible. If that fails, we fall back on the parent document.
-  // If that fails too, we force creation of a content viewer and use the
-  // resulting principal. If aConsiderCurrentDocument is false, we just look
-  // at the parent.
-  nsIPrincipal* GetInheritedPrincipal(bool aConsiderCurrentDocument);
+  nsresult AddToSessionHistory(nsIURI* aURI, nsIChannel* aChannel,
+                               nsIPrincipal* aTriggeringPrincipal,
+                               nsIPrincipal* aPrincipalToInherit,
+                               bool aCloneChildren,
+                               nsISHEntry** aNewEntry);
+
+  nsresult AddChildSHEntryToParent(nsISHEntry* aNewEntry, int32_t aChildOffset,
+                                   bool aCloneChildren);
+
+  nsresult AddChildSHEntryInternal(nsISHEntry* aCloneRef, nsISHEntry* aNewEntry,
+                                   int32_t aChildOffset, uint32_t aLoadType,
+                                   bool aCloneChildren);
+
+  // Call this method to swap in a new history entry to m[OL]SHE, rather than
+  // setting it directly. This completes the navigation in all docshells
+  // in the case of a subframe navigation.
+  void SetHistoryEntry(nsCOMPtr<nsISHEntry>* aPtr, nsISHEntry* aEntry);
+
+  //
+  // URI Load
+  //
 
   // Actually open a channel and perform a URI load. Callers need to pass a
   // non-null aTriggeringPrincipal which initiated the URI load. Please note
   // that aTriggeringPrincipal will be used for performing security checks.
   // If the argument aURI is provided by the web, then please do not pass a
   // SystemPrincipal as the triggeringPrincipal. If principalToInherit is
   // null, then no inheritance of any sort will happen and the load will
   // get a principal based on the URI being loaded.
@@ -472,18 +538,20 @@ protected:
                      nsIDocShell** aDocShell,
                      nsIRequest** aRequest,
                      bool aIsNewWindowTarget,
                      bool aBypassClassifier,
                      bool aForceAllowCookies,
                      const nsAString& aSrcdoc,
                      nsIURI* aBaseURI,
                      nsContentPolicyType aContentPolicyType);
+
   nsresult AddHeadersToChannel(nsIInputStream* aHeadersData,
                                nsIChannel* aChannel);
+
   nsresult DoChannelLoad(nsIChannel* aChannel,
                          nsIURILoader* aURILoader,
                          bool aBypassClassifier);
 
   nsresult ScrollToAnchor(bool aCurHasRef,
                           bool aNewHasRef,
                           nsACString& aNewHash,
                           uint32_t aLoadType);
@@ -509,107 +577,52 @@ protected:
   bool OnNewURI(nsIURI* aURI, nsIChannel* aChannel,
                 nsIPrincipal* aTriggeringPrincipal,
                 nsIPrincipal* aPrincipalToInherit,
                 uint32_t aLoadType,
                 bool aFireOnLocationChange,
                 bool aAddToGlobalHistory,
                 bool aCloneSHChildren);
 
-  void SetReferrerURI(nsIURI* aURI);
-  void SetReferrerPolicy(uint32_t aReferrerPolicy);
+  // Helper method that is called when a new document (including any
+  // sub-documents - ie. frames) has been completely loaded.
+  nsresult EndPageLoad(nsIWebProgress* aProgress,
+                       nsIChannel* aChannel,
+                       nsresult aResult);
 
-  // Session History
-  bool ShouldAddToSessionHistory(nsIURI* aURI, nsIChannel* aChannel);
-  // Either aChannel or aOwner must be null. If aChannel is
-  // present, the owner should be gotten from it.
-  // If aCloneChildren is true, then our current session history's
-  // children will be cloned onto the new entry. This should be
-  // used when we aren't actually changing the document while adding
-  // the new session history entry.
-  nsresult AddToSessionHistory(nsIURI* aURI, nsIChannel* aChannel,
-                               nsIPrincipal* aTriggeringPrincipal,
-                               nsIPrincipal* aPrincipalToInherit,
-                               bool aCloneChildren,
-                               nsISHEntry** aNewEntry);
-  nsresult AddChildSHEntryToParent(nsISHEntry* aNewEntry, int32_t aChildOffset,
-                                   bool aCloneChildren);
-
-  nsresult AddChildSHEntryInternal(nsISHEntry* aCloneRef, nsISHEntry* aNewEntry,
-                                   int32_t aChildOffset, uint32_t aLoadType,
-                                   bool aCloneChildren);
-
-  nsresult LoadHistoryEntry(nsISHEntry* aEntry, uint32_t aLoadType);
-  nsresult PersistLayoutHistoryState();
 
-  // Clone a session history tree for subframe navigation.
-  // The tree rooted at |aSrcEntry| will be cloned into |aDestEntry|, except
-  // for the entry with id |aCloneID|, which will be replaced with
-  // |aReplaceEntry|. |aSrcShell| is a (possibly null) docshell which
-  // corresponds to |aSrcEntry| via its mLSHE or mOHE pointers, and will
-  // have that pointer updated to point to the cloned history entry.
-  // If aCloneChildren is true then the children of the entry with id
-  // |aCloneID| will be cloned into |aReplaceEntry|.
-  static nsresult CloneAndReplace(nsISHEntry* aSrcEntry,
-                                  nsDocShell* aSrcShell,
-                                  uint32_t aCloneID,
-                                  nsISHEntry* aReplaceEntry,
-                                  bool aCloneChildren,
-                                  nsISHEntry** aDestEntry);
+  nsresult LoadErrorPage(nsIURI* aURI, const char16_t* aURL,
+                           const char* aErrorPage,
+                           const char* aErrorType,
+                           const char16_t* aDescription,
+                           const char* aCSSClass,
+                           nsIChannel* aFailedChannel);
 
-  // Child-walking callback for CloneAndReplace
-  static nsresult CloneAndReplaceChild(nsISHEntry* aEntry, nsDocShell* aShell,
-                                       int32_t aChildIndex, void* aData);
-
-  nsresult GetRootSessionHistory(nsISHistory** aReturn);
-  nsresult GetHttpChannel(nsIChannel* aChannel, nsIHttpChannel** aReturn);
-  bool ShouldDiscardLayoutState(nsIHttpChannel* aChannel);
-
-  // Determine whether this docshell corresponds to the given history entry,
-  // via having a pointer to it in mOSHE or mLSHE.
-  bool HasHistoryEntry(nsISHEntry* aEntry) const
+  bool DisplayLoadError(nsresult aError, nsIURI* aURI, const char16_t* aURL,
+                        nsIChannel* aFailedChannel)
   {
-    return aEntry && (aEntry == mOSHE || aEntry == mLSHE);
+    bool didDisplayLoadError = false;
+    DisplayLoadError(aError, aURI, aURL, aFailedChannel, &didDisplayLoadError);
+    return didDisplayLoadError;
   }
 
-  // Update any pointers (mOSHE or mLSHE) to aOldEntry to point to aNewEntry
-  void SwapHistoryEntries(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry);
-
-  // Call this method to swap in a new history entry to m[OL]SHE, rather than
-  // setting it directly. This completes the navigation in all docshells
-  // in the case of a subframe navigation.
-  void SetHistoryEntry(nsCOMPtr<nsISHEntry>* aPtr, nsISHEntry* aEntry);
-
-  // Child-walking callback for SetHistoryEntry
-  static nsresult SetChildHistoryEntry(nsISHEntry* aEntry, nsDocShell* aShell,
-                                       int32_t aEntryIndex, void* aData);
+  //
+  // Uncategorized
+  //
 
-  // Callback prototype for WalkHistoryEntries.
-  // aEntry is the child history entry, aShell is its corresponding docshell,
-  // aChildIndex is the child's index in its parent entry, and aData is
-  // the opaque pointer passed to WalkHistoryEntries.
-  typedef nsresult(*WalkHistoryEntriesFunc)(nsISHEntry* aEntry,
-                                            nsDocShell* aShell,
-                                            int32_t aChildIndex,
-                                            void* aData);
+  // Get the principal that we'll set on the channel if we're inheriting. If
+  // aConsiderCurrentDocument is true, we try to use the current document if
+  // at all possible. If that fails, we fall back on the parent document.
+  // If that fails too, we force creation of a content viewer and use the
+  // resulting principal. If aConsiderCurrentDocument is false, we just look
+  // at the parent.
+  nsIPrincipal* GetInheritedPrincipal(bool aConsiderCurrentDocument);
 
-  // For each child of aRootEntry, find the corresponding docshell which is
-  // a child of aRootShell, and call aCallback. The opaque pointer aData
-  // is passed to the callback.
-  static nsresult WalkHistoryEntries(nsISHEntry* aRootEntry,
-                                     nsDocShell* aRootShell,
-                                     WalkHistoryEntriesFunc aCallback,
-                                     void* aData);
-
-  // overridden from nsDocLoader, this provides more information than the
-  // normal OnStateChange with flags STATE_REDIRECTING
-  virtual void OnRedirectStateChange(nsIChannel* aOldChannel,
-                                     nsIChannel* aNewChannel,
-                                     uint32_t aRedirectFlags,
-                                     uint32_t aStateFlags) override;
+  nsresult CreatePrincipalFromReferrer(nsIURI* aReferrer,
+                                       nsIPrincipal** aResult);
 
   /**
    * Helper function that determines if channel is an HTTP POST.
    *
    * @param aChannel
    *        The channel to test
    *
    * @return True iff channel is an HTTP post.
@@ -676,76 +689,21 @@ protected:
    *        For HTTP channels, the response code (0 otherwise).
    */
   void AddURIVisit(nsIURI* aURI,
                    nsIURI* aReferrerURI,
                    nsIURI* aPreviousURI,
                    uint32_t aChannelRedirectFlags,
                    uint32_t aResponseStatus = 0);
 
-  // Helper Routines
-  nsresult ConfirmRepost(bool* aRepost);
-  NS_IMETHOD GetPromptAndStringBundle(nsIPrompt** aPrompt,
-                                      nsIStringBundle** aStringBundle);
-  NS_IMETHOD GetChildOffset(nsIDOMNode* aChild, nsIDOMNode* aParent,
-                            int32_t* aOffset);
-  nsIScrollableFrame* GetRootScrollFrame();
-  NS_IMETHOD EnsureScriptEnvironment();
-  NS_IMETHOD EnsureEditorData();
-  nsresult EnsureTransferableHookData();
-  NS_IMETHOD EnsureFind();
-  nsresult RefreshURIFromQueue();
-  NS_IMETHOD LoadErrorPage(nsIURI* aURI, const char16_t* aURL,
-                           const char* aErrorPage,
-                           const char* aErrorType,
-                           const char16_t* aDescription,
-                           const char* aCSSClass,
-                           nsIChannel* aFailedChannel);
-  bool IsPrintingOrPP(bool aDisplayErrorDialog = true);
-  bool IsNavigationAllowed(bool aDisplayPrintErrorDialog = true,
-                           bool aCheckIfUnloadFired = true);
-
-  nsresult SetBaseUrlForWyciwyg(nsIContentViewer* aContentViewer);
-
-  static inline uint32_t PRTimeToSeconds(PRTime aTimeUsec)
-  {
-    PRTime usecPerSec = PR_USEC_PER_SEC;
-    return uint32_t(aTimeUsec /= usecPerSec);
-  }
-
-  inline bool UseErrorPages()
-  {
-    return (mObserveErrorPages ? sUseErrorPages : mUseErrorPages);
-  }
-
-  bool IsFrame();
-
-  //
-  // Helper method that is called when a new document (including any
-  // sub-documents - ie. frames) has been completely loaded.
-  //
-  virtual nsresult EndPageLoad(nsIWebProgress* aProgress,
-                               nsIChannel* aChannel,
-                               nsresult aResult);
-
   // Sets the current document's current state object to the given SHEntry's
   // state object. The current state object is eventually given to the page
   // in the PopState event.
   nsresult SetDocCurrentStateObj(nsISHEntry* aShEntry);
 
-  nsresult CheckLoadingPermissions();
-
-  // Security checks to prevent frameset spoofing. See comments at
-  // implementation sites.
-  static bool CanAccessItem(nsIDocShellTreeItem* aTargetItem,
-                            nsIDocShellTreeItem* aAccessingItem,
-                            bool aConsiderOpener = true);
-  static bool ValidateOrigin(nsIDocShellTreeItem* aOriginTreeItem,
-                             nsIDocShellTreeItem* aTargetTreeItem);
-
   // Returns true if would have called FireOnLocationChange,
   // but did not because aFireOnLocationChange was false on entry.
   // In this case it is the caller's responsibility to ensure
   // FireOnLocationChange is called.
   // In all other cases false is returned.
   bool SetCurrentURI(nsIURI* aURI, nsIRequest* aRequest,
                      bool aFireOnLocationChange,
                      uint32_t aLocationFlags);
@@ -805,32 +763,19 @@ protected:
   void DoGetPositionAndSize(int32_t* aX, int32_t* aY, int32_t* aWidth,
                             int32_t* aHeight);
 
   // Call this when a URI load is handed to us (via OnLinkClick or
   // InternalLoad). This makes sure that we're not inside unload, or that if
   // we are it's still OK to load this URI.
   bool IsOKToLoadURI(nsIURI* aURI);
 
-  void ReattachEditorToWindow(nsISHEntry* aSHEntry);
-
-  nsCOMPtr<nsIDOMStorageManager> mSessionStorageManager;
-  nsIDOMStorageManager* TopSessionStorageManager();
-
   // helpers for executing commands
   nsresult GetControllerForCommand(const char* aCommand,
                                    nsIController** aResult);
-  nsresult EnsureCommandHandler();
-
-  nsIChannel* GetCurrentDocChannel();
-
-  bool ShouldBlockLoadingForBackButton();
-
-  // Convenience method for getting our parent docshell. Can return null
-  already_AddRefed<nsDocShell> GetParentDocshell();
 
   // Possibly create a ClientSource object to represent an initial about:blank
   // window that has not been allocated yet.  Normally we try not to create
   // this about:blank window until something calls GetDocument().  We still need
   // the ClientSource to exist for this conceptual window, though.
   //
   // The ClientSource is created with the given principal if specified.  If
   // the principal is not provided we will attempt to inherit it when we
@@ -842,120 +787,162 @@ protected:
   void MaybeCreateInitialClientSource(nsIPrincipal* aPrincipal = nullptr);
 
   // Return the ClientInfo for the initial about:blank window, if it exists
   // or we have speculatively created a ClientSource in
   // MaybeCreateInitialClientSource().  This can return a ClientInfo object
   // even if GetExtantDoc() returns nullptr.
   mozilla::Maybe<mozilla::dom::ClientInfo> GetInitialClientInfo() const;
 
-protected:
-  nsresult GetCurScrollPos(int32_t aScrollOrientation, int32_t* aCurPos);
-  nsresult SetCurScrollPosEx(int32_t aCurHorizontalPos,
-                             int32_t aCurVerticalPos);
-
-  // Override the parent setter from nsDocLoader
-  virtual nsresult SetDocLoaderParent(nsDocLoader* aLoader) override;
-
-  void ClearFrameHistory(nsISHEntry* aEntry);
-
   /**
    * Initializes mTiming if it isn't yet.
    * After calling this, mTiming is non-null. This method returns true if the
    * initialization of the Timing can be reset (basically this is true if a new
    * Timing object is created).
    * In case the loading is aborted, MaybeResetInitTiming() can be called
    * passing the return value of MaybeInitTiming(): if it's possible to reset
    * the Timing, this method will do it.
    */
   MOZ_MUST_USE bool MaybeInitTiming();
   void MaybeResetInitTiming(bool aReset);
 
-  bool DisplayLoadError(nsresult aError, nsIURI* aURI, const char16_t* aURL,
-                        nsIChannel* aFailedChannel)
+  // Separate function to do the actual name (i.e. not _top, _self etc.)
+  // searching for FindItemWithName.
+  nsresult DoFindItemWithName(const nsAString& aName,
+                              nsIDocShellTreeItem* aRequestor,
+                              nsIDocShellTreeItem* aOriginalRequestor,
+                              bool aSkipTabGroup,
+                              nsIDocShellTreeItem** aResult);
+
+  // Convenience method for getting our parent docshell. Can return null
+  already_AddRefed<nsDocShell> GetParentDocshell();
+
+  // Helper assertion to enforce that mInPrivateBrowsing is in sync with
+  // OriginAttributes.mPrivateBrowsingId
+  void AssertOriginAttributesMatchPrivateBrowsing();
+
+  // Notify consumers of a search being loaded through the observer service:
+  void MaybeNotifyKeywordSearchLoading(const nsString& aProvider,
+                                       const nsString& aKeyword);
+
+  // Internal implementation of nsIDocShell::FirePageHideNotification.
+  // If aSkipCheckingDynEntries is true, it will not try to remove dynamic
+  // subframe entries. This is to avoid redundant RemoveDynEntries calls in all
+  // children docshells.
+  void FirePageHideNotificationInternal(bool aIsUnload,
+                                        bool aSkipCheckingDynEntries);
+
+  // Dispatch a runnable to the TabGroup associated to this docshell.
+  nsresult DispatchToTabGroup(mozilla::TaskCategory aCategory,
+                              already_AddRefed<nsIRunnable>&& aRunnable);
+
+  void SetupReferrerFromChannel(nsIChannel* aChannel);
+  void SetReferrerURI(nsIURI* aURI);
+  void SetReferrerPolicy(uint32_t aReferrerPolicy);
+  void ReattachEditorToWindow(nsISHEntry* aSHEntry);
+  void RecomputeCanExecuteScripts();
+  void ClearFrameHistory(nsISHEntry* aEntry);
+  void UpdateGlobalHistoryTitle(nsIURI* aURI);
+  bool IsFrame();
+  bool CanSetOriginAttributes();
+  bool ShouldBlockLoadingForBackButton();
+  bool ShouldDiscardLayoutState(nsIHttpChannel* aChannel);
+  bool HasUnloadedParent();
+  bool JustStartedNetworkLoad();
+  bool IsPrintingOrPP(bool aDisplayErrorDialog = true);
+  bool IsNavigationAllowed(bool aDisplayPrintErrorDialog = true,
+                           bool aCheckIfUnloadFired = true);
+  uint32_t GetInheritedFrameType();
+  nsIScrollableFrame* GetRootScrollFrame();
+  nsIDOMStorageManager* TopSessionStorageManager();
+  nsIChannel* GetCurrentDocChannel();
+  nsresult EnsureScriptEnvironment();
+  nsresult EnsureEditorData();
+  nsresult EnsureTransferableHookData();
+  nsresult EnsureFind();
+  nsresult EnsureCommandHandler();
+  nsresult RefreshURIFromQueue();
+  nsresult GetEldestPresContext(nsPresContext** aPresContext);
+  nsresult CheckLoadingPermissions();
+  nsresult PersistLayoutHistoryState();
+  nsresult LoadHistoryEntry(nsISHEntry* aEntry, uint32_t aLoadType);
+  nsresult SetBaseUrlForWyciwyg(nsIContentViewer* aContentViewer);
+  nsresult GetRootSessionHistory(nsISHistory** aReturn);
+  nsresult GetHttpChannel(nsIChannel* aChannel, nsIHttpChannel** aReturn);
+  nsresult ConfirmRepost(bool* aRepost);
+  nsresult GetPromptAndStringBundle(nsIPrompt** aPrompt,
+                                    nsIStringBundle** aStringBundle);
+  nsresult GetCurScrollPos(int32_t aScrollOrientation, int32_t* aCurPos);
+  nsresult SetCurScrollPosEx(int32_t aCurHorizontalPos,
+                             int32_t aCurVerticalPos);
+
+  inline bool UseErrorPages()
   {
-    bool didDisplayLoadError = false;
-    DisplayLoadError(aError, aURI, aURL, aFailedChannel, &didDisplayLoadError);
-    return didDisplayLoadError;
+    return (mObserveErrorPages ? sUseErrorPages : mUseErrorPages);
   }
 
-public:
-  // Event type dispatched by RestorePresentation
-  class RestorePresentationEvent : public mozilla::Runnable
-  {
-  public:
-    NS_DECL_NSIRUNNABLE
-    explicit RestorePresentationEvent(nsDocShell* aDs)
-      : mozilla::Runnable("nsDocShell::RestorePresentationEvent")
-      , mDocShell(aDs)
-    {
-    }
-    void Revoke() { mDocShell = nullptr; }
-  private:
-    RefPtr<nsDocShell> mDocShell;
-  };
+private: // data members
+  static nsIURIFixup* sURIFixup;
 
-protected:
-  bool JustStartedNetworkLoad();
+  // Cached value of the "browser.xul.error_pages.enabled" preference.
+  static bool sUseErrorPages;
+
+#ifdef DEBUG
+  // We're counting the number of |nsDocShells| to help find leaks
+  static unsigned long gNumberOfDocShells;
+#endif /* DEBUG */
 
-  nsresult CreatePrincipalFromReferrer(nsIURI* aReferrer,
-                                       nsIPrincipal** aResult);
-
-  static const nsCString FrameTypeToString(uint32_t aFrameType)
-  {
-    switch (aFrameType) {
-      case FRAME_TYPE_BROWSER:
-        return NS_LITERAL_CSTRING("browser");
-      case FRAME_TYPE_REGULAR:
-        return NS_LITERAL_CSTRING("regular");
-      default:
-        NS_ERROR("Unknown frame type");
-        return EmptyCString();
-    }
-  }
-
-  uint32_t GetInheritedFrameType();
-
-  bool HasUnloadedParent();
-
-  void UpdateGlobalHistoryTitle(nsIURI* aURI);
-
-  NS_IMETHOD_(void) GetOriginAttributes(mozilla::OriginAttributes& aAttrs) override;
+  nsID mHistoryID;
+  nsString mName;
+  nsString mTitle;
+  nsString mCustomUserAgent;
+  nsCString mOriginalUriString;
+  nsWeakPtr mOnePermittedSandboxedNavigator;
+  nsWeakPtr mOpener;
+  nsTObserverArray<nsWeakPtr> mPrivacyObservers;
+  nsTObserverArray<nsWeakPtr> mReflowObservers;
+  nsTObserverArray<nsWeakPtr> mScrollObservers;
+  mozilla::OriginAttributes mOriginAttributes;
+  mozilla::UniquePtr<mozilla::dom::PendingGlobalHistoryEntry> mPrerenderGlobalHistory;
+  mozilla::UniquePtr<mozilla::dom::ClientSource> mInitialClientSource;
+  RefPtr<nsDOMNavigationTiming> mTiming;
+  RefPtr<nsDSURIContentListener> mContentListener;
+  RefPtr<nsGlobalWindowOuter> mScriptGlobal;
+  nsCOMPtr<nsIPrincipal> mParentCharsetPrincipal;
+  nsCOMPtr<nsILoadURIDelegate> mLoadURIDelegate;
+  nsCOMPtr<nsIMutableArray> mRefreshURIList;
+  nsCOMPtr<nsIMutableArray> mSavedRefreshURIList;
+  nsCOMPtr<nsIDOMStorageManager> mSessionStorageManager;
+  nsCOMPtr<nsIContentViewer> mContentViewer;
+  nsCOMPtr<nsIWidget> mParentWidget;
+  nsCOMPtr<nsISHistory> mSessionHistory;
+  nsCOMPtr<nsIGlobalHistory2> mGlobalHistory;
+  nsCOMPtr<nsIWebBrowserFind> mFind;
+  nsCOMPtr<nsICommandManager> mCommandManager;
 
   // Dimensions of the docshell
   nsIntRect mBounds;
-  nsString mName;
-  nsString mTitle;
-  nsString mCustomUserAgent;
 
   /**
    * Content-Type Hint of the most-recently initiated load. Used for
    * session history entries.
    */
   nsCString mContentTypeHint;
-  nsIntPoint mDefaultScrollbarPref; // persistent across doc loads
 
-  nsCOMPtr<nsIMutableArray> mRefreshURIList;
-  nsCOMPtr<nsIMutableArray> mSavedRefreshURIList;
-  RefPtr<nsDSURIContentListener> mContentListener;
-  nsCOMPtr<nsIContentViewer> mContentViewer;
-  nsCOMPtr<nsIWidget> mParentWidget;
+  // An observed docshell wrapper is created when recording markers is enabled.
+  mozilla::UniquePtr<mozilla::ObservedDocShell> mObserved;
 
   // mCurrentURI should be marked immutable on set if possible.
   nsCOMPtr<nsIURI> mCurrentURI;
   nsCOMPtr<nsIURI> mReferrerURI;
-  uint32_t mReferrerPolicy;
-  RefPtr<nsGlobalWindowOuter> mScriptGlobal;
-  nsCOMPtr<nsISHistory> mSessionHistory;
-  nsCOMPtr<nsIGlobalHistory2> mGlobalHistory;
-  nsCOMPtr<nsIWebBrowserFind> mFind;
-  nsCOMPtr<nsICommandManager> mCommandManager;
+
   // Reference to the SHEntry for this docshell until the page is destroyed.
   // Somebody give me better name
   nsCOMPtr<nsISHEntry> mOSHE;
+
   // Reference to the SHEntry for this docshell until the page is loaded
   // Somebody give me better name.
   // If mLSHE is non-null, non-pushState subframe loads don't create separate
   // root history entries. That is, frames loaded during the parent page
   // load don't generate history entries the way frame navigation after the
   // parent has loaded does. (This isn't the only purpose of mLSHE.)
   nsCOMPtr<nsISHEntry> mLSHE;
 
@@ -975,65 +962,106 @@ protected:
 
   // The URI we're currently loading. This is only relevant during the
   // firing of a pagehide/unload. The caller of FirePageHideNotification()
   // is responsible for setting it and unsetting it. It may be null if the
   // pagehide/unload is happening for some reason other than just loading a
   // new URI.
   nsCOMPtr<nsIURI> mLoadingURI;
 
+  // Our list of ancestor principals.
+  nsTArray<nsCOMPtr<nsIPrincipal>> mAncestorPrincipals;
+
+  // Our list of ancestor outerWindowIDs.
+  nsTArray<uint64_t> mAncestorOuterWindowIDs;
+
   // Set in LoadErrorPage from the method argument and used later
   // in CreateContentViewer. We have to delay an shistory entry creation
   // for which these objects are needed.
   nsCOMPtr<nsIURI> mFailedURI;
   nsCOMPtr<nsIChannel> mFailedChannel;
-  uint32_t mFailedLoadType;
 
   // Set in DoURILoad when either the LOAD_RELOAD_ALLOW_MIXED_CONTENT flag or
   // the LOAD_NORMAL_ALLOW_MIXED_CONTENT flag is set.
   // Checked in nsMixedContentBlocker, to see if the channels match.
   nsCOMPtr<nsIChannel> mMixedContentChannel;
 
+  const mozilla::Encoding* mForcedCharset;
+  const mozilla::Encoding* mParentCharset;
+
   // WEAK REFERENCES BELOW HERE.
   // Note these are intentionally not addrefd. Doing so will create a cycle.
   // For that reasons don't use nsCOMPtr.
 
   nsIDocShellTreeOwner* mTreeOwner; // Weak Reference
   mozilla::dom::EventTarget* mChromeEventHandler; // Weak Reference
 
+  nsIntPoint mDefaultScrollbarPref; // persistent across doc loads
+
   eCharsetReloadState mCharsetReloadState;
 
-  nsCOMPtr<nsILoadURIDelegate> mLoadURIDelegate;
+  // The orientation lock as described by
+  // https://w3c.github.io/screen-orientation/
+  mozilla::dom::ScreenOrientationInternal mOrientationLock;
 
-  // Offset in the parent's child list.
-  // -1 if the docshell is added dynamically to the parent shell.
-  int32_t mChildOffset;
-  uint32_t mBusyFlags;
-  uint32_t mAppType;
-  uint32_t mLoadType;
-
+  int32_t mParentCharsetSource;
   int32_t mMarginWidth;
   int32_t mMarginHeight;
 
   // This can either be a content docshell or a chrome docshell. After
   // Create() is called, the type is not expected to change.
   int32_t mItemType;
 
   // Index into the SHTransaction list, indicating the previous and current
   // transaction at the time that this DocShell begins to load. Consequently
   // root docshell's indices can differ from child docshells'.
   int32_t mPreviousTransIndex;
   int32_t mLoadedTransIndex;
 
+  // Offset in the parent's child list.
+  // -1 if the docshell is added dynamically to the parent shell.
+  int32_t mChildOffset;
+
   uint32_t mSandboxFlags;
-  nsWeakPtr mOnePermittedSandboxedNavigator;
+  uint32_t mBusyFlags;
+  uint32_t mAppType;
+  uint32_t mLoadType;
+  uint32_t mDefaultLoadFlags;
+  uint32_t mReferrerPolicy;
+  uint32_t mFailedLoadType;
+
+  // Are we a regular frame, a browser frame, or an app frame?
+  uint32_t mFrameType;
 
-  // The orientation lock as described by
-  // https://w3c.github.io/screen-orientation/
-  mozilla::dom::ScreenOrientationInternal mOrientationLock;
+  // This represents the state of private browsing in the docshell.
+  // Currently treated as a binary value: 1 - in private mode, 0 - not private mode
+  // On content docshells mPrivateBrowsingId == mOriginAttributes.mPrivateBrowsingId
+  // On chrome docshells this value will be set, but not have the corresponding
+  // origin attribute set.
+  uint32_t mPrivateBrowsingId;
+
+  // This represents the CSS display-mode we are currently using.
+  // It can be any of the following values from nsIDocShell.idl:
+  //
+  // DISPLAY_MODE_BROWSER = 0
+  // DISPLAY_MODE_MINIMAL_UI = 1
+  // DISPLAY_MODE_STANDALONE = 2
+  // DISPLAY_MODE_FULLSCREEN = 3
+  //
+  // This is mostly used for media queries. The integer values above
+  // match those used in nsStyleConsts.h
+  uint32_t mDisplayMode;
+
+  // A depth count of how many times NotifyRunToCompletionStart
+  // has been called without a matching NotifyRunToCompletionStop.
+  uint32_t mJSRunToCompletionDepth;
+
+  // Whether or not touch events are overridden. Possible values are defined
+  // as constants in the nsIDocShell.idl file.
+  uint32_t mTouchEventsOverride;
 
   // mFullscreenAllowed stores how we determine whether fullscreen is allowed
   // when GetFullscreenAllowed() is called. Fullscreen is allowed in a
   // docshell when all containing iframes have the allowfullscreen
   // attribute set to true. When mFullscreenAllowed is CHECK_ATTRIBUTES
   // we check this docshell's containing frame for the allowfullscreen
   // attribute, and recurse onto the parent docshell to ensure all containing
   // frames also have the allowfullscreen attribute. If we find an ancestor
@@ -1045,18 +1073,22 @@ protected:
   enum FullscreenAllowedState : uint8_t
   {
     CHECK_ATTRIBUTES,
     PARENT_ALLOWS,
     PARENT_PROHIBITS
   };
   FullscreenAllowedState mFullscreenAllowed;
 
-  // Cached value of the "browser.xul.error_pages.enabled" preference.
-  static bool sUseErrorPages;
+  // The following two fields cannot be declared as bit fields
+  // because of uses with AutoRestore.
+  bool mCreatingDocument; // (should be) debugging only
+#ifdef DEBUG
+  bool mInEnsureScriptEnv;
+#endif
 
   bool mCreated : 1;
   bool mAllowSubframes : 1;
   bool mAllowPlugins : 1;
   bool mAllowJavascript : 1;
   bool mAllowMetaRedirects : 1;
   bool mAllowImages : 1;
   bool mAllowMedia : 1;
@@ -1080,17 +1112,16 @@ protected:
   bool mWindowDraggingAllowed : 1;
   bool mInFrameSwap : 1;
   bool mInheritPrivateBrowsingId : 1;
 
   // Because scriptability depends on the mAllowJavascript values of our
   // ancestors, we cache the effective scriptability and recompute it when
   // it might have changed;
   bool mCanExecuteScripts : 1;
-  void RecomputeCanExecuteScripts();
 
   // This boolean is set to true right before we fire pagehide and generally
   // unset when we embed a new content viewer. While it's true no navigation
   // is allowed in this docshell.
   bool mFiredUnloadEvent : 1;
 
   // this flag is for bug #21358. a docshell may load many urls
   // which don't result in new documents being created (i.e. a new
@@ -1116,122 +1147,11 @@ protected:
   bool mAffectPrivateSessionLifetime : 1;
   bool mInvisible : 1;
   bool mHasLoadedNonBlankURI : 1;
 
   // This flag means that mTiming has been initialized but nulled out.
   // We will check the innerWin's timing before creating a new one
   // in MaybeInitTiming()
   bool mBlankTiming : 1;
-
-  // The following two fields cannot be declared as bit fields
-  // because of uses with AutoRestore.
-  bool mCreatingDocument; // (should be) debugging only
-#ifdef DEBUG
-  bool mInEnsureScriptEnv;
-#endif
-
-  nsID mHistoryID;
-  uint32_t mDefaultLoadFlags;
-
-  static nsIURIFixup* sURIFixup;
-
-  RefPtr<nsDOMNavigationTiming> mTiming;
-
-  // Are we a regular frame, a browser frame, or an app frame?
-  uint32_t mFrameType;
-
-  // This represents the state of private browsing in the docshell.
-  // Currently treated as a binary value: 1 - in private mode, 0 - not private mode
-  // On content docshells mPrivateBrowsingId == mOriginAttributes.mPrivateBrowsingId
-  // On chrome docshells this value will be set, but not have the corresponding
-  // origin attribute set.
-  uint32_t mPrivateBrowsingId;
-
-  // This represents the CSS display-mode we are currently using.
-  // It can be any of the following values from nsIDocShell.idl:
-  //
-  // DISPLAY_MODE_BROWSER = 0
-  // DISPLAY_MODE_MINIMAL_UI = 1
-  // DISPLAY_MODE_STANDALONE = 2
-  // DISPLAY_MODE_FULLSCREEN = 3
-  //
-  // This is mostly used for media queries. The integer values above
-  // match those used in nsStyleConsts.h
-  uint32_t mDisplayMode;
-
-private:
-  const Encoding* mForcedCharset;
-  const Encoding* mParentCharset;
-  int32_t mParentCharsetSource;
-  nsCOMPtr<nsIPrincipal> mParentCharsetPrincipal;
-  nsTObserverArray<nsWeakPtr> mPrivacyObservers;
-  nsTObserverArray<nsWeakPtr> mReflowObservers;
-  nsTObserverArray<nsWeakPtr> mScrollObservers;
-  nsCString mOriginalUriString;
-  nsWeakPtr mOpener;
-  mozilla::OriginAttributes mOriginAttributes;
-
-  mozilla::UniquePtr<mozilla::dom::PendingGlobalHistoryEntry> mPrerenderGlobalHistory;
-
-  mozilla::UniquePtr<mozilla::dom::ClientSource> mInitialClientSource;
-
-  // A depth count of how many times NotifyRunToCompletionStart
-  // has been called without a matching NotifyRunToCompletionStop.
-  uint32_t mJSRunToCompletionDepth;
-
-  // Whether or not touch events are overridden. Possible values are defined
-  // as constants in the nsIDocShell.idl file.
-  uint32_t mTouchEventsOverride;
-
-  // Our list of ancestor principals.
-  nsTArray<nsCOMPtr<nsIPrincipal>> mAncestorPrincipals;
-  // Our list of ancestor outerWindowIDs.
-  nsTArray<uint64_t> mAncestorOuterWindowIDs;
-
-  // Separate function to do the actual name (i.e. not _top, _self etc.)
-  // searching for FindItemWithName.
-  nsresult DoFindItemWithName(const nsAString& aName,
-                              nsIDocShellTreeItem* aRequestor,
-                              nsIDocShellTreeItem* aOriginalRequestor,
-                              bool aSkipTabGroup,
-                              nsIDocShellTreeItem** aResult);
-
-  // Helper assertion to enforce that mInPrivateBrowsing is in sync with
-  // OriginAttributes.mPrivateBrowsingId
-  void AssertOriginAttributesMatchPrivateBrowsing();
-
-  // Notify consumers of a search being loaded through the observer service:
-  void MaybeNotifyKeywordSearchLoading(const nsString& aProvider,
-                                       const nsString& aKeyword);
-
-  // Internal implementation of nsIDocShell::FirePageHideNotification.
-  // If aSkipCheckingDynEntries is true, it will not try to remove dynamic
-  // subframe entries. This is to avoid redundant RemoveDynEntries calls in all
-  // children docshells.
-  void FirePageHideNotificationInternal(bool aIsUnload,
-                                        bool aSkipCheckingDynEntries);
-
-  // Dispatch a runnable to the TabGroup associated to this docshell.
-  nsresult DispatchToTabGroup(mozilla::TaskCategory aCategory,
-                              already_AddRefed<nsIRunnable>&& aRunnable);
-
-#ifdef DEBUG
-  // We're counting the number of |nsDocShells| to help find leaks
-  static unsigned long gNumberOfDocShells;
-#endif /* DEBUG */
-
-public:
-  class InterfaceRequestorProxy : public nsIInterfaceRequestor
-  {
-  public:
-    explicit InterfaceRequestorProxy(nsIInterfaceRequestor* aRequestor);
-    NS_DECL_THREADSAFE_ISUPPORTS
-    NS_DECL_NSIINTERFACEREQUESTOR
-
-  protected:
-    virtual ~InterfaceRequestorProxy();
-    InterfaceRequestorProxy() {}
-    nsWeakPtr mWeakPtr;
-  };
 };
 
 #endif /* nsDocShell_h__ */
--- a/docshell/base/nsDocShellLoadTypes.h
+++ b/docshell/base/nsDocShellLoadTypes.h
@@ -4,17 +4,19 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsDocShellLoadTypes_h_
 #define nsDocShellLoadTypes_h_
 
 #ifdef MOZILLA_INTERNAL_API
 
+#include "nsDOMNavigationTiming.h"
 #include "nsIDocShell.h"
+#include "nsIDocShellLoadInfo.h"
 #include "nsIWebNavigation.h"
 
 /**
  * Load flag for error pages. This uses one of the reserved flag
  * values from nsIWebNavigation.
  */
 #define LOAD_FLAGS_ERROR_PAGE 0x0001U
 
@@ -67,16 +69,17 @@ enum LoadType
    * Docshell. Instead, Docshell triggers the load itself when a
    * consumer-triggered load failed.
    */
   LOAD_ERROR_PAGE = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
                                    LOAD_FLAGS_ERROR_PAGE)
 
   // NOTE: Adding a new value? Remember to update IsValidLoadType!
 };
+
 static inline bool
 IsValidLoadType(uint32_t aLoadType)
 {
   switch (aLoadType) {
     case LOAD_NORMAL:
     case LOAD_NORMAL_REPLACE:
     case LOAD_NORMAL_EXTERNAL:
     case LOAD_NORMAL_BYPASS_CACHE:
@@ -112,10 +115,224 @@ IsForceReloadType(uint32_t aLoadType) {
     case LOAD_RELOAD_BYPASS_PROXY:
     case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
     case LOAD_RELOAD_ALLOW_MIXED_CONTENT:
       return true;
   }
   return false;
 }
 
+static inline nsDocShellInfoLoadType
+ConvertLoadTypeToDocShellInfoLoadType(uint32_t aLoadType)
+{
+  nsDocShellInfoLoadType docShellLoadType = nsIDocShellLoadInfo::loadNormal;
+  switch (aLoadType) {
+    case LOAD_NORMAL:
+      docShellLoadType = nsIDocShellLoadInfo::loadNormal;
+      break;
+    case LOAD_NORMAL_REPLACE:
+      docShellLoadType = nsIDocShellLoadInfo::loadNormalReplace;
+      break;
+    case LOAD_NORMAL_EXTERNAL:
+      docShellLoadType = nsIDocShellLoadInfo::loadNormalExternal;
+      break;
+    case LOAD_NORMAL_BYPASS_CACHE:
+      docShellLoadType = nsIDocShellLoadInfo::loadNormalBypassCache;
+      break;
+    case LOAD_NORMAL_BYPASS_PROXY:
+      docShellLoadType = nsIDocShellLoadInfo::loadNormalBypassProxy;
+      break;
+    case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE:
+      docShellLoadType = nsIDocShellLoadInfo::loadNormalBypassProxyAndCache;
+      break;
+    case LOAD_NORMAL_ALLOW_MIXED_CONTENT:
+      docShellLoadType = nsIDocShellLoadInfo::loadNormalAllowMixedContent;
+      break;
+    case LOAD_HISTORY:
+      docShellLoadType = nsIDocShellLoadInfo::loadHistory;
+      break;
+    case LOAD_RELOAD_NORMAL:
+      docShellLoadType = nsIDocShellLoadInfo::loadReloadNormal;
+      break;
+    case LOAD_RELOAD_CHARSET_CHANGE:
+      docShellLoadType = nsIDocShellLoadInfo::loadReloadCharsetChange;
+      break;
+    case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE:
+      docShellLoadType = nsIDocShellLoadInfo::loadReloadCharsetChangeBypassCache;
+      break;
+    case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE:
+      docShellLoadType = nsIDocShellLoadInfo::loadReloadCharsetChangeBypassProxyAndCache;
+      break;
+    case LOAD_RELOAD_BYPASS_CACHE:
+      docShellLoadType = nsIDocShellLoadInfo::loadReloadBypassCache;
+      break;
+    case LOAD_RELOAD_BYPASS_PROXY:
+      docShellLoadType = nsIDocShellLoadInfo::loadReloadBypassProxy;
+      break;
+    case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
+      docShellLoadType = nsIDocShellLoadInfo::loadReloadBypassProxyAndCache;
+      break;
+    case LOAD_LINK:
+      docShellLoadType = nsIDocShellLoadInfo::loadLink;
+      break;
+    case LOAD_REFRESH:
+      docShellLoadType = nsIDocShellLoadInfo::loadRefresh;
+      break;
+    case LOAD_BYPASS_HISTORY:
+    case LOAD_ERROR_PAGE:
+      docShellLoadType = nsIDocShellLoadInfo::loadBypassHistory;
+      break;
+    case LOAD_STOP_CONTENT:
+      docShellLoadType = nsIDocShellLoadInfo::loadStopContent;
+      break;
+    case LOAD_STOP_CONTENT_AND_REPLACE:
+      docShellLoadType = nsIDocShellLoadInfo::loadStopContentAndReplace;
+      break;
+    case LOAD_PUSHSTATE:
+      docShellLoadType = nsIDocShellLoadInfo::loadPushState;
+      break;
+    case LOAD_REPLACE_BYPASS_CACHE:
+      docShellLoadType = nsIDocShellLoadInfo::loadReplaceBypassCache;
+      break;
+    case LOAD_RELOAD_ALLOW_MIXED_CONTENT:
+      docShellLoadType = nsIDocShellLoadInfo::loadReloadMixedContent;
+      break;
+    default:
+      NS_NOTREACHED("Unexpected load type value");
+  }
+
+  return docShellLoadType;
+}
+
+static inline uint32_t
+ConvertDocShellInfoLoadTypeToLoadType(nsDocShellInfoLoadType aDocShellLoadType)
+{
+  uint32_t loadType = LOAD_NORMAL;
+
+  switch (aDocShellLoadType) {
+    case nsIDocShellLoadInfo::loadNormal:
+      loadType = LOAD_NORMAL;
+      break;
+    case nsIDocShellLoadInfo::loadNormalReplace:
+      loadType = LOAD_NORMAL_REPLACE;
+      break;
+    case nsIDocShellLoadInfo::loadNormalExternal:
+      loadType = LOAD_NORMAL_EXTERNAL;
+      break;
+    case nsIDocShellLoadInfo::loadHistory:
+      loadType = LOAD_HISTORY;
+      break;
+    case nsIDocShellLoadInfo::loadNormalBypassCache:
+      loadType = LOAD_NORMAL_BYPASS_CACHE;
+      break;
+    case nsIDocShellLoadInfo::loadNormalBypassProxy:
+      loadType = LOAD_NORMAL_BYPASS_PROXY;
+      break;
+    case nsIDocShellLoadInfo::loadNormalBypassProxyAndCache:
+      loadType = LOAD_NORMAL_BYPASS_PROXY_AND_CACHE;
+      break;
+    case nsIDocShellLoadInfo::loadNormalAllowMixedContent:
+      loadType = LOAD_NORMAL_ALLOW_MIXED_CONTENT;
+      break;
+    case nsIDocShellLoadInfo::loadReloadNormal:
+      loadType = LOAD_RELOAD_NORMAL;
+      break;
+    case nsIDocShellLoadInfo::loadReloadCharsetChange:
+      loadType = LOAD_RELOAD_CHARSET_CHANGE;
+      break;
+    case nsIDocShellLoadInfo::loadReloadCharsetChangeBypassCache:
+      loadType = LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE;
+      break;
+    case nsIDocShellLoadInfo::loadReloadCharsetChangeBypassProxyAndCache:
+      loadType = LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE;
+      break;
+    case nsIDocShellLoadInfo::loadReloadBypassCache:
+      loadType = LOAD_RELOAD_BYPASS_CACHE;
+      break;
+    case nsIDocShellLoadInfo::loadReloadBypassProxy:
+      loadType = LOAD_RELOAD_BYPASS_PROXY;
+      break;
+    case nsIDocShellLoadInfo::loadReloadBypassProxyAndCache:
+      loadType = LOAD_RELOAD_BYPASS_PROXY_AND_CACHE;
+      break;
+    case nsIDocShellLoadInfo::loadLink:
+      loadType = LOAD_LINK;
+      break;
+    case nsIDocShellLoadInfo::loadRefresh:
+      loadType = LOAD_REFRESH;
+      break;
+    case nsIDocShellLoadInfo::loadBypassHistory:
+      loadType = LOAD_BYPASS_HISTORY;
+      break;
+    case nsIDocShellLoadInfo::loadStopContent:
+      loadType = LOAD_STOP_CONTENT;
+      break;
+    case nsIDocShellLoadInfo::loadStopContentAndReplace:
+      loadType = LOAD_STOP_CONTENT_AND_REPLACE;
+      break;
+    case nsIDocShellLoadInfo::loadPushState:
+      loadType = LOAD_PUSHSTATE;
+      break;
+    case nsIDocShellLoadInfo::loadReplaceBypassCache:
+      loadType = LOAD_REPLACE_BYPASS_CACHE;
+      break;
+    case nsIDocShellLoadInfo::loadReloadMixedContent:
+      loadType = LOAD_RELOAD_ALLOW_MIXED_CONTENT;
+      break;
+    default:
+      NS_NOTREACHED("Unexpected nsDocShellInfoLoadType value");
+  }
+
+  return loadType;
+}
+
+static inline nsDOMNavigationTiming::Type
+ConvertLoadTypeToNavigationType(uint32_t aLoadType)
+{
+  // Not initialized, assume it's normal load.
+  if (aLoadType == 0) {
+    aLoadType = LOAD_NORMAL;
+  }
+
+  auto result = nsDOMNavigationTiming::TYPE_RESERVED;
+  switch (aLoadType) {
+    case LOAD_NORMAL:
+    case LOAD_NORMAL_EXTERNAL:
+    case LOAD_NORMAL_BYPASS_CACHE:
+    case LOAD_NORMAL_BYPASS_PROXY:
+    case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE:
+    case LOAD_NORMAL_REPLACE:
+    case LOAD_NORMAL_ALLOW_MIXED_CONTENT:
+    case LOAD_LINK:
+    case LOAD_STOP_CONTENT:
+    case LOAD_REPLACE_BYPASS_CACHE:
+      result = nsDOMNavigationTiming::TYPE_NAVIGATE;
+      break;
+    case LOAD_HISTORY:
+      result = nsDOMNavigationTiming::TYPE_BACK_FORWARD;
+      break;
+    case LOAD_RELOAD_NORMAL:
+    case LOAD_RELOAD_CHARSET_CHANGE:
+    case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE:
+    case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE:
+    case LOAD_RELOAD_BYPASS_CACHE:
+    case LOAD_RELOAD_BYPASS_PROXY:
+    case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
+    case LOAD_RELOAD_ALLOW_MIXED_CONTENT:
+      result = nsDOMNavigationTiming::TYPE_RELOAD;
+      break;
+    case LOAD_STOP_CONTENT_AND_REPLACE:
+    case LOAD_REFRESH:
+    case LOAD_BYPASS_HISTORY:
+    case LOAD_ERROR_PAGE:
+    case LOAD_PUSHSTATE:
+      result = nsDOMNavigationTiming::TYPE_RESERVED;
+      break;
+    default:
+      result = nsDOMNavigationTiming::TYPE_RESERVED;
+      break;
+  }
+
+  return result;
+}
+
 #endif // MOZILLA_INTERNAL_API
 #endif
new file mode 100644
--- /dev/null
+++ b/docshell/base/nsPingListener.cpp
@@ -0,0 +1,370 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsPingListener.h"
+
+#include "mozilla/Preferences.h"
+
+#include "mozilla/dom/DocGroup.h"
+
+#include "nsIDocument.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIInputStream.h"
+#include "nsIProtocolHandler.h"
+#include "nsIUploadChannel2.h"
+
+#include "nsDocument.h"
+#include "nsNetUtil.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+#include "nsWhitespaceTokenizer.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_ISUPPORTS(nsPingListener, nsIStreamListener, nsIRequestObserver)
+
+//*****************************************************************************
+// <a ping> support
+//*****************************************************************************
+
+#define PREF_PINGS_ENABLED           "browser.send_pings"
+#define PREF_PINGS_MAX_PER_LINK      "browser.send_pings.max_per_link"
+#define PREF_PINGS_REQUIRE_SAME_HOST "browser.send_pings.require_same_host"
+
+// Check prefs to see if pings are enabled and if so what restrictions might
+// be applied.
+//
+// @param maxPerLink
+//   This parameter returns the number of pings that are allowed per link click
+//
+// @param requireSameHost
+//   This parameter returns true if pings are restricted to the same host as
+//   the document in which the click occurs.  If the same host restriction is
+//   imposed, then we still allow for pings to cross over to different
+//   protocols and ports for flexibility and because it is not possible to send
+//   a ping via FTP.
+//
+// @returns
+//   true if pings are enabled and false otherwise.
+//
+static bool
+PingsEnabled(int32_t* aMaxPerLink, bool* aRequireSameHost)
+{
+  bool allow = Preferences::GetBool(PREF_PINGS_ENABLED, false);
+
+  *aMaxPerLink = 1;
+  *aRequireSameHost = true;
+
+  if (allow) {
+    Preferences::GetInt(PREF_PINGS_MAX_PER_LINK, aMaxPerLink);
+    Preferences::GetBool(PREF_PINGS_REQUIRE_SAME_HOST, aRequireSameHost);
+  }
+
+  return allow;
+}
+
+// We wait this many milliseconds before killing the ping channel...
+#define PING_TIMEOUT 10000
+
+static void
+OnPingTimeout(nsITimer* aTimer, void* aClosure)
+{
+  nsILoadGroup* loadGroup = static_cast<nsILoadGroup*>(aClosure);
+  if (loadGroup) {
+    loadGroup->Cancel(NS_ERROR_ABORT);
+  }
+}
+
+struct MOZ_STACK_CLASS SendPingInfo
+{
+  int32_t numPings;
+  int32_t maxPings;
+  bool requireSameHost;
+  nsIURI* target;
+  nsIURI* referrer;
+  nsIDocShell* docShell;
+  uint32_t referrerPolicy;
+};
+
+static void
+SendPing(void* aClosure, nsIContent* aContent, nsIURI* aURI,
+         nsIIOService* aIOService)
+{
+  SendPingInfo* info = static_cast<SendPingInfo*>(aClosure);
+  if (info->maxPings > -1 && info->numPings >= info->maxPings) {
+    return;
+  }
+
+  nsIDocument* doc = aContent->OwnerDoc();
+
+  nsCOMPtr<nsIChannel> chan;
+  NS_NewChannel(getter_AddRefs(chan),
+                aURI,
+                doc,
+                info->requireSameHost
+                  ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
+                  : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+                nsIContentPolicy::TYPE_PING,
+                nullptr, // aLoadGroup
+                nullptr, // aCallbacks
+                nsIRequest::LOAD_NORMAL, // aLoadFlags,
+                aIOService);
+
+  if (!chan) {
+    return;
+  }
+
+  // Don't bother caching the result of this URI load, but do not exempt
+  // it from Safe Browsing.
+  chan->SetLoadFlags(nsIRequest::INHIBIT_CACHING | nsIChannel::LOAD_CLASSIFY_URI);
+
+  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) {
+    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)
+  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))) {
+    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;
+    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))) {
+        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) {
+      rv = httpChan->SetReferrerWithPolicy(info->referrer, info->referrerPolicy);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
+    }
+  }
+
+  nsCOMPtr<nsIUploadChannel2> uploadChan = do_QueryInterface(httpChan);
+  if (!uploadChan) {
+    return;
+  }
+
+  NS_NAMED_LITERAL_CSTRING(uploadData, "PING");
+
+  nsCOMPtr<nsIInputStream> uploadStream;
+  rv = NS_NewCStringInputStream(getter_AddRefs(uploadStream), uploadData);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  uploadChan->ExplicitSetUploadStream(uploadStream,
+                                      NS_LITERAL_CSTRING("text/ping"),
+                                      uploadData.Length(),
+                                      NS_LITERAL_CSTRING("POST"), false);
+
+  // The channel needs to have a loadgroup associated with it, so that we can
+  // cancel the channel and any redirected channels it may create.
+  nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
+  if (!loadGroup) {
+    return;
+  }
+  nsCOMPtr<nsIInterfaceRequestor> callbacks = do_QueryInterface(info->docShell);
+  loadGroup->SetNotificationCallbacks(callbacks);
+  chan->SetLoadGroup(loadGroup);
+
+  RefPtr<nsPingListener> pingListener = new nsPingListener();
+  chan->AsyncOpen2(pingListener);
+
+  // Even if AsyncOpen failed, we still count this as a successful ping.  It's
+  // possible that AsyncOpen may have failed after triggering some background
+  // process that may have written something to the network.
+  info->numPings++;
+
+  // Prevent ping requests from stalling and never being garbage collected...
+  if (NS_FAILED(pingListener->StartTimeout(doc->GetDocGroup()))) {
+    // If we failed to setup the timer, then we should just cancel the channel
+    // because we won't be able to ensure that it goes away in a timely manner.
+    chan->Cancel(NS_ERROR_ABORT);
+    return;
+  }
+  // if the channel openend successfully, then make the pingListener hold
+  // a strong reference to the loadgroup which is released in ::OnStopRequest
+  pingListener->SetLoadGroup(loadGroup);
+}
+
+typedef void (*ForEachPingCallback)(void* closure, nsIContent* content,
+                                    nsIURI* uri, nsIIOService* ios);
+
+static void
+ForEachPing(nsIContent* aContent, ForEachPingCallback aCallback, void* aClosure)
+{
+  // NOTE: Using nsIDOMHTMLAnchorElement::GetPing isn't really worth it here
+  //       since we'd still need to parse the resulting string.  Instead, we
+  //       just parse the raw attribute.  It might be nice if the content node
+  //       implemented an interface that exposed an enumeration of nsIURIs.
+
+  // Make sure we are dealing with either an <A> or <AREA> element in the HTML
+  // or XHTML namespace.
+  if (!aContent->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area)) {
+    return;
+  }
+
+  nsAutoString value;
+  aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::ping, value);
+  if (value.IsEmpty()) {
+    return;
+  }
+
+  nsCOMPtr<nsIIOService> ios = do_GetIOService();
+  if (!ios) {
+    return;
+  }
+
+  nsIDocument* doc = aContent->OwnerDoc();
+  nsAutoCString charset;
+  doc->GetDocumentCharacterSet()->Name(charset);
+
+  nsWhitespaceTokenizer tokenizer(value);
+
+  while (tokenizer.hasMoreTokens()) {
+    nsCOMPtr<nsIURI> uri, baseURI = aContent->GetBaseURI();
+    ios->NewURI(NS_ConvertUTF16toUTF8(tokenizer.nextToken()),
+                charset.get(), baseURI, getter_AddRefs(uri));
+    // if we can't generate a valid URI, then there is nothing to do
+    if (!uri) {
+      continue;
+    }
+    // Explicitly not allow loading data: URIs
+    bool isDataScheme =
+      (NS_SUCCEEDED(uri->SchemeIs("data", &isDataScheme)) && isDataScheme);
+
+    if (!isDataScheme) {
+      aCallback(aClosure, aContent, uri, ios);
+    }
+  }
+}
+
+// Spec: http://whatwg.org/specs/web-apps/current-work/#ping
+/*static*/ void
+nsPingListener::DispatchPings(nsIDocShell* aDocShell,
+                              nsIContent* aContent,
+                              nsIURI* aTarget,
+                              nsIURI* aReferrer,
+                              uint32_t aReferrerPolicy)
+{
+  SendPingInfo info;
+
+  if (!PingsEnabled(&info.maxPings, &info.requireSameHost)) {
+    return;
+  }
+  if (info.maxPings == 0) {
+    return;
+  }
+
+  info.numPings = 0;
+  info.target = aTarget;
+  info.referrer = aReferrer;
+  info.referrerPolicy = aReferrerPolicy;
+  info.docShell = aDocShell;
+
+  ForEachPing(aContent, SendPing, &info);
+}
+
+nsPingListener::~nsPingListener()
+{
+  if (mTimer) {
+    mTimer->Cancel();
+    mTimer = nullptr;
+  }
+}
+
+nsresult
+nsPingListener::StartTimeout(DocGroup* aDocGroup)
+{
+  NS_ENSURE_ARG(aDocGroup);
+
+  return NS_NewTimerWithFuncCallback(getter_AddRefs(mTimer),
+                                     OnPingTimeout,
+                                     mLoadGroup,
+                                     PING_TIMEOUT,
+                                     nsITimer::TYPE_ONE_SHOT,
+                                     "nsPingListener::StartTimeout",
+                                     aDocGroup->EventTargetFor(TaskCategory::Network));
+}
+
+NS_IMETHODIMP
+nsPingListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPingListener::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
+                                nsIInputStream* aStream, uint64_t aOffset,
+                                uint32_t aCount)
+{
+  uint32_t result;
+  return aStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &result);
+}
+
+NS_IMETHODIMP
+nsPingListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
+                              nsresult aStatus)
+{
+  mLoadGroup = nullptr;
+
+  if (mTimer) {
+    mTimer->Cancel();
+    mTimer = nullptr;
+  }
+
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/docshell/base/nsPingListener.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPingListener_h__
+#define nsPingListener_h__
+
+#include "nsIStreamListener.h"
+
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+namespace dom {
+class DocGroup;
+}
+}
+
+class nsIContent;
+class nsIDocShell;
+class nsILoadGroup;
+class nsITimer;
+class nsIURI;
+
+class nsPingListener final : public nsIStreamListener
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIREQUESTOBSERVER
+  NS_DECL_NSISTREAMLISTENER
+
+  nsPingListener()
+  {
+  }
+
+  void SetLoadGroup(nsILoadGroup* aLoadGroup) {
+    mLoadGroup = aLoadGroup;
+  }
+
+  nsresult StartTimeout(mozilla::dom::DocGroup* aDocGroup);
+
+  static void DispatchPings(nsIDocShell* aDocShell,
+              nsIContent* aContent,
+              nsIURI* aTarget,
+              nsIURI* aReferrer,
+              uint32_t aReferrerPolicy);
+private:
+  ~nsPingListener();
+
+  nsCOMPtr<nsILoadGroup> mLoadGroup;
+  nsCOMPtr<nsITimer> mTimer;
+};
+
+#endif /* nsPingListener_h__ */
new file mode 100644
--- /dev/null
+++ b/docshell/base/nsRefreshTimer.cpp
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsRefreshTimer.h"
+
+#include "nsIURI.h"
+#include "nsIPrincipal.h"
+
+#include "nsDocShell.h"
+
+NS_IMPL_ADDREF(nsRefreshTimer)
+NS_IMPL_RELEASE(nsRefreshTimer)
+
+NS_INTERFACE_MAP_BEGIN(nsRefreshTimer)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITimerCallback)
+  NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
+  NS_INTERFACE_MAP_ENTRY(nsINamed)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+nsRefreshTimer::nsRefreshTimer(nsDocShell* aDocShell,
+                               nsIURI* aURI,
+                               nsIPrincipal* aPrincipal,
+                               int32_t aDelay, bool aRepeat, bool aMetaRefresh)
+  : mDocShell(aDocShell), mURI(aURI), mPrincipal(aPrincipal),
+    mDelay(aDelay), mRepeat(aRepeat),
+    mMetaRefresh(aMetaRefresh)
+{
+}
+
+nsRefreshTimer::~nsRefreshTimer()
+{
+}
+
+NS_IMETHODIMP
+nsRefreshTimer::Notify(nsITimer* aTimer)
+{
+  NS_ASSERTION(mDocShell, "DocShell is somehow null");
+
+  if (mDocShell && aTimer) {
+    // Get the delay count to determine load type
+    uint32_t delay = 0;
+    aTimer->GetDelay(&delay);
+    mDocShell->ForceRefreshURIFromTimer(mURI, mPrincipal, delay, mMetaRefresh, aTimer);
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsRefreshTimer::GetName(nsACString& aName)
+{
+  aName.AssignLiteral("nsRefreshTimer");
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/docshell/base/nsRefreshTimer.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsRefreshTimer_h__
+#define nsRefreshTimer_h__
+
+#include "nsINamed.h"
+#include "nsITimer.h"
+
+#include "nsCOMPtr.h"
+
+class nsDocShell;
+class nsIURI;
+class nsIPrincipal;
+
+class nsRefreshTimer : public nsITimerCallback
+                     , public nsINamed
+{
+public:
+  nsRefreshTimer(nsDocShell* aDocShell,
+                 nsIURI* aURI,
+                 nsIPrincipal* aPrincipal,
+                 int32_t aDelay,
+                 bool aRepeat,
+                 bool aMetaRefresh);
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSITIMERCALLBACK
+  NS_DECL_NSINAMED
+
+  int32_t GetDelay() { return mDelay ;}
+
+  RefPtr<nsDocShell> mDocShell;
+  nsCOMPtr<nsIURI> mURI;
+  nsCOMPtr<nsIPrincipal> mPrincipal;
+  int32_t mDelay;
+  bool mRepeat;
+  bool mMetaRefresh;
+
+private:
+  virtual ~nsRefreshTimer();
+};
+
+#endif /* nsRefreshTimer_h__ */
--- a/docshell/shistory/nsSHistory.cpp
+++ b/docshell/shistory/nsSHistory.cpp
@@ -372,16 +372,233 @@ nsSHistory::Shutdown()
     if (obsSvc) {
       obsSvc->RemoveObserver(gObserver, "cacheservice:empty-cache");
       obsSvc->RemoveObserver(gObserver, "memory-pressure");
     }
     gObserver = nullptr;
   }
 }
 
+// static
+nsISHEntry*
+nsSHistory::GetRootSHEntry(nsISHEntry* aEntry)
+{
+  nsCOMPtr<nsISHEntry> rootEntry = aEntry;
+  nsISHEntry* result = nullptr;
+  while (rootEntry) {
+    result = rootEntry;
+    result->GetParent(getter_AddRefs(rootEntry));
+  }
+
+  return result;
+}
+
+// static
+nsresult
+nsSHistory::WalkHistoryEntries(nsISHEntry* aRootEntry,
+                               nsDocShell* aRootShell,
+                               WalkHistoryEntriesFunc aCallback,
+                               void* aData)
+{
+  NS_ENSURE_TRUE(aRootEntry, NS_ERROR_FAILURE);
+
+  nsCOMPtr<nsISHContainer> container(do_QueryInterface(aRootEntry));
+  if (!container) {
+    return NS_ERROR_FAILURE;
+  }
+
+  int32_t childCount;
+  container->GetChildCount(&childCount);
+  for (int32_t i = 0; i < childCount; i++) {
+    nsCOMPtr<nsISHEntry> childEntry;
+    container->GetChildAt(i, getter_AddRefs(childEntry));
+    if (!childEntry) {
+      // childEntry can be null for valid reasons, for example if the
+      // docshell at index i never loaded anything useful.
+      // Remember to clone also nulls in the child array (bug 464064).
+      aCallback(nullptr, nullptr, i, aData);
+      continue;
+    }
+
+    nsDocShell* childShell = nullptr;
+    if (aRootShell) {
+      // Walk the children of aRootShell and see if one of them
+      // has srcChild as a SHEntry.
+      int32_t length;
+      aRootShell->GetChildCount(&length);
+      for (int32_t i = 0; i < length; i++) {
+        nsCOMPtr<nsIDocShellTreeItem> item;
+        nsresult rv = aRootShell->GetChildAt(i, getter_AddRefs(item));
+        NS_ENSURE_SUCCESS(rv, rv);
+        nsDocShell* child = static_cast<nsDocShell*>(item.get());
+        if (child->HasHistoryEntry(childEntry)) {
+          childShell = child;
+          break;
+        }
+      }
+    }
+    nsresult rv = aCallback(childEntry, childShell, i, aData);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return NS_OK;
+}
+
+// callback data for WalkHistoryEntries
+struct MOZ_STACK_CLASS CloneAndReplaceData
+{
+  CloneAndReplaceData(uint32_t aCloneID, nsISHEntry* aReplaceEntry,
+                      bool aCloneChildren, nsISHEntry* aDestTreeParent)
+    : cloneID(aCloneID)
+    , cloneChildren(aCloneChildren)
+    , replaceEntry(aReplaceEntry)
+    , destTreeParent(aDestTreeParent)
+  {
+  }
+
+  uint32_t cloneID;
+  bool cloneChildren;
+  nsISHEntry* replaceEntry;
+  nsISHEntry* destTreeParent;
+  nsCOMPtr<nsISHEntry> resultEntry;
+};
+
+// static
+nsresult
+nsSHistory::CloneAndReplaceChild(nsISHEntry* aEntry,
+                                          nsDocShell* aShell,
+                                          int32_t aEntryIndex,
+                                          void* aData)
+{
+  nsCOMPtr<nsISHEntry> dest;
+
+  CloneAndReplaceData* data = static_cast<CloneAndReplaceData*>(aData);
+  uint32_t cloneID = data->cloneID;
+  nsISHEntry* replaceEntry = data->replaceEntry;
+
+  nsCOMPtr<nsISHContainer> container = do_QueryInterface(data->destTreeParent);
+  if (!aEntry) {
+    if (container) {
+      container->AddChild(nullptr, aEntryIndex);
+    }
+    return NS_OK;
+  }
+
+  uint32_t srcID;
+  aEntry->GetID(&srcID);
+
+  nsresult rv = NS_OK;
+  if (srcID == cloneID) {
+    // Replace the entry
+    dest = replaceEntry;
+  } else {
+    // Clone the SHEntry...
+    rv = aEntry->Clone(getter_AddRefs(dest));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  dest->SetIsSubFrame(true);
+
+  if (srcID != cloneID || data->cloneChildren) {
+    // Walk the children
+    CloneAndReplaceData childData(cloneID, replaceEntry,
+                                  data->cloneChildren, dest);
+    rv = WalkHistoryEntries(aEntry, aShell,
+                            CloneAndReplaceChild, &childData);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  if (srcID != cloneID && aShell) {
+    aShell->SwapHistoryEntries(aEntry, dest);
+  }
+
+  if (container) {
+    container->AddChild(dest, aEntryIndex);
+  }
+
+  data->resultEntry = dest;
+  return rv;
+}
+
+// static
+nsresult
+nsSHistory::CloneAndReplace(nsISHEntry* aSrcEntry,
+                                     nsDocShell* aSrcShell,
+                                     uint32_t aCloneID,
+                                     nsISHEntry* aReplaceEntry,
+                                     bool aCloneChildren,
+                                     nsISHEntry** aResultEntry)
+{
+  NS_ENSURE_ARG_POINTER(aResultEntry);
+  NS_ENSURE_TRUE(aReplaceEntry, NS_ERROR_FAILURE);
+
+  CloneAndReplaceData data(aCloneID, aReplaceEntry, aCloneChildren, nullptr);
+  nsresult rv = CloneAndReplaceChild(aSrcEntry, aSrcShell, 0, &data);
+
+  data.resultEntry.swap(*aResultEntry);
+  return rv;
+}
+
+// static
+nsresult
+nsSHistory::SetChildHistoryEntry(nsISHEntry* aEntry, nsDocShell* aShell,
+                                 int32_t aEntryIndex, void* aData)
+{
+  SwapEntriesData* data = static_cast<SwapEntriesData*>(aData);
+  nsDocShell* ignoreShell = data->ignoreShell;
+
+  if (!aShell || aShell == ignoreShell) {
+    return NS_OK;
+  }
+
+  nsISHEntry* destTreeRoot = data->destTreeRoot;
+
+  nsCOMPtr<nsISHEntry> destEntry;
+  nsCOMPtr<nsISHContainer> container = do_QueryInterface(data->destTreeParent);
+
+  if (container) {
+    // aEntry is a clone of some child of destTreeParent, but since the
+    // trees aren't necessarily in sync, we'll have to locate it.
+    // Note that we could set aShell's entry to null if we don't find a
+    // corresponding entry under destTreeParent.
+
+    uint32_t targetID, id;
+    aEntry->GetID(&targetID);
+
+    // First look at the given index, since this is the common case.
+    nsCOMPtr<nsISHEntry> entry;
+    container->GetChildAt(aEntryIndex, getter_AddRefs(entry));
+    if (entry && NS_SUCCEEDED(entry->GetID(&id)) && id == targetID) {
+      destEntry.swap(entry);
+    } else {
+      int32_t childCount;
+      container->GetChildCount(&childCount);
+      for (int32_t i = 0; i < childCount; ++i) {
+        container->GetChildAt(i, getter_AddRefs(entry));
+        if (!entry) {
+          continue;
+        }
+
+        entry->GetID(&id);
+        if (id == targetID) {
+          destEntry.swap(entry);
+          break;
+        }
+      }
+    }
+  } else {
+    destEntry = destTreeRoot;
+  }
+
+  aShell->SwapHistoryEntries(aEntry, destEntry);
+
+  // Now handle the children of aEntry.
+  SwapEntriesData childData = { ignoreShell, destTreeRoot, destEntry };
+  return WalkHistoryEntries(aEntry, aShell, SetChildHistoryEntry, &childData);
+}
+
 /* Add an entry to the History list at mIndex and
  * increment the index to point to the new entry
  */
 NS_IMETHODIMP
 nsSHistory::AddEntry(nsISHEntry* aSHEntry, bool aPersist)
 {
   NS_ENSURE_ARG(aSHEntry);
 
--- a/docshell/shistory/nsSHistory.h
+++ b/docshell/shistory/nsSHistory.h
@@ -56,16 +56,25 @@ public:
     }
 
   private:
     // HistoryTracker is owned by nsSHistory; it always outlives HistoryTracker
     // so it's safe to use raw pointer here.
     nsSHistory* mSHistory;
   };
 
+  // Structure used in SetChildHistoryEntry
+  struct SwapEntriesData
+  {
+    nsDocShell* ignoreShell;     // constant; the shell to ignore
+    nsISHEntry* destTreeRoot;    // constant; the root of the dest tree
+    nsISHEntry* destTreeParent;  // constant; the node under destTreeRoot
+                                 // whose children will correspond to aEntry
+  };
+
   nsSHistory();
   NS_DECL_ISUPPORTS
   NS_DECL_NSISHISTORY
   NS_DECL_NSISHISTORYINTERNAL
   NS_DECL_NSIWEBNAVIGATION
 
   // One time initialization method called upon docshell module construction
   static nsresult Startup();
@@ -73,16 +82,60 @@ public:
   static void UpdatePrefs();
 
   // Max number of total cached content viewers.  If the pref
   // browser.sessionhistory.max_total_viewers is negative, then
   // this value is calculated based on the total amount of memory.
   // Otherwise, it comes straight from the pref.
   static uint32_t GetMaxTotalViewers() { return sHistoryMaxTotalViewers; }
 
+  // Get the root SHEntry from a given entry.
+  static nsISHEntry* GetRootSHEntry(nsISHEntry* aEntry);
+
+  // Callback prototype for WalkHistoryEntries.
+  // aEntry is the child history entry, aShell is its corresponding docshell,
+  // aChildIndex is the child's index in its parent entry, and aData is
+  // the opaque pointer passed to WalkHistoryEntries.
+  typedef nsresult(*WalkHistoryEntriesFunc)(nsISHEntry* aEntry,
+                                            nsDocShell* aShell,
+                                            int32_t aChildIndex,
+                                            void* aData);
+
+  // Clone a session history tree for subframe navigation.
+  // The tree rooted at |aSrcEntry| will be cloned into |aDestEntry|, except
+  // for the entry with id |aCloneID|, which will be replaced with
+  // |aReplaceEntry|. |aSrcShell| is a (possibly null) docshell which
+  // corresponds to |aSrcEntry| via its mLSHE or mOHE pointers, and will
+  // have that pointer updated to point to the cloned history entry.
+  // If aCloneChildren is true then the children of the entry with id
+  // |aCloneID| will be cloned into |aReplaceEntry|.
+  static nsresult CloneAndReplace(nsISHEntry* aSrcEntry,
+                                  nsDocShell* aSrcShell,
+                                  uint32_t aCloneID,
+                                  nsISHEntry* aReplaceEntry,
+                                  bool aCloneChildren,
+                                  nsISHEntry** aDestEntry);
+
+  // Child-walking callback for CloneAndReplace
+  static nsresult CloneAndReplaceChild(nsISHEntry* aEntry, nsDocShell* aShell,
+                                       int32_t aChildIndex, void* aData);
+
+
+  // Child-walking callback for SetHistoryEntry
+  static nsresult SetChildHistoryEntry(nsISHEntry* aEntry, nsDocShell* aShell,
+                                       int32_t aEntryIndex, void* aData);
+
+  // For each child of aRootEntry, find the corresponding docshell which is
+  // a child of aRootShell, and call aCallback. The opaque pointer aData
+  // is passed to the callback.
+  static nsresult WalkHistoryEntries(nsISHEntry* aRootEntry,
+                                     nsDocShell* aRootShell,
+                                     WalkHistoryEntriesFunc aCallback,
+                                     void* aData);
+
 private:
   virtual ~nsSHistory();
   friend class nsSHEnumerator;
   friend class nsSHistoryObserver;
 
   nsresult GetTransactionAtIndex(int32_t aIndex, nsISHTransaction** aResult);
   nsresult LoadDifferingEntries(nsISHEntry* aPrevEntry, nsISHEntry* aNextEntry,
                                 nsIDocShell* aRootDocShell, long aLoadType,
--- a/dom/base/ShadowRoot.cpp
+++ b/dom/base/ShadowRoot.cpp
@@ -351,37 +351,36 @@ ShadowRoot::AssignSlotFor(nsIContent* aC
 
   HTMLSlotElement* slot = slots->ElementAt(0);
   MOZ_ASSERT(slot);
 
   // Find the appropriate position in the assigned node list for the
   // newly assigned content.
   const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes();
   nsIContent* currentContent = GetHost()->GetFirstChild();
-  bool indexFound = false;
-  uint32_t insertionIndex;
+  Maybe<uint32_t> insertionIndex;
   for (uint32_t i = 0; i < assignedNodes.Length(); i++) {
     // Seek through the host's explicit children until the
     // assigned content is found.
     while (currentContent && currentContent != assignedNodes[i]) {
       if (currentContent == aContent) {
-        indexFound = true;
-        insertionIndex = i;
+        insertionIndex.emplace(i);
+        break;
       }
 
       currentContent = currentContent->GetNextSibling();
     }
 
-    if (indexFound) {
+    if (insertionIndex) {
       break;
     }
   }
 
-  if (indexFound) {
-    slot->InsertAssignedNode(insertionIndex, aContent);
+  if (insertionIndex) {
+    slot->InsertAssignedNode(*insertionIndex, aContent);
   } else {
     slot->AppendAssignedNode(aContent);
   }
 
   return slot;
 }
 
 const HTMLSlotElement*
@@ -529,17 +528,17 @@ ShadowRoot::AttributeChanged(nsIDocument
 void
 ShadowRoot::ContentAppended(nsIDocument* aDocument,
                             nsIContent* aContainer,
                             nsIContent* aFirstNewContent)
 {
   for (nsIContent* content = aFirstNewContent;
        content;
        content = content->GetNextSibling()) {
-    ContentInserted(aDocument, aContainer, aFirstNewContent);
+    ContentInserted(aDocument, aContainer, content);
   }
 }
 
 void
 ShadowRoot::ContentInserted(nsIDocument* aDocument,
                             nsIContent* aContainer,
                             nsIContent* aChild)
 {
new file mode 100644
--- /dev/null
+++ b/dom/base/crashtests/1428053.html
@@ -0,0 +1,23 @@
+<script>
+  try { o1 = document.createElement("h1") } catch (e) {}
+  try { o2 = document.createElement("slot") } catch (e) {}
+  try { o3 = document.createElement("o") } catch (e) {}
+  try { document.documentElement.appendChild(o1) } catch (e) {}
+  try { document.documentElement.appendChild(o3) } catch (e) {}
+  try { o4 = o1.attachShadow({mode: "open"}) } catch (e) {}
+  try { o5 = new Range() } catch (e) {}
+  try { o6 = new MutationObserver(function(arg_0, arg_1) {
+      try { o4.append("", o2, "") } catch (e) {};
+  }) } catch (e) {}
+  try { o5.selectNode(o3); } catch (e) {}
+  try { customElements.define("custom-element_0", class extends HTMLElement {
+      connectedCallback() {
+          try { this.offsetHeight } catch (e) {}
+      }
+  }) } catch (e) {}
+  try { o8 = document.createElement("custom-element_0") } catch (e) {}
+  try { document.documentElement.appendChild(o8) } catch (e) {}
+  try { o6.observe(document, { "childList": [""] }); } catch (e) {}
+  try { document.replaceChild(o5.endContainer, document.documentElement); } catch (e) {}
+  try { o1.insertAdjacentHTML("afterBegin", "<</#"); } catch (e) {}
+</script>
--- a/dom/base/crashtests/crashtests.list
+++ b/dom/base/crashtests/crashtests.list
@@ -234,8 +234,9 @@ load 1403377.html
 load 1405771.html
 load 1406109-1.html
 pref(dom.webcomponents.enabled,true) load 1324463.html
 pref(dom.webcomponents.customelements.enabled,true) load 1413815.html
 load 1411473.html
 pref(dom.webcomponents.enabled,false) load 1422931.html
 pref(dom.webcomponents.enabled,true) load 1419799.html
 skip-if(!browserIsRemote) pref(dom.webcomponents.customelements.enabled,true) pref(dom.disable_open_during_load,false) load 1419902.html # skip on non e10s loads, Bug 1419902
+pref(dom.webcomponents.enabled,true) load 1428053.html
--- a/dom/html/HTMLSlotElement.cpp
+++ b/dom/html/HTMLSlotElement.cpp
@@ -174,39 +174,47 @@ const nsTArray<RefPtr<nsINode>>&
 HTMLSlotElement::AssignedNodes() const
 {
   return mAssignedNodes;
 }
 
 void
 HTMLSlotElement::InsertAssignedNode(uint32_t aIndex, nsINode* aNode)
 {
+  MOZ_ASSERT(!aNode->AsContent()->GetAssignedSlot(), "Losing track of a slot");
   mAssignedNodes.InsertElementAt(aIndex, aNode);
   aNode->AsContent()->SetAssignedSlot(this);
 }
 
 void
 HTMLSlotElement::AppendAssignedNode(nsINode* aNode)
 {
+  MOZ_ASSERT(!aNode->AsContent()->GetAssignedSlot(), "Losing track of a slot");
   mAssignedNodes.AppendElement(aNode);
   aNode->AsContent()->SetAssignedSlot(this);
 }
 
 void
 HTMLSlotElement::RemoveAssignedNode(nsINode* aNode)
 {
+  // This one runs from unlinking, so we can't guarantee that the slot pointer
+  // hasn't been cleared.
+  MOZ_ASSERT(!aNode->AsContent()->GetAssignedSlot() ||
+             aNode->AsContent()->GetAssignedSlot() == this, "How exactly?");
   mAssignedNodes.RemoveElement(aNode);
   aNode->AsContent()->SetAssignedSlot(nullptr);
 }
 
 void
 HTMLSlotElement::ClearAssignedNodes()
 {
-  for (uint32_t i = 0; i < mAssignedNodes.Length(); i++) {
-    mAssignedNodes[i]->AsContent()->SetAssignedSlot(nullptr);
+  for (RefPtr<nsINode>& node : mAssignedNodes) {
+    MOZ_ASSERT(!node->AsContent()->GetAssignedSlot() ||
+               node->AsContent()->GetAssignedSlot() == this, "How exactly?");
+    node->AsContent()->SetAssignedSlot(nullptr);
   }
 
   mAssignedNodes.Clear();
 }
 
 void
 HTMLSlotElement::EnqueueSlotChangeEvent() const
 {
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -1806,17 +1806,20 @@ ContentChild::RecvPBrowserConstructor(PB
 
   static bool hasRunOnce = false;
   if (!hasRunOnce) {
     hasRunOnce = true;
     MOZ_ASSERT(!gFirstIdleTask);
     RefPtr<CancelableRunnable> firstIdleTask = NewCancelableRunnableFunction("FirstIdleRunnable",
                                                                              FirstIdle);
     gFirstIdleTask = firstIdleTask;
-    NS_IdleDispatchToCurrentThread(firstIdleTask.forget());
+    if (NS_FAILED(NS_IdleDispatchToCurrentThread(firstIdleTask.forget()))) {
+      gFirstIdleTask = nullptr;
+      hasRunOnce = false;
+    }
   }
 
   return nsIContentChild::RecvPBrowserConstructor(aActor,
                                                   aTabId,
                                                   aSameTabGroupAs,
                                                   aContext,
                                                   aChromeFlags,
                                                   aCpID,
@@ -2312,16 +2315,17 @@ ContentChild::ActorDestroy(ActorDestroyR
 #ifndef NS_FREE_PERMANENT_DATA
   // In release builds, there's no point in the content process
   // going through the full XPCOM shutdown path, because it doesn't
   // keep persistent state.
   ProcessChild::QuickExit();
 #else
   if (gFirstIdleTask) {
     gFirstIdleTask->Cancel();
+    gFirstIdleTask = nullptr;
   }
 
   nsHostObjectProtocolHandler::RemoveDataEntries();
 
   mAlertObservers.Clear();
 
   mIdleObservers.Clear();
 
--- a/dom/media/MediaCache.cpp
+++ b/dom/media/MediaCache.cpp
@@ -11,18 +11,18 @@
 #include "MediaBlockCacheBase.h"
 #include "MediaPrefs.h"
 #include "MediaResource.h"
 #include "MemoryBlockCache.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/ErrorNames.h"
 #include "mozilla/Logging.h"
+#include "mozilla/Monitor.h"
 #include "mozilla/Preferences.h"
-#include "mozilla/ReentrantMonitor.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/SystemGroup.h"
 #include "mozilla/Telemetry.h"
 #include "nsContentUtils.h"
 #include "nsIObserverService.h"
 #include "nsIPrincipal.h"
 #include "nsPrintfCString.h"
@@ -137,18 +137,17 @@ MediaCacheFlusher::UnregisterMediaCache(
 
   if (gMediaCacheFlusher->mMediaCaches.Length() == 0) {
     gMediaCacheFlusher = nullptr;
   }
 }
 
 class MediaCache
 {
-  using AutoLock = ReentrantMonitorAutoEnter;
-  using AutoUnlock = ReentrantMonitorAutoExit;
+  using AutoLock = MonitorAutoLock;
 
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaCache)
 
   friend class MediaCacheStream::BlockList;
   typedef MediaCacheStream::BlockList BlockList;
   static const int64_t BLOCK_SIZE = MediaCacheStream::BLOCK_SIZE;
 
@@ -243,32 +242,36 @@ public:
 
 #ifdef DEBUG_VERIFY_CACHE
   // Verify invariants, especially block list invariants
   void Verify(AutoLock&);
 #else
   void Verify(AutoLock&) {}
 #endif
 
-  ReentrantMonitor& Monitor() { return mMonitor; }
+  mozilla::Monitor& Monitor()
+  {
+    MOZ_DIAGNOSTIC_ASSERT(!NS_IsMainThread());
+    return mMonitor;
+  }
 
   /**
    * An iterator that makes it easy to iterate through all streams that
    * have a given resource ID and are not closed.
    * Must be used while holding the media cache lock.
    */
   class ResourceStreamIterator
   {
   public:
     ResourceStreamIterator(MediaCache* aMediaCache, int64_t aResourceID)
       : mMediaCache(aMediaCache)
       , mResourceID(aResourceID)
       , mNext(0)
     {
-      aMediaCache->mMonitor.AssertCurrentThreadIn();
+      aMediaCache->mMonitor.AssertCurrentThreadOwns();
     }
     MediaCacheStream* Next(AutoLock& aLock)
     {
       while (mNext < mMediaCache->mStreams.Length()) {
         MediaCacheStream* stream = mMediaCache->mStreams[mNext];
         ++mNext;
         if (stream->GetResourceID() == mResourceID && !stream->IsClosed(aLock))
           return stream;
@@ -438,17 +441,17 @@ protected:
 
   // This member is main-thread only. It's used to allocate unique
   // resource IDs to streams.
   int64_t mNextResourceID = 0;
 
   // The monitor protects all the data members here. Also, off-main-thread
   // readers that need to block will Wait() on this monitor. When new
   // data becomes available in the cache, we NotifyAll() on this monitor.
-  ReentrantMonitor mMonitor;
+  mozilla::Monitor mMonitor;
   // This must always be accessed when the monitor is held.
   nsTArray<MediaCacheStream*> mStreams;
   // The Blocks describing the cache entries.
   nsTArray<Block> mIndex;
   // Keep track for highest number of blocks used, for telemetry purposes.
   int32_t mIndexWatermark = 0;
   // Keep track for highest number of blocks owners, for telemetry purposes.
   uint32_t mBlockOwnersWatermark = 0;
@@ -808,27 +811,20 @@ MediaCache::GetMediaCache(int64_t aConte
 
 nsresult
 MediaCache::ReadCacheFile(AutoLock&,
                           int64_t aOffset,
                           void* aData,
                           int32_t aLength,
                           int32_t* aBytes)
 {
-  RefPtr<MediaBlockCacheBase> blockCache = mBlockCache;
-  if (!blockCache) {
+  if (!mBlockCache) {
     return NS_ERROR_FAILURE;
   }
-  {
-    // Since the monitor might be acquired on the main thread, we need to drop
-    // the monitor while doing IO in order not to block the main thread.
-    AutoUnlock unlock(mMonitor);
-    return blockCache->Read(
-      aOffset, reinterpret_cast<uint8_t*>(aData), aLength, aBytes);
-  }
+  return mBlockCache->Read(aOffset, reinterpret_cast<uint8_t*>(aData), aLength, aBytes);
 }
 
 // Allowed range is whatever can be accessed with an int32_t block index.
 static bool
 IsOffsetAllowed(int64_t aOffset)
 {
   return aOffset < (int64_t(INT32_MAX) + 1) * MediaCache::BLOCK_SIZE &&
          aOffset >= 0;
@@ -2671,20 +2667,19 @@ MediaCacheStream::ReadBlockFromCache(Aut
     mMediaCache->NoteBlockUsage(
       aLock, this, cacheBlock, aOffset, mCurrentMode, TimeStamp::Now());
   }
 
   return bytesRead;
 }
 
 nsresult
-MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
+MediaCacheStream::Read(AutoLock& aLock, char* aBuffer, uint32_t aCount, uint32_t* aBytes)
 {
   MOZ_ASSERT(!NS_IsMainThread());
-  AutoLock lock(mMediaCache->Monitor());
 
   // Cache the offset in case it is changed again when we are waiting for the
   // monitor to be notified to avoid reading at the wrong position.
   auto streamOffset = mStreamOffset;
 
   // The buffer we are about to fill.
   auto buffer = MakeSpan<char>(aBuffer, aCount);
 
@@ -2700,17 +2695,17 @@ MediaCacheStream::Read(char* aBuffer, ui
     }
 
     if (mStreamLength >= 0 && streamOffset >= mStreamLength) {
       // Don't try to read beyond the end of the stream
       break;
     }
 
     Result<uint32_t, nsresult> rv = ReadBlockFromCache(
-      lock, streamOffset, buffer, true /* aNoteBlockUsage */);
+      aLock, streamOffset, buffer, true /* aNoteBlockUsage */);
     if (rv.isErr()) {
       return rv.unwrapErr();
     }
 
     uint32_t bytes = rv.unwrap();
     if (bytes > 0) {
       // Got data from the cache successfully. Read next block.
       streamOffset += bytes;
@@ -2718,78 +2713,78 @@ MediaCacheStream::Read(char* aBuffer, ui
       continue;
     }
 
     // See if we can use the data in the partial block of any stream reading
     // this resource. Note we use the partial block only when it is completed,
     // that is reaching EOS.
     bool foundDataInPartialBlock = false;
     MediaCache::ResourceStreamIterator iter(mMediaCache, mResourceID);
-    while (MediaCacheStream* stream = iter.Next(lock)) {
+    while (MediaCacheStream* stream = iter.Next(aLock)) {
       if (OffsetToBlockIndexUnchecked(stream->mChannelOffset) ==
             OffsetToBlockIndexUnchecked(streamOffset) &&
           stream->mChannelOffset == stream->mStreamLength) {
-        uint32_t bytes = stream->ReadPartialBlock(lock, streamOffset, buffer);
+        uint32_t bytes = stream->ReadPartialBlock(aLock, streamOffset, buffer);
         streamOffset += bytes;
         buffer = buffer.From(bytes);
         foundDataInPartialBlock = true;
         break;
       }
     }
     if (foundDataInPartialBlock) {
       // Break for we've reached EOS.
       break;
     }
 
     if (mDidNotifyDataEnded && NS_FAILED(mNotifyDataEndedStatus)) {
       // Since download ends abnormally, there is no point in waiting for new
       // data to come. We will check the partial block to read as many bytes as
       // possible before exiting this function.
-      bytes = ReadPartialBlock(lock, streamOffset, buffer);
+      bytes = ReadPartialBlock(aLock, streamOffset, buffer);
       streamOffset += bytes;
       buffer = buffer.From(bytes);
       break;
     }
 
     if (mStreamOffset != streamOffset) {
       // Update mStreamOffset before we drop the lock. We need to run
       // Update() again since stream reading strategy might have changed.
       mStreamOffset = streamOffset;
-      mMediaCache->QueueUpdate(lock);
+      mMediaCache->QueueUpdate(aLock);
     }
 
     // No data to read, so block
-    lock.Wait();
+    aLock.Wait();
     continue;
   }
 
   uint32_t count = buffer.Elements() - aBuffer;
   *aBytes = count;
   if (count == 0) {
     return NS_OK;
   }
 
   // Some data was read, so queue an update since block priorities may
   // have changed
-  mMediaCache->QueueUpdate(lock);
+  mMediaCache->QueueUpdate(aLock);
 
   LOG("Stream %p Read at %" PRId64 " count=%d", this, streamOffset-count, count);
   mStreamOffset = streamOffset;
   return NS_OK;
 }
 
 nsresult
 MediaCacheStream::ReadAt(int64_t aOffset, char* aBuffer,
                          uint32_t aCount, uint32_t* aBytes)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   AutoLock lock(mMediaCache->Monitor());
   nsresult rv = Seek(lock, aOffset);
   if (NS_FAILED(rv)) return rv;
-  return Read(aBuffer, aCount, aBytes);
+  return Read(lock, aBuffer, aCount, aBytes);
 }
 
 nsresult
 MediaCacheStream::ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   AutoLock lock(mMediaCache->Monitor());
 
--- a/dom/media/MediaCache.h
+++ b/dom/media/MediaCache.h
@@ -21,17 +21,17 @@
 class nsIEventTarget;
 class nsIPrincipal;
 
 namespace mozilla {
 // defined in MediaResource.h
 class ChannelMediaResource;
 typedef media::IntervalSet<int64_t> MediaByteRangeSet;
 class MediaResource;
-class ReentrantMonitorAutoEnter;
+class MonitorAutoLock;
 
 /**
  * Media applications want fast, "on demand" random access to media data,
  * for pausing, seeking, etc. But we are primarily interested
  * in transporting media data using HTTP over the Internet, which has
  * high latency to open a connection, requires a new connection for every
  * seek, may not even support seeking on some connections (especially
  * live streams), and uses a push model --- data comes from the server
@@ -187,17 +187,17 @@ DDLoggedTypeDeclName(MediaCacheStream);
 /**
  * If the cache fails to initialize then Init will fail, so nonstatic
  * methods of this class can assume gMediaCache is non-null.
  *
  * This class can be directly embedded as a value.
  */
 class MediaCacheStream : public DecoderDoctorLifeLogger<MediaCacheStream>
 {
-  using AutoLock = ReentrantMonitorAutoEnter;
+  using AutoLock = MonitorAutoLock;
 
 public:
   // This needs to be a power of two
   static const int64_t BLOCK_SIZE = 32768;
 
   enum ReadMode {
     MODE_METADATA,
     MODE_PLAYBACK
@@ -345,17 +345,17 @@ public:
 
   // These methods must be called on a different thread from the main
   // thread. They should always be called on the same thread for a given
   // stream.
   // *aBytes gets the number of bytes that were actually read. This can
   // be less than aCount. If the first byte of data is not in the cache,
   // this will block until the data is available or the stream is
   // closed, otherwise it won't block.
-  nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes);
+  nsresult Read(AutoLock&, char* aBuffer, uint32_t aCount, uint32_t* aBytes);
   // Seeks to aOffset in the stream then performs a Read operation. See
   // 'Read' for argument and return details.
   nsresult ReadAt(int64_t aOffset, char* aBuffer,
                   uint32_t aCount, uint32_t* aBytes);
 
   void ThrottleReadahead(bool bThrottle);
 
   size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -3262,84 +3262,16 @@ HTMLEditor::IsModifiableNode(nsIDOMNode*
 }
 
 bool
 HTMLEditor::IsModifiableNode(nsINode* aNode)
 {
   return !aNode || aNode->IsEditable();
 }
 
-static nsresult
-SetSelectionAroundHeadChildren(Selection* aSelection,
-                               nsCOMPtr<nsIDocument>& aDocument)
-{
-  MOZ_ASSERT(aDocument);
-
-  // Set selection around <head> node
-  dom::Element* headNode = aDocument->GetHeadElement();
-  NS_ENSURE_STATE(headNode);
-
-  // Collapse selection to before first child of the head,
-  nsresult rv = aSelection->Collapse(headNode, 0);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // Then extend it to just after.
-  uint32_t childCount = headNode->GetChildCount();
-  return aSelection->Extend(headNode, childCount + 1);
-}
-
-NS_IMETHODIMP
-HTMLEditor::GetHeadContentsAsHTML(nsAString& aOutputString)
-{
-  RefPtr<Selection> selection = GetSelection();
-  NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
-
-  // Save current selection
-  AutoSelectionRestorer selectionRestorer(selection, this);
-
-  nsCOMPtr<nsIDocument> document = GetDocument();
-  if (NS_WARN_IF(!document)) {
-    return NS_ERROR_NOT_INITIALIZED;
-  }
-  nsresult rv = SetSelectionAroundHeadChildren(selection, document);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = OutputToString(NS_LITERAL_STRING("text/html"),
-                      nsIDocumentEncoder::OutputSelectionOnly,
-                      aOutputString);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  // Selection always includes <body></body>,
-  //  so terminate there
-  nsReadingIterator<char16_t> findIter,endFindIter;
-  aOutputString.BeginReading(findIter);
-  aOutputString.EndReading(endFindIter);
-  //counting on our parser to always lower case!!!
-  if (CaseInsensitiveFindInReadable(NS_LITERAL_STRING("<body"),
-                                    findIter, endFindIter)) {
-    nsReadingIterator<char16_t> beginIter;
-    aOutputString.BeginReading(beginIter);
-    int32_t offset = Distance(beginIter, findIter);//get the distance
-
-    nsWritingIterator<char16_t> writeIter;
-    aOutputString.BeginWriting(writeIter);
-    // Ensure the string ends in a newline
-    char16_t newline ('\n');
-    findIter.advance(-1);
-    if (!offset || (offset > 0 && (*findIter) != newline)) {
-      writeIter.advance(offset);
-      *writeIter = newline;
-      aOutputString.Truncate(offset+1);
-    }
-  }
-  return NS_OK;
-}
-
 NS_IMETHODIMP
 HTMLEditor::DebugUnitTests(int32_t* outNumTests,
                            int32_t* outNumTestsFailed)
 {
 #ifdef DEBUG
   NS_ENSURE_TRUE(outNumTests && outNumTestsFailed, NS_ERROR_NULL_POINTER);
 
   TextEditorTest *tester = new TextEditorTest();
--- a/editor/nsIHTMLEditor.idl
+++ b/editor/nsIHTMLEditor.idl
@@ -377,21 +377,16 @@ interface nsIHTMLEditor : nsISupports
    *    Use "anchor" or "namedanchor" to get a named anchor node
    *      (an "A" tag with the "name" attribute set)
    * @return          NS_EDITOR_ELEMENT_NOT_FOUND if an element is not found
    *                  (passes NS_SUCCEEDED macro)
    */
   nsIDOMElement getSelectedElement(in AString aTagName);
 
   /**
-   * Output the contents of the <HEAD> section as text/HTML format
-   */
-  AString getHeadContentsAsHTML();
-
-  /**
    * Replace all children of <HEAD> with string of HTML source
    */
   void replaceHeadContentsWithHTML(in AString aSourceToInsert);
 
   /**
    * Return a new element with default attribute values
    *
    * This does not rely on the selection, and is not sensitive to context.
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -904,17 +904,16 @@ ServoRestyleManager::ProcessPostTraversa
     if (MOZ_UNLIKELY(displayContentsStyle)) {
       MOZ_ASSERT(!styleFrame);
       PresContext()->FrameConstructor()->
         ChangeRegisteredDisplayContentsStyleFor(aElement, upToDateContext);
     }
 
     if (styleFrame) {
       UpdateAdditionalStyleContexts(styleFrame, aRestyleState);
-      styleFrame->UpdateStyleOfOwnedAnonBoxes(childrenRestyleState);
     }
 
     if (!aElement->GetParent()) {
       // This is the root.  Update styles on the viewport as needed.
       ViewportFrame* viewport =
         do_QueryFrame(mPresContext->PresShell()->GetRootFrame());
       if (viewport) {
         // NB: The root restyle state, not the one for our children!
@@ -968,16 +967,20 @@ ServoRestyleManager::ProcessPostTraversa
   // kids, because some of those updates (::first-line/::first-letter) need to
   // modify the styles of the kids, and the child traversal above would just
   // clobber those modifications.
   if (styleFrame) {
     // Process anon box wrapper frames before ::first-line bits.
     childrenRestyleState.ProcessWrapperRestyles(styleFrame);
 
     if (wasRestyled) {
+      // Make sure to update anon boxes and pseudo bits after updating text,
+      // otherwise we could clobber first-letter styles from
+      // ProcessPostTraversalForText, for example.
+      styleFrame->UpdateStyleOfOwnedAnonBoxes(childrenRestyleState);
       UpdateFramePseudoElementStyles(styleFrame, childrenRestyleState);
     } else if (traverseElementChildren &&
                styleFrame->IsFrameOfType(nsIFrame::eBlockFrame)) {
       // Even if we were not restyled, if we're a block with a first-line and
       // one of our descendant elements which is on the first line was restyled,
       // we need to update the styles of things on the first line, because
       // they're wrong now.
       //
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -6497,19 +6497,16 @@ nsCSSFrameConstructor::GetFloatContainin
  */
 static nsIFrame*
 FindAppendPrevSibling(nsIFrame* aParentFrame, nsIFrame* aNextSibling)
 {
   aParentFrame->DrainSelfOverflowList();
 
   if (aNextSibling) {
     MOZ_ASSERT(aNextSibling->GetParent() == aParentFrame, "Wrong parent");
-    MOZ_ASSERT(aNextSibling->GetPrevSibling() ||
-               aParentFrame->PrincipalChildList().FirstChild() == aNextSibling,
-               "next sibling must be on the principal child list here");
     return aNextSibling->GetPrevSibling();
   }
 
   return aParentFrame->GetChildList(kPrincipalList).LastChild();
 }
 
 /**
  * Finds the right parent frame to append content to aParentFrame.
new file mode 100644
--- /dev/null
+++ b/layout/generic/crashtests/1427824.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<div id="container"></div>
+<script>
+let contents = document.createElement('div');
+contents.style.display = "contents";
+container.appendChild(contents);
+container.appendChild(document.createElement('colgroup'));
+container.offsetTop;
+contents.appendChild(document.createElement('colgroup'));
+</script>
--- a/layout/generic/crashtests/crashtests.list
+++ b/layout/generic/crashtests/crashtests.list
@@ -671,8 +671,9 @@ load 1375858.html
 load 1381134.html
 load 1381134-2.html
 load 1401420-1.html
 load 1401709.html
 load 1401807.html
 load 1405443.html
 load 1415185.html
 load 1416544.html
+load 1427824.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/first-letter/dynamic-5-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<style>
+fieldset::first-letter {
+  color: green;
+}
+</style>
+<fieldset>Text</fieldset>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/first-letter/dynamic-5.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<style>
+fieldset::first-letter {
+  color: red;
+}
+fieldset.f::first-letter {
+  color: green;
+}
+</style>
+<fieldset>Text</fieldset>
+<script>
+window.onload = function() {
+  document.querySelector('fieldset').classList.add('f');
+}
+</script>
--- a/layout/reftests/first-letter/reftest.list
+++ b/layout/reftests/first-letter/reftest.list
@@ -22,16 +22,17 @@ fails == quote-1d.html quote-1-ref.html
 fails == quote-1e.html quote-1-ref.html # bug 509685
 == quote-1e.html quote-1b.html
 == quote-1f.html quote-1-ref.html
 fails-if(!stylo) fails-if(styloVsGecko) == dynamic-1.html dynamic-1-ref.html # bug 8253
 random-if(d2d) == dynamic-2.html dynamic-2-ref.html
 == dynamic-3a.html dynamic-3-ref.html
 == dynamic-3b.html dynamic-3-ref.html
 == dynamic-4.html dynamic-4-ref.html
+== dynamic-5.html dynamic-5-ref.html
 == 23605-1.html 23605-1-ref.html
 == 23605-2.html 23605-2-ref.html
 == 23605-3.html 23605-3-ref.html
 == 23605-4.html 23605-4-ref.html
 == 23605-5.html 23605-5-ref.html
 == 23605-6.html 23605-6-ref.html
 != 229764-1.html 229764-ref.html
 == 229764-2.html 229764-ref.html
--- a/mobile/android/docs/mma.rst
+++ b/mobile/android/docs/mma.rst
@@ -146,16 +146,21 @@ List of current Events related data that
 * App start but Fennec is not set as default browser
 {
   "event" : "E_Launch_But_Not_Default_Browser"
 }
 * General app start event
 {
   "event" : "E_Launch_Browser"
 }
+* The user just dismissed on-boarding
+{
+  "event" : "E_Dismiss_Onboarding"
+}
+
 Deep Links:
 Deep links are actions that can point Fennec to open certain pages or load features such as `show bookmark list` or
 `open a SUMO page`. When users see a prompt Leanplum message, they can click the button(s) on it. These buttons can
 trigger the following deep links
 * Link to Set Default Browser settings (firefox://default_browser)
 * Link to specific Add-on page (http://link_to_the_add_on_page)
 * Link to sync signup/sign in (firefox://sign_up)
 * Link to default search engine settings (firefox://preferences_search)
--- a/mobile/locales/search/list.json
+++ b/mobile/locales/search/list.json
@@ -166,26 +166,36 @@
         "visibleDefaultEngines": [
           "google", "yahoo", "bing", "skroutz", "twitter", "wikipedia-el"
         ]
       }
     },
     "en-GB": {
       "default": {
         "visibleDefaultEngines": [
-          "google", "yahoo-en-GB",  "bing", "amazon-co-uk", "ddg", "qwant", "twitter", "wikipedia"
+          "google", "yahoo-en-GB", "bing", "amazon-co-uk", "ddg", "qwant", "twitter", "wikipedia"
+        ]
+      },
+      "RU": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-en-GB", "bing", "amazon-co-uk", "ddg", "qwant", "twitter", "wikipedia", "yandex-en"
         ]
       }
     },
     "en-US": {
       "default": {
         "visibleDefaultEngines": [
           "google", "yahoo", "bing", "amazondotcom", "ddg", "twitter", "wikipedia"
         ]
       },
+      "RU": {
+        "visibleDefaultEngines": [
+          "google", "yahoo", "bing", "amazondotcom", "ddg", "twitter", "wikipedia", "yandex-en"
+        ]
+      },
       "experimental-hidden": {
         "visibleDefaultEngines": [
           "amazon-ca", "amazon-au", "google-2018", "duckduckgo"
         ]
       }
     },
     "en-ZA": {
       "default": {
new file mode 100644
--- /dev/null
+++ b/mobile/locales/searchplugins/yandex-en.xml
@@ -0,0 +1,22 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yandex</ShortName>
+<Description>Use Yandex to search the Internet.</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAAAqFBMVEUAAAD29PL29PL29PP29PLd3d3w8PD29fP/JQD7ion35uT28/H8bmz36+r6paT+Lh7+KRL+Jwz41tX4ysn4wcD7h4X8dHL+OTD27+333t37lZT7jYz8aWb9Qz3+JQH36Of34+H40tH5vLv5uLj5sbD5rKv8e3n9WVb9Uk/9TUn9SEP+Myf+Jgb32tr4xMP6nJv7mpn7goH8gH78X1z9Pzb+NSn+JQD8Yl99ghQbAAAAB3RSTlMA88/IbQYbgu4OFQAAAe5JREFUaN7t2slywjAMBmCzKjSsISxhXwtlXwp9/zcr+VsQKfQU6dCpdfLooG/isWVPxsaYTDqVIIVIpNIZc4lsktQimTUmo1YfQsakSTXSJkWqkTIJUo2EIeWwgAUsYAFVwK84lyhhrALUnTA+MNYA3BKACSEUAB/1y01CKAAHAHWMYwG1/CXqg4cZOgHw4wPvKNT+ma4ifXLjAy1U6j58GNI1ig8EPZQaPv2woQBAM5TKRZMef1dsYIpajWcT1xIBCmUUq0aSizDVC+ID3BP296kAqRnJABtUW7rEsUWqKAS4DZTbEMc8TFQKQgC1AbxyYoCl2yEpYA2gxHNURGIsAvCMOCO6xhFgUw4oAsjfFu6Kp0wGKFQivX8CryoIUD5yenV4a0sBvnO3bJplNCdRwF3eLfwxNE8O4PY/5daxIFnAA3Dk68RWGKC3sOqqfz0sewNpoHhrb21upJJAH1thfv2WqSjAi/8cfG3jckEeGH0fkmPuGnIAb4UuHdD3pAHeCsMuOrcG4AHIn9FIpQHeCtxI5QFsBW6k4gC2At+E5QGcCtxI5QGcCtxIxQG+IO0IoQDQHsAaYxUgB+AFYwtYwAJEFrCABYgs8DR2zi9X97/5c9wCFrDAfwK0Hw2oP3tQf7ih/vRE/fGM+vOfTzkzCrugwvNDAAAAAElFTkSuQmCC</Image>
+<Url type="application/x-suggestions+json" method="GET" template="http://suggest.yandex.com/suggest-ff.cgi">
+  <Param name="part" value="{searchTerms}"/>
+  <Param name="uil" value="tr"/>
+</Url>
+<Url type="text/html" method="GET" template="https://yandex.com/search">
+  <Param name="clid" value="2186727"/>
+  <Param name="text" value="{searchTerms}"/>
+</Url>
+<Url type="application/x-moz-tabletsearch" method="GET" template="https://yandex.com/search">
+  <Param name="clid" value="2186733"/>
+  <Param name="text" value="{searchTerms}"/>
+</Url>
+</SearchPlugin>
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -46,17 +46,17 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "app_units"
 version = "0.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "arrayvec"
 version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -138,17 +138,17 @@ version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "bincode"
 version = "0.9.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "bindgen"
 version = "0.31.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cexpr 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -199,17 +199,17 @@ dependencies = [
 ]
 
 [[package]]
 name = "bluetooth_traits"
 version = "0.0.1"
 dependencies = [
  "ipc-channel 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_config 0.0.1",
 ]
 
 [[package]]
 name = "blurdroid"
 version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
@@ -298,17 +298,17 @@ dependencies = [
  "cssparser 0.23.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "malloc_size_of 0.0.1",
  "malloc_size_of_derive 0.0.1",
  "nonzero 0.0.1",
  "offscreen_gl_context 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_config 0.0.1",
  "webrender_api 0.56.1 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "caseless"
 version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -319,17 +319,17 @@ dependencies = [
 
 [[package]]
 name = "cbindgen"
 version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "clap 2.28.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "toml 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "cc"
@@ -495,17 +495,17 @@ dependencies = [
  "layout_traits 0.0.1",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "metrics 0.0.1",
  "msg 0.0.1",
  "net 0.0.1",
  "net_traits 0.0.1",
  "profile_traits 0.0.1",
  "script_traits 0.0.1",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_config 0.0.1",
  "servo_rand 0.0.1",
  "servo_remutex 0.0.1",
  "servo_url 0.0.1",
  "style_traits 0.0.1",
  "webrender_api 0.56.1 (git+https://github.com/servo/webrender)",
  "webvr_traits 0.0.1",
 ]
@@ -564,17 +564,17 @@ source = "registry+https://github.com/ru
 dependencies = [
  "cssparser-macros 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "dtoa-short 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
  "procedural-masquerade 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "smallvec 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "cssparser-macros"
 version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -708,33 +708,33 @@ name = "devtools"
 version = "0.0.1"
 dependencies = [
  "devtools_traits 0.0.1",
  "hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)",
  "hyper_serde 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "devtools_traits"
 version = "0.0.1"
 dependencies = [
  "bitflags 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)",
  "hyper_serde 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "malloc_size_of 0.0.1",
  "malloc_size_of_derive 0.0.1",
  "msg 0.0.1",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_url 0.0.1",
  "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "dom_struct"
 version = "0.0.1"
 
@@ -772,18 +772,18 @@ dependencies = [
 name = "dwrote"
 version = "0.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
- "serde_derive 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "either"
 version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
@@ -897,17 +897,17 @@ dependencies = [
 
 [[package]]
 name = "euclid"
 version = "0.16.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "expat-sys"
 version = "2.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cmake 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1076,17 +1076,17 @@ dependencies = [
  "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "malloc_size_of 0.0.1",
  "malloc_size_of_derive 0.0.1",
  "msg 0.0.1",
  "net_traits 0.0.1",
  "ordered-float 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "range 0.0.1",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo-fontconfig 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_allocator 0.0.1",
  "servo_arc 0.0.1",
  "servo_atoms 0.0.1",
  "servo_geometry 0.0.1",
  "servo_url 0.0.1",
  "simd 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "smallvec 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1113,17 +1113,17 @@ dependencies = [
 
 [[package]]
 name = "gfx_traits"
 version = "0.0.1"
 dependencies = [
  "malloc_size_of 0.0.1",
  "malloc_size_of_derive 0.0.1",
  "range 0.0.1",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "gif"
 version = "0.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "color_quant 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1287,17 +1287,17 @@ dependencies = [
 [[package]]
 name = "hyper_serde"
 version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cookie 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)",
  "mime 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_bytes 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "ident_case"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1382,17 +1382,17 @@ version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "bincode 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
  "mio 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "uuid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "itertools"
 version = "0.5.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
@@ -1481,17 +1481,17 @@ dependencies = [
  "ordered-float 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "profile_traits 0.0.1",
  "range 0.0.1",
  "rayon 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "script_layout_interface 0.0.1",
  "script_traits 0.0.1",
  "selectors 0.19.0",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_arc 0.0.1",
  "servo_atoms 0.0.1",
  "servo_config 0.0.1",
  "servo_geometry 0.0.1",
  "servo_url 0.0.1",
  "smallvec 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "style 0.0.1",
@@ -1721,18 +1721,18 @@ dependencies = [
 
 [[package]]
 name = "markup5ever"
 version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
- "serde_derive 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "string_cache 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "string_cache_codegen 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "tendril 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "matches"
@@ -1911,17 +1911,17 @@ dependencies = [
 [[package]]
 name = "msg"
 version = "0.0.1"
 dependencies = [
  "bitflags 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "malloc_size_of 0.0.1",
  "malloc_size_of_derive 0.0.1",
  "nonzero 0.0.1",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "webrender_api 0.56.1 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "msg_tests"
 version = "0.0.1"
 dependencies = [
  "msg 0.0.1",
@@ -1946,17 +1946,17 @@ dependencies = [
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "mime 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "mime_guess 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "net_traits 0.0.1",
  "openssl 0.9.22 (registry+https://github.com/rust-lang/crates.io-index)",
  "profile_traits 0.0.1",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo-websocket 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_config 0.0.1",
  "servo_url 0.0.1",
  "threadpool 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
  "tinyfiledialogs 2.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2009,17 +2009,17 @@ dependencies = [
  "image 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "malloc_size_of 0.0.1",
  "malloc_size_of_derive 0.0.1",
  "msg 0.0.1",
  "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_config 0.0.1",
  "servo_url 0.0.1",
  "url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "uuid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "webrender_api 0.56.1 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
@@ -2038,17 +2038,17 @@ source = "registry+https://github.com/ru
 name = "nom"
 version = "1.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "nonzero"
 version = "0.0.1"
 dependencies = [
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "nsstring"
 version = "0.1.0"
 dependencies = [
  "bitflags 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -2140,17 +2140,17 @@ dependencies = [
  "gl_generator 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.4.15 (registry+https://github.com/rust-lang/crates.io-index)",
  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "libloading 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "objc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "x11 2.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "ogg"
 version = "0.5.0"
@@ -2341,17 +2341,17 @@ dependencies = [
  "heartbeats-simple 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "influent 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "jemalloc-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "profile_traits 0.0.1",
  "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_config 0.0.1",
  "task_info 0.0.1",
  "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "profile_tests"
@@ -2366,17 +2366,17 @@ dependencies = [
 [[package]]
 name = "profile_traits"
 version = "0.0.1"
 dependencies = [
  "energy-monitor 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "energymon 0.3.0 (git+https://github.com/energymon/energymon-rust.git)",
  "ipc-channel 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_config 0.0.1",
  "signpost 0.1.0 (git+https://github.com/pcwalton/signpost.git)",
  "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "pulse"
 version = "0.2.0"
@@ -2409,17 +2409,17 @@ dependencies = [
 
 [[package]]
 name = "range"
 version = "0.0.1"
 dependencies = [
  "malloc_size_of 0.0.1",
  "malloc_size_of_derive 0.0.1",
  "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "rayon"
 version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "rayon-core 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2492,18 +2492,18 @@ dependencies = [
 ]
 
 [[package]]
 name = "rust-webvr-api"
 version = "0.9.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "android_injected_glue 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
- "serde_derive 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "rustc-demangle"
 version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
@@ -2600,17 +2600,17 @@ dependencies = [
  "profile_traits 0.0.1",
  "ref_filter_map 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "ref_slice 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "script_layout_interface 0.0.1",
  "script_plugins 0.0.1",
  "script_traits 0.0.1",
  "selectors 0.19.0",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_allocator 0.0.1",
  "servo_arc 0.0.1",
  "servo_atoms 0.0.1",
  "servo_config 0.0.1",
  "servo_geometry 0.0.1",
  "servo_rand 0.0.1",
  "servo_url 0.0.1",
@@ -2697,17 +2697,17 @@ dependencies = [
  "ipc-channel 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
  "malloc_size_of 0.0.1",
  "malloc_size_of_derive 0.0.1",
  "msg 0.0.1",
  "net_traits 0.0.1",
  "profile_traits 0.0.1",
  "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_atoms 0.0.1",
  "servo_url 0.0.1",
  "style_traits 0.0.1",
  "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
  "url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "webrender_api 0.56.1 (git+https://github.com/servo/webrender)",
  "webvr_traits 0.0.1",
 ]
@@ -2746,58 +2746,58 @@ dependencies = [
 
 [[package]]
 name = "semver-parser"
 version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "serde"
-version = "1.0.23"
-source = "git+https://github.com/gankro/serde?branch=deserialize_from_enums3#fc6117367ef974fb2d3b2017c21c375d34823415"
-dependencies = [
- "serde_derive 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+version = "1.0.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "serde_bytes"
 version = "0.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.23"
-source = "git+https://github.com/gankro/serde?branch=deserialize_from_enums3#fc6117367ef974fb2d3b2017c21c375d34823415"
+version = "1.0.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_derive_internals 0.17.0 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde_derive_internals 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "serde_derive_internals"
-version = "0.17.0"
-source = "git+https://github.com/gankro/serde?branch=deserialize_from_enums3#fc6117367ef974fb2d3b2017c21c375d34823415"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "serde_json"
 version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "servo"
 version = "0.0.1"
 dependencies = [
  "android_injected_glue 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "backtrace 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2915,17 +2915,17 @@ dependencies = [
  "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "servo_arc"
 version = "0.0.1"
 dependencies = [
  "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "servo_atoms"
 version = "0.0.1"
 dependencies = [
  "string_cache 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2939,17 +2939,17 @@ dependencies = [
  "android_injected_glue 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "num_cpus 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_geometry 0.0.1",
  "servo_url 0.0.1",
  "url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "xdg 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "servo_config_tests"
@@ -2994,17 +2994,17 @@ dependencies = [
 ]
 
 [[package]]
 name = "servo_url"
 version = "0.0.1"
 dependencies = [
  "malloc_size_of 0.0.1",
  "malloc_size_of_derive 0.0.1",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_rand 0.0.1",
  "url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "url_serde 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "uuid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "sha1"
@@ -3077,17 +3077,17 @@ source = "registry+https://github.com/ru
 name = "string_cache"
 version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "debug_unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
  "precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "string_cache_codegen 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "string_cache_codegen"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3140,17 +3140,17 @@ dependencies = [
  "num_cpus 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "ordered-float 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "rayon 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "selectors 0.19.0",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_arc 0.0.1",
  "servo_atoms 0.0.1",
  "servo_config 0.0.1",
  "servo_url 0.0.1",
  "smallbitvec 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "smallvec 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "style_derive 0.0.1",
  "style_traits 0.0.1",
@@ -3200,17 +3200,17 @@ version = "0.0.1"
 dependencies = [
  "app_units 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.23.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "malloc_size_of 0.0.1",
  "malloc_size_of_derive 0.0.1",
  "selectors 0.19.0",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_arc 0.0.1",
  "servo_atoms 0.0.1",
  "webrender_api 0.56.1 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "stylo_tests"
 version = "0.0.1"
@@ -3359,17 +3359,17 @@ name = "toml"
 version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "toml"
 version = "0.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "traitobject"
 version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -3407,18 +3407,18 @@ dependencies = [
 ]
 
 [[package]]
 name = "unicode-bidi"
 version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
- "serde_derive 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "unicode-normalization"
 version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -3462,17 +3462,17 @@ dependencies = [
  "percent-encoding 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "url_serde"
 version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "user32-sys"
 version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
@@ -3494,17 +3494,17 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "uuid"
 version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "vcpkg"
 version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -3562,17 +3562,17 @@ dependencies = [
  "url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "uuid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "webdriver 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "webrender"
 version = "0.56.1"
-source = "git+https://github.com/servo/webrender#1142dfc557c319119a5117450718c5b67a93cb9f"
+source = "git+https://github.com/servo/webrender#92959212d069eace3cf1c5f939458108de4432b5"
 dependencies = [
  "app_units 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "bincode 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-foundation 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-text 8.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3590,29 +3590,29 @@ dependencies = [
  "thread_profiler 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
  "webrender_api 0.56.1 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "webrender_api"
 version = "0.56.1"
-source = "git+https://github.com/servo/webrender#1142dfc557c319119a5117450718c5b67a93cb9f"
+source = "git+https://github.com/servo/webrender#92959212d069eace3cf1c5f939458108de4432b5"
 dependencies = [
  "app_units 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "bincode 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-foundation 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "dwrote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
- "serde_derive 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "webvr"
 version = "0.0.1"
 dependencies = [
  "canvas_traits 0.0.1",
@@ -3628,17 +3628,17 @@ dependencies = [
 
 [[package]]
 name = "webvr_traits"
 version = "0.0.1"
 dependencies = [
  "ipc-channel 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "rust-webvr-api 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "which"
 version = "1.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3956,20 +3956,20 @@ dependencies = [
 "checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084"
 "checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f"
 "checksum same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d931a44fdaa43b8637009e7632a02adc4f2b2e0733c08caa4cf00e8da4a117a7"
 "checksum scoped_threadpool 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3ef399c8893e8cb7aa9696e895427fab3a6bf265977bb96e126f24ddd2cda85a"
 "checksum scopeguard 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c79eb2c3ac4bc2507cda80e7f3ac5b88bd8eae4c0914d5663e6a8933994be918"
 "checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac"
 "checksum semver 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537"
 "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
-"checksum serde 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)" = "<none>"
+"checksum serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "db99f3919e20faa51bb2996057f5031d8685019b5a06139b1ce761da671b8526"
 "checksum serde_bytes 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a73f5ad9bb83e1e407254c7a355f4efdaffe3c1442fc0657ddb8b9b6b225655"
-"checksum serde_derive 1.0.23 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)" = "<none>"
-"checksum serde_derive_internals 0.17.0 (git+https://github.com/gankro/serde?branch=deserialize_from_enums3)" = "<none>"
+"checksum serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "f4ba7591cfe93755e89eeecdbcc668885624829b020050e6aec99c2a03bd3fd0"
+"checksum serde_derive_internals 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6e03f1c9530c3fb0a0a5c9b826bdd9246a5921ae995d75f512ac917fc4dd55b5"
 "checksum serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "48b04779552e92037212c3615370f6bd57a40ebba7f20e554ff9f55e41a69a7b"
 "checksum servo-egl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "21069a884c33fe6ee596975e1f3849ed88c4ec857fbaf11d33672d8ebe051217"
 "checksum servo-fontconfig 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "93f799b649b4a2bf362398910eca35240704c7e765e780349b2bb1070d892262"
 "checksum servo-fontconfig-sys 4.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "38b494f03009ee81914b0e7d387ad7c145cafcd69747c2ec89b0e17bb94f303a"
 "checksum servo-freetype-sys 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9232032c2e85118c0282c6562c84cab12316e655491ba0a5d1905b2320060d1b"
 "checksum servo-glutin 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "19ddbce9573bd316e4fe59924386997c4921dfa30c41a188c63cf18319283afa"
 "checksum servo-skia 0.30000009.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b47b49a24c705e80b193cb3c08ef37caef652abd844063f5bfea9b45e858576e"
 "checksum servo-websocket 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efde78dfcf2178d5a11e1e2268e0d8df0627dfe2724546db8585d6678e1af150"
--- a/servo/Cargo.toml
+++ b/servo/Cargo.toml
@@ -19,18 +19,16 @@ codegen-units = 4
 
 [profile.release]
 opt-level = 3
 # Uncomment to profile on Linux:
 # debug = true
 # lto = false
 
 [patch.crates-io]
-serde = { git = "https://github.com/gankro/serde", branch = "deserialize_from_enums3" }
-serde_derive = { git = "https://github.com/gankro/serde", branch = "deserialize_from_enums3", feature="deserialize_from" }
 
 # If you need to temporarily test Servo with a local fork of some upstream
 # crate, add that here. Use the form:
 #
 #     <crate> = { path = "/path/to/local/checkout" }
 #
 # Or for a git dependency:
 #
--- a/servo/components/compositing/compositor.rs
+++ b/servo/components/compositing/compositor.rs
@@ -1029,17 +1029,16 @@ impl<Window: WindowMethods> IOCompositor
                     if let ScrollLocation::Delta(delta) = last_combined_event.scroll_location {
                         last_combined_event.scroll_location = ScrollLocation::Delta(delta + this_delta);
                         last_combined_event.event_count += 1
                     }
                 }
             }
         }
 
-        // TODO(gw): Support zoom (WR issue #28).
         if let Some(combined_event) = last_combined_event {
             let scroll_location = match combined_event.scroll_location {
                 ScrollLocation::Delta(delta) => {
                     let scaled_delta = (TypedVector2D::from_untyped(&delta.to_untyped()) / self.scale)
                                        .to_untyped();
                     let calculated_delta = webrender_api::LayoutVector2D::from_untyped(&scaled_delta);
                                            ScrollLocation::Delta(calculated_delta)
                 },
--- a/servo/components/layout/block.rs
+++ b/servo/components/layout/block.rs
@@ -789,30 +789,29 @@ impl BlockFlow {
 
             // Absolute positioning establishes a block formatting context. Don't propagate floats
             // in or out. (But do propagate them between kids.)
             if self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) ||
                     margins_may_collapse != MarginsMayCollapseFlag::MarginsMayCollapse {
                 self.base.floats = Floats::new(self.fragment.style.writing_mode);
             }
 
-            let mut margin_collapse_info = MarginCollapseInfo::new();
             let writing_mode = self.base.floats.writing_mode;
             self.base.floats.translate(LogicalSize::new(
                 writing_mode, -self.fragment.inline_start_offset(), Au(0)));
 
             // The sum of our block-start border and block-start padding.
             let block_start_offset = self.fragment.border_padding.block_start;
             translate_including_floats(&mut cur_b, block_start_offset, &mut self.base.floats);
 
             let can_collapse_block_start_margin_with_kids =
                 margins_may_collapse == MarginsMayCollapseFlag::MarginsMayCollapse &&
                 !self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) &&
                 self.fragment.border_padding.block_start == Au(0);
-            margin_collapse_info.initialize_block_start_margin(
+            let mut margin_collapse_info = MarginCollapseInfo::initialize_block_start_margin(
                 &self.fragment,
                 can_collapse_block_start_margin_with_kids);
 
             // At this point, `cur_b` is at the content edge of our box. Now iterate over children.
             let mut floats = self.base.floats.clone();
             let thread_id = self.base.thread_id;
             let (mut had_floated_children, mut had_children_with_clearance) = (false, false);
             for (child_index, kid) in self.base.child_iter_mut().enumerate() {
--- a/servo/components/layout/construct.rs
+++ b/servo/components/layout/construct.rs
@@ -1348,23 +1348,24 @@ impl<'a, ConcreteThreadSafeLayoutNode: T
         if need_to_reconstruct {
             return false
         }
 
         if node.restyle_damage().contains(ServoRestyleDamage::RECONSTRUCT_FLOW) {
             return false
         }
 
-        if node.can_be_fragmented() || node.style(self.style_context()).is_multicol() {
-            return false
-        }
-
         let mut set_has_newly_constructed_flow_flag = false;
         let result = {
             let style = node.style(self.style_context());
+
+            if style.can_be_fragmented() || style.is_multicol() {
+                return false
+            }
+
             let damage = node.restyle_damage();
             let mut data = node.mutate_layout_data().unwrap();
 
             match *node.construction_result_mut(&mut *data) {
                 ConstructionResult::None => true,
                 ConstructionResult::Flow(ref mut flow, _) => {
                     // The node's flow is of the same type and has the same set of children and can
                     // therefore be repaired by simply propagating damage and style to the flow.
@@ -1652,26 +1653,19 @@ impl<ConcreteThreadSafeLayoutNode> NodeU
             PseudoElementType::After(_) => &mut data.after_flow_construction_result,
             PseudoElementType::DetailsSummary(_) => &mut data.details_summary_flow_construction_result,
             PseudoElementType::DetailsContent(_) => &mut data.details_content_flow_construction_result,
             PseudoElementType::Normal    => &mut data.flow_construction_result,
         }
     }
 
     #[inline(always)]
-    fn set_flow_construction_result(self, mut result: ConstructionResult) {
-        if self.can_be_fragmented() {
-            if let ConstructionResult::Flow(ref mut flow, _) = result {
-                FlowRef::deref_mut(flow).mut_base().flags.insert(FlowFlags::CAN_BE_FRAGMENTED);
-            }
-        }
-
+    fn set_flow_construction_result(self, result: ConstructionResult) {
         let mut layout_data = self.mutate_layout_data().unwrap();
         let dst = self.construction_result_mut(&mut *layout_data);
-
         *dst = result;
     }
 
     #[inline(always)]
     fn get_construction_result(self) -> ConstructionResult {
         let mut layout_data = self.mutate_layout_data().unwrap();
         self.construction_result_mut(&mut *layout_data).get()
     }
--- a/servo/components/layout/display_list_builder.rs
+++ b/servo/components/layout/display_list_builder.rs
@@ -1576,17 +1576,17 @@ impl FragmentDisplayListBuilding for Fra
                 // Get current viewport
                 state.layout_context.shared_context().viewport_size(),
             ),
         };
 
         let mut tile_size = compute_background_image_size(bg_size, bounds.size, intrinsic_size);
 
         let mut tile_spacing = Size2D::zero();
-        let own_position = bounds.size - intrinsic_size.unwrap_or(Size2D::zero());
+        let own_position = bounds.size - tile_size;
         let pos_x = bg_position_x.to_used_value(own_position.width);
         let pos_y = bg_position_y.to_used_value(own_position.height);
         tile_image_axis(
             bg_repeat.0,
             &mut bounds.origin.x,
             &mut bounds.size.width,
             &mut tile_size.width,
             &mut tile_spacing.width,
@@ -3561,18 +3561,16 @@ impl InlineFlowDisplayListBuilding for I
                 .relative_containing_block_mode,
             BorderPaintingMode::Separate,
             DisplayListSection::Content,
             &self.base.clip,
         );
     }
 
     fn build_display_list_for_inline(&mut self, state: &mut DisplayListBuildState) {
-        // TODO(#228): Once we form lines and have their cached bounds, we can be smarter and
-        // not recurse on a line if nothing in it can intersect the dirty region.
         debug!(
             "Flow: building display list for {} inline fragments",
             self.fragments.len()
         );
 
         // We iterate using an index here, because we want to avoid doing a doing
         // a double-borrow of self (one mutable for the method call and one immutable
         // for the self.fragments.fragment iterator itself).
--- a/servo/components/layout/flow.rs
+++ b/servo/components/layout/flow.rs
@@ -987,16 +987,20 @@ impl BaseFlow {
     #[inline]
     pub fn new(style: Option<&ComputedValues>,
                writing_mode: WritingMode,
                force_nonfloated: ForceNonfloatedFlag)
                -> BaseFlow {
         let mut flags = FlowFlags::empty();
         match style {
             Some(style) => {
+                if style.can_be_fragmented() {
+                    flags.insert(FlowFlags::CAN_BE_FRAGMENTED);
+                }
+
                 match style.get_box().position {
                     Position::Absolute | Position::Fixed => {
                         flags.insert(FlowFlags::IS_ABSOLUTELY_POSITIONED);
 
                         let logical_position = style.logical_position();
                         if logical_position.inline_start == LengthOrPercentageOrAuto::Auto &&
                                 logical_position.inline_end == LengthOrPercentageOrAuto::Auto {
                             flags.insert(FlowFlags::INLINE_POSITION_IS_STATIC);
--- a/servo/components/layout/model.rs
+++ b/servo/components/layout/model.rs
@@ -106,35 +106,31 @@ enum FinalMarginState {
 
 pub struct MarginCollapseInfo {
     pub state: MarginCollapseState,
     pub block_start_margin: AdjoiningMargins,
     pub margin_in: AdjoiningMargins,
 }
 
 impl MarginCollapseInfo {
-    /// TODO(#2012, pcwalton): Remove this method once `fragment` is not an `Option`.
-    pub fn new() -> MarginCollapseInfo {
+    pub fn initialize_block_start_margin(
+        fragment: &Fragment,
+        can_collapse_block_start_margin_with_kids: bool,
+    ) -> MarginCollapseInfo {
         MarginCollapseInfo {
-            state: MarginCollapseState::AccumulatingCollapsibleTopMargin,
-            block_start_margin: AdjoiningMargins::new(),
+            state: if can_collapse_block_start_margin_with_kids {
+                MarginCollapseState::AccumulatingCollapsibleTopMargin
+            } else {
+                MarginCollapseState::AccumulatingMarginIn
+            },
+            block_start_margin: AdjoiningMargins::from_margin(fragment.margin.block_start),
             margin_in: AdjoiningMargins::new(),
         }
     }
 
-    pub fn initialize_block_start_margin(&mut self,
-                                         fragment: &Fragment,
-                                         can_collapse_block_start_margin_with_kids: bool) {
-        if !can_collapse_block_start_margin_with_kids {
-            self.state = MarginCollapseState::AccumulatingMarginIn
-        }
-
-        self.block_start_margin = AdjoiningMargins::from_margin(fragment.margin.block_start)
-    }
-
     pub fn finish_and_compute_collapsible_margins(mut self,
                                                   fragment: &Fragment,
                                                   containing_block_size: Option<Au>,
                                                   can_collapse_block_end_margin_with_kids: bool,
                                                   mut may_collapse_through: bool)
                                                   -> (CollapsibleMargins, Au) {
         let state = match self.state {
             MarginCollapseState::AccumulatingCollapsibleTopMargin => {
--- a/servo/components/layout/traversal.rs
+++ b/servo/components/layout/traversal.rs
@@ -275,17 +275,18 @@ impl<'a> PostorderFlowTraversal for Assi
 
         flow.assign_block_size(self.layout_context);
     }
 
     #[inline]
     fn should_process(&self, flow: &mut Flow) -> bool {
         let base = flow.base();
         base.restyle_damage.intersects(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW) &&
-        // The fragmentation countainer is responsible for calling Flow::fragment recursively
+        // The fragmentation countainer is responsible for calling
+        // Flow::fragment recursively
         !base.flags.contains(FlowFlags::CAN_BE_FRAGMENTED)
     }
 }
 
 pub struct ComputeStackingRelativePositions<'a> {
     pub layout_context: &'a LayoutContext<'a>,
 }
 
--- a/servo/components/layout_thread/dom_wrapper.rs
+++ b/servo/components/layout_thread/dom_wrapper.rs
@@ -204,27 +204,19 @@ impl<'ln> TNode for ServoLayoutNode<'ln>
     fn as_element(&self) -> Option<ServoLayoutElement<'ln>> {
         as_element(self.node)
     }
 
     fn as_document(&self) -> Option<ServoLayoutDocument<'ln>> {
         self.node.downcast().map(ServoLayoutDocument::from_layout_js)
     }
 
-    fn can_be_fragmented(&self) -> bool {
-        unsafe { self.node.get_flag(NodeFlags::CAN_BE_FRAGMENTED) }
-    }
-
     fn is_in_document(&self) -> bool {
         unsafe { self.node.get_flag(NodeFlags::IS_IN_DOC) }
     }
-
-    unsafe fn set_can_be_fragmented(&self, value: bool) {
-        self.node.set_flag(NodeFlags::CAN_BE_FRAGMENTED, value)
-    }
 }
 
 impl<'ln> LayoutNode for ServoLayoutNode<'ln> {
     type ConcreteThreadSafeLayoutNode = ServoThreadSafeLayoutNode<'ln>;
 
     fn to_threadsafe(&self) -> Self::ConcreteThreadSafeLayoutNode {
         ServoThreadSafeLayoutNode::new(self)
     }
@@ -925,20 +917,16 @@ impl<'ln> ThreadSafeLayoutNode for Servo
             !self.style(context).get_inheritedtext().white_space.preserve_newlines()
         }
     }
 
     unsafe fn unsafe_get(self) -> Self::ConcreteNode {
         self.node
     }
 
-    fn can_be_fragmented(&self) -> bool {
-        self.node.can_be_fragmented()
-    }
-
     fn node_text_content(&self) -> String {
         let this = unsafe { self.get_jsmanaged() };
         return this.text_content();
     }
 
     fn selection(&self) -> Option<Range<ByteIndex>> {
         let this = unsafe { self.get_jsmanaged() };
 
--- a/servo/components/script/dom/node.rs
+++ b/servo/components/script/dom/node.rs
@@ -159,20 +159,17 @@ bitflags! {
         // Perhaps using a Set in Document?
         #[doc = "Specifies whether or not there is an authentic click in progress on \
                  this element."]
         const CLICK_IN_PROGRESS = 1 << 2;
         #[doc = "Specifies whether this node is focusable and whether it is supposed \
                  to be reachable with using sequential focus navigation."]
         const SEQUENTIALLY_FOCUSABLE = 1 << 3;
 
-        /// Whether any ancestor is a fragmentation container
-        const CAN_BE_FRAGMENTED = 1 << 4;
-
-        // There's a free bit here.
+        // There are two free bits here.
 
         #[doc = "Specifies whether the parser has set an associated form owner for \
                  this element. Only applicable for form-associatable elements."]
         const PARSER_ASSOCIATED_FORM_OWNER = 1 << 6;
 
         /// Whether this element has a snapshot stored due to a style or
         /// attribute change.
         ///
--- a/servo/components/script_layout_interface/wrapper_traits.rs
+++ b/servo/components/script_layout_interface/wrapper_traits.rs
@@ -235,18 +235,16 @@ pub trait ThreadSafeLayoutNode: Clone + 
     /// Returns access to the underlying LayoutNode. This is breaks the abstraction
     /// barrier of ThreadSafeLayout wrapper layer, and can lead to races if not used
     /// carefully.
     ///
     /// We need this because the implementation of some methods need to access the layout
     /// data flags, and we have this annoying trait separation between script and layout :-(
     unsafe fn unsafe_get(self) -> Self::ConcreteNode;
 
-    fn can_be_fragmented(&self) -> bool;
-
     fn node_text_content(&self) -> String;
 
     /// If the insertion point is within this node, returns it. Otherwise, returns `None`.
     fn selection(&self) -> Option<Range<ByteIndex>>;
 
     /// If this is an image element, returns its URL. If this is not an image element, fails.
     fn image_url(&self) -> Option<ServoUrl>;
 
--- a/servo/components/style/animation.rs
+++ b/servo/components/style/animation.rs
@@ -480,16 +480,17 @@ fn compute_style_for_animation_step(cont
             };
 
             // This currently ignores visited styles, which seems acceptable,
             // as existing browsers don't appear to animate visited styles.
             let computed =
                 properties::apply_declarations(context.stylist.device(),
                                                /* pseudo = */ None,
                                                previous_style.rules(),
+                                               &context.guards,
                                                iter,
                                                Some(previous_style),
                                                Some(previous_style),
                                                Some(previous_style),
                                                /* visited_style = */ None,
                                                font_metrics_provider,
                                                CascadeFlags::empty(),
                                                context.quirks_mode(),
--- a/servo/components/style/dom.rs
+++ b/servo/components/style/dom.rs
@@ -234,23 +234,16 @@ pub trait TNode : Sized + Copy + Clone +
     /// A debug id, only useful, mm... for debugging.
     fn debug_id(self) -> usize;
 
     /// Get this node as an element, if it's one.
     fn as_element(&self) -> Option<Self::ConcreteElement>;
 
     /// Get this node as a document, if it's one.
     fn as_document(&self) -> Option<Self::ConcreteDocument>;
-
-    /// Whether this node can be fragmented. This is used for multicol, and only
-    /// for Servo.
-    fn can_be_fragmented(&self) -> bool;
-
-    /// Set whether this node can be fragmented.
-    unsafe fn set_can_be_fragmented(&self, value: bool);
 }
 
 /// Wrapper to output the subtree rather than the single node when formatting
 /// for Debug.
 pub struct ShowSubtree<N: TNode>(pub N);
 impl<N: TNode> Debug for ShowSubtree<N> {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         writeln!(f, "DOM Subtree:")?;
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -340,28 +340,16 @@ impl<'ln> TNode for GeckoNode<'ln> {
     #[inline]
     fn as_document(&self) -> Option<Self::ConcreteDocument> {
         if self.is_document() {
             Some(self.owner_doc())
         } else {
             None
         }
     }
-
-    #[inline]
-    fn can_be_fragmented(&self) -> bool {
-        // FIXME(SimonSapin): Servo uses this to implement CSS multicol / fragmentation
-        // Maybe this isn’t useful for Gecko?
-        false
-    }
-
-    unsafe fn set_can_be_fragmented(&self, _value: bool) {
-        // FIXME(SimonSapin): Servo uses this to implement CSS multicol / fragmentation
-        // Maybe this isn’t useful for Gecko?
-    }
 }
 
 /// A wrapper on top of two kind of iterators, depending on the parent being
 /// iterated.
 ///
 /// We generally iterate children by traversing the light-tree siblings of the
 /// first child like Servo does.
 ///
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -408,16 +408,25 @@ trait PrivateMatchMethods: TElement {
             }
 
             if was_legacy_justify_items &&
                 old_justify_items.computed != new_justify_items.computed {
                 return ChildCascadeRequirement::MustCascadeChildren;
             }
         }
 
+        #[cfg(feature = "servo")]
+        {
+            // We may need to set or propagate the CAN_BE_FRAGMENTED bit
+            // on our children.
+            if old_values.is_multicol() != new_values.is_multicol() {
+                return ChildCascadeRequirement::MustCascadeChildren;
+            }
+        }
+
         // We could prove that, if our children don't inherit reset
         // properties, we can stop the cascade.
         ChildCascadeRequirement::MustCascadeChildrenIfInheritResetStyle
     }
 
     #[cfg(feature = "servo")]
     fn update_animations_for_cascade(&self,
                                      context: &SharedStyleContext,
@@ -499,48 +508,29 @@ pub trait MatchMethods : TElement {
     /// damage.
     fn finish_restyle(
         &self,
         context: &mut StyleContext<Self>,
         data: &mut ElementData,
         mut new_styles: ResolvedElementStyles,
         important_rules_changed: bool,
     ) -> ChildCascadeRequirement {
-        use dom::TNode;
         use std::cmp;
 
         self.process_animations(
             context,
             &mut data.styles.primary,
             &mut new_styles.primary.style.0,
             data.hint,
             important_rules_changed,
         );
 
         // First of all, update the styles.
         let old_styles = data.set_styles(new_styles);
 
-        // Propagate the "can be fragmented" bit. It would be nice to
-        // encapsulate this better.
-        if cfg!(feature = "servo") {
-            let layout_parent =
-                self.inheritance_parent().map(|e| e.layout_parent());
-            let layout_parent_data =
-                layout_parent.as_ref().and_then(|e| e.borrow_data());
-            let layout_parent_style =
-                layout_parent_data.as_ref().map(|d| d.styles.primary());
-
-            if let Some(ref p) = layout_parent_style {
-                let can_be_fragmented =
-                    p.is_multicol() ||
-                    layout_parent.as_ref().unwrap().as_node().can_be_fragmented();
-                unsafe { self.as_node().set_can_be_fragmented(can_be_fragmented); }
-            }
-        }
-
         let new_primary_style = data.styles.primary.as_ref().unwrap();
 
         let mut cascade_requirement = ChildCascadeRequirement::CanSkipCascade;
         if self.is_root() && !self.is_native_anonymous() {
             let device = context.shared.stylist.device();
             let new_font_size = new_primary_style.get_font().clone_font_size();
 
             if old_styles.primary.as_ref().map_or(true, |s| s.get_font().clone_font_size() != new_font_size) {
--- a/servo/components/style/properties/computed_value_flags.rs
+++ b/servo/components/style/properties/computed_value_flags.rs
@@ -57,16 +57,21 @@ bitflags! {
         /// Important because of the same reason.
         const INHERITS_CONTENT = 1 << 7;
 
         /// Whether the child explicitly inherits any reset property.
         const INHERITS_RESET_STYLE = 1 << 8;
 
         /// A flag to mark a style which is a visited style.
         const IS_STYLE_IF_VISITED = 1 << 9;
+
+        /// Whether the style or any of the ancestors has a multicol style.
+        ///
+        /// Only used in Servo.
+        const CAN_BE_FRAGMENTED = 1 << 10;
     }
 }
 
 impl ComputedValueFlags {
     /// Returns the flags that are inherited.
     #[inline]
     pub fn inherited(self) -> Self {
         self & !(ComputedValueFlags::INHERITS_DISPLAY |
--- a/servo/components/style/properties/declaration_block.rs
+++ b/servo/components/style/properties/declaration_block.rs
@@ -278,16 +278,22 @@ impl PropertyDeclarationBlock {
         !self.declarations_importance.all_true()
     }
 
     /// Returns whether this block contains a declaration of a given longhand.
     pub fn contains(&self, id: LonghandId) -> bool {
         self.longhands.contains(id)
     }
 
+    /// Returns whether this block contains any reset longhand.
+    #[inline]
+    pub fn contains_any_reset(&self) -> bool {
+        self.longhands.contains_any_reset()
+    }
+
     /// Get a declaration for a given property.
     ///
     /// NOTE: This is linear time.
     pub fn get(&self, property: PropertyDeclarationId) -> Option<(&PropertyDeclaration, Importance)> {
         self.declarations.iter().enumerate().find(|&(_, decl)| decl.id() == property).map(|(i, decl)| {
             let importance = if self.declarations_importance.get(i as u32) {
                 Importance::Important
             } else {
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -295,20 +295,16 @@ impl ComputedValuesInner {
         &self.visited_style
     }
 
     #[allow(non_snake_case)]
     pub fn has_moz_binding(&self) -> bool {
         !self.get_box().gecko.mBinding.mRawPtr.is_null()
     }
 
-    // FIXME(bholley): Implement this properly.
-    #[inline]
-    pub fn is_multicol(&self) -> bool { false }
-
     pub fn to_declaration_block(&self, property: PropertyDeclarationId) -> PropertyDeclarationBlock {
         let value = match property {
             % for prop in data.longhands:
                 % if prop.animatable:
                     PropertyDeclarationId::Longhand(LonghandId::${prop.camel_case}) => {
                         PropertyDeclaration::${prop.camel_case}(
                             % if prop.boxed:
                                 Box::new(
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -304,29 +304,46 @@ impl LonghandIdSet {
         for (self_cell, other_cell) in self.storage.iter().zip(other.storage.iter()) {
             if (*self_cell & *other_cell) != *other_cell {
                 return false;
             }
         }
         true
     }
 
+    /// Returns whether this set contains any longhand that `other` also contains.
+    pub fn contains_any(&self, other: &Self) -> bool {
+        for (self_cell, other_cell) in self.storage.iter().zip(other.storage.iter()) {
+            if (*self_cell & *other_cell) != 0 {
+                return true;
+            }
+        }
+        false
+    }
+
     /// Create an empty set
     #[inline]
     pub fn new() -> LonghandIdSet {
         LonghandIdSet { storage: [0; (${len(data.longhands)} - 1 + 32) / 32] }
     }
 
     /// Return whether the given property is in the set
     #[inline]
     pub fn contains(&self, id: LonghandId) -> bool {
         let bit = id as usize;
         (self.storage[bit / 32] & (1 << (bit % 32))) != 0
     }
 
+    /// Return whether this set contains any reset longhand.
+    #[inline]
+    pub fn contains_any_reset(&self) -> bool {
+        ${static_longhand_id_set("RESET", lambda p: not p.style_struct.inherited)}
+        self.contains_any(&RESET)
+    }
+
     /// Add the given property to the set
     #[inline]
     pub fn insert(&mut self, id: LonghandId) {
         let bit = id as usize;
         self.storage[bit / 32] |= 1 << (bit % 32);
     }
 
     /// Remove the given property from the set
@@ -2036,16 +2053,28 @@ pub mod style_structs {
 
             /// Returns whether there are any transitions specified.
             #[cfg(feature = "servo")]
             pub fn specifies_transitions(&self) -> bool {
                 self.transition_duration_iter()
                     .take(self.transition_property_count())
                     .any(|t| t.seconds() > 0.)
             }
+        % elif style_struct.name == "Column":
+            /// Whether this is a multicol style.
+            #[cfg(feature = "servo")]
+            pub fn is_multicol(&self) -> bool {
+                match self.column_width {
+                    Either::First(_width) => true,
+                    Either::Second(_auto) => match self.column_count {
+                        Either::First(_n) => true,
+                        Either::Second(_auto) => false,
+                    }
+                }
+            }
         % endif
     }
 
     % for longhand in style_struct.longhands:
         % if longhand.need_index:
             /// An iterator over the values of the ${longhand.name} properties.
             pub struct ${longhand.camel_case}Iter<'a> {
                 style_struct: &'a style_structs::${style_struct.name},
@@ -2254,27 +2283,26 @@ impl ComputedValuesInner {
     pub fn ineffective_content_property(&self) -> bool {
         use properties::longhands::content::computed_value::T;
         match self.get_counters().content {
             T::Normal | T::None => true,
             T::Items(ref items) => items.is_empty(),
         }
     }
 
+    /// Whether the current style or any of its ancestors is multicolumn.
+    #[inline]
+    pub fn can_be_fragmented(&self) -> bool {
+        self.flags.contains(ComputedValueFlags::CAN_BE_FRAGMENTED)
+    }
+
     /// Whether the current style is multicolumn.
     #[inline]
     pub fn is_multicol(&self) -> bool {
-        let style = self.get_column();
-        match style.column_width {
-            Either::First(_width) => true,
-            Either::Second(_auto) => match style.column_count {
-                Either::First(_n) => true,
-                Either::Second(_auto) => false,
-            }
-        }
+        self.get_column().is_multicol()
     }
 
     /// Resolves the currentColor keyword.
     ///
     /// Any color value from computed values (except for the 'color' property
     /// itself) should go through this method.
     ///
     /// Usage example:
@@ -2739,26 +2767,26 @@ impl<'a> StyleBuilder<'a> {
         % if property.style_struct.inherited:
             self.inherited_style.get_${property.style_struct.name_lower}();
         % else:
             self.inherited_style_ignoring_first_line
                 .get_${property.style_struct.name_lower}();
         % endif
 
         % if not property.style_struct.inherited:
-        self.flags.insert(::properties::computed_value_flags::ComputedValueFlags::INHERITS_RESET_STYLE);
+        self.flags.insert(ComputedValueFlags::INHERITS_RESET_STYLE);
         self.modified_reset = true;
         % endif
 
         % if property.ident == "content":
-        self.flags.insert(::properties::computed_value_flags::ComputedValueFlags::INHERITS_CONTENT);
+        self.flags.insert(ComputedValueFlags::INHERITS_CONTENT);
         % endif
 
         % if property.ident == "display":
-        self.flags.insert(::properties::computed_value_flags::ComputedValueFlags::INHERITS_DISPLAY);
+        self.flags.insert(ComputedValueFlags::INHERITS_DISPLAY);
         % endif
 
         self.${property.style_struct.ident}.mutate()
             .copy_${property.ident}_from(
                 inherited_struct,
                 % if property.logical:
                 self.writing_mode,
                 % endif
@@ -3152,16 +3180,17 @@ pub fn cascade(
                 })
         })
     };
 
     apply_declarations(
         device,
         pseudo,
         rule_node,
+        guards,
         iter_declarations,
         parent_style,
         parent_style_ignoring_first_line,
         layout_parent_style,
         visited_style,
         font_metrics_provider,
         flags,
         quirks_mode,
@@ -3171,16 +3200,17 @@ pub fn cascade(
 }
 
 /// NOTE: This function expects the declaration with more priority to appear
 /// first.
 pub fn apply_declarations<'a, F, I>(
     device: &Device,
     pseudo: Option<<&PseudoElement>,
     rules: &StrongRuleNode,
+    guards: &StylesheetGuards,
     iter_declarations: F,
     parent_style: Option<<&ComputedValues>,
     parent_style_ignoring_first_line: Option<<&ComputedValues>,
     layout_parent_style: Option<<&ComputedValues>,
     visited_style: Option<Arc<ComputedValues>>,
     font_metrics_provider: &FontMetricsProvider,
     flags: CascadeFlags,
     quirks_mode: QuirksMode,
@@ -3473,17 +3503,17 @@ where
                 let discriminant = LonghandId::FontSize as usize;
                 let size = PropertyDeclaration::CSSWideKeyword(
                     LonghandId::FontSize, CSSWideKeyword::Inherit);
 
                 (CASCADE_PROPERTY[discriminant])(&size, &mut context);
             % endif
             }
 
-            if let Some(style) = rule_cache.and_then(|c| c.find(&context.builder)) {
+            if let Some(style) = rule_cache.and_then(|c| c.find(guards, &context.builder)) {
                 context.builder.copy_reset_from(style);
                 apply_reset = false;
             }
         % endif // category == "early"
     % endfor
 
     let mut builder = context.builder;
 
--- a/servo/components/style/rule_cache.rs
+++ b/servo/components/style/rule_cache.rs
@@ -3,19 +3,20 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! A cache from rule node to computed values, in order to cache reset
 //! properties.
 
 use fnv::FnvHashMap;
 use logical_geometry::WritingMode;
 use properties::{ComputedValues, StyleBuilder};
-use rule_tree::StrongRuleNode;
+use rule_tree::{StrongRuleNode, StyleSource};
 use selector_parser::PseudoElement;
 use servo_arc::Arc;
+use shared_lock::StylesheetGuards;
 use smallvec::SmallVec;
 use values::computed::NonNegativeLength;
 
 /// The conditions for caching and matching a style in the rule cache.
 #[derive(Clone, Debug, Default)]
 pub struct RuleCacheConditions {
     uncacheable: bool,
     font_size: Option<NonNegativeLength>,
@@ -76,54 +77,88 @@ pub struct RuleCache {
 impl RuleCache {
     /// Creates an empty `RuleCache`.
     pub fn new() -> Self {
         Self {
             map: FnvHashMap::default(),
         }
     }
 
+    /// Walk the rule tree and return a rule node for using as the key
+    /// for rule cache.
+    ///
+    /// It currently skips a rule node when it is neither from a style
+    /// rule, nor containing any declaration of reset property. We don't
+    /// skip style rule so that we don't need to walk a long way in the
+    /// worst case. Skipping declarations rule nodes should be enough
+    /// to address common cases that rule cache would fail to share
+    /// when using the rule node directly, like preshint, style attrs,
+    /// and animations.
+    fn get_rule_node_for_cache<'r>(
+        guards: &StylesheetGuards,
+        mut rule_node: Option<&'r StrongRuleNode>
+    ) -> Option<&'r StrongRuleNode> {
+        while let Some(node) = rule_node {
+            match *node.style_source() {
+                StyleSource::Declarations(ref decls) => {
+                    let cascade_level = node.cascade_level();
+                    let decls = decls.read_with(cascade_level.guard(guards));
+                    if decls.contains_any_reset() {
+                        break;
+                    }
+                }
+                StyleSource::None => {}
+                StyleSource::Style(_) => break,
+            }
+            rule_node = node.parent();
+        }
+        rule_node
+    }
+
     /// Finds a node in the properties matched cache.
     ///
     /// This needs to receive a `StyleBuilder` with the `early` properties
     /// already applied.
     pub fn find(
         &self,
+        guards: &StylesheetGuards,
         builder_with_early_props: &StyleBuilder,
     ) -> Option<&ComputedValues> {
         if builder_with_early_props.is_style_if_visited() {
             // FIXME(emilio): We can probably do better, does it matter much?
             return None;
         }
 
         // A pseudo-element with property restrictions can result in different
         // computed values if it's also used for a non-pseudo.
         if builder_with_early_props.pseudo
            .and_then(|p| p.property_restriction())
            .is_some() {
             return None;
         }
 
-        let rules = builder_with_early_props.rules.as_ref()?;
+        let rules = builder_with_early_props.rules.as_ref();
+        let rules = Self::get_rule_node_for_cache(guards, rules)?;
         let cached_values = self.map.get(rules)?;
 
         for &(ref conditions, ref values) in cached_values.iter() {
             if conditions.matches(builder_with_early_props) {
                 debug!("Using cached reset style with conditions {:?}", conditions);
                 return Some(&**values)
             }
         }
         None
     }
 
     /// Inserts a node into the rules cache if possible.
     ///
     /// Returns whether the style was inserted into the cache.
     pub fn insert_if_possible(
         &mut self,
+        guards: &StylesheetGuards,
         style: &Arc<ComputedValues>,
         pseudo: Option<&PseudoElement>,
         conditions: &RuleCacheConditions,
     ) -> bool {
         if !conditions.cacheable() {
             return false;
         }
 
@@ -133,18 +168,19 @@ impl RuleCache {
         }
 
         // A pseudo-element with property restrictions can result in different
         // computed values if it's also used for a non-pseudo.
         if pseudo.and_then(|p| p.property_restriction()).is_some() {
             return false;
         }
 
-        let rules = match style.rules {
-            Some(ref r) => r.clone(),
+        let rules = style.rules.as_ref();
+        let rules = match Self::get_rule_node_for_cache(guards, rules) {
+            Some(r) => r.clone(),
             None => return false,
         };
 
         debug!("Inserting cached reset style with conditions {:?}", conditions);
         self.map
             .entry(rules)
             .or_insert_with(SmallVec::new)
             .push((conditions.clone(), style.clone()));
--- a/servo/components/style/rule_tree/mod.rs
+++ b/servo/components/style/rule_tree/mod.rs
@@ -859,17 +859,18 @@ impl StrongRuleNode {
             p: NonZeroPtrMut::new(ptr)
         }
     }
 
     fn downgrade(&self) -> WeakRuleNode {
         WeakRuleNode::from_ptr(self.ptr())
     }
 
-    fn parent(&self) -> Option<&StrongRuleNode> {
+    /// Get the parent rule node of this rule node.
+    pub fn parent(&self) -> Option<&StrongRuleNode> {
         self.get().parent.as_ref()
     }
 
     fn ensure_child(
         &self,
         root: WeakRuleNode,
         source: StyleSource,
         level: CascadeLevel
--- a/servo/components/style/style_adjuster.rs
+++ b/servo/components/style/style_adjuster.rs
@@ -99,16 +99,25 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> {
             self.style.get_box().clone_display() == Display::None {
             self.style.flags.insert(ComputedValueFlags::IS_IN_DISPLAY_NONE_SUBTREE);
         }
 
         if self.style.inherited_flags().contains(ComputedValueFlags::IS_IN_PSEUDO_ELEMENT_SUBTREE) ||
             self.style.is_pseudo_element() {
             self.style.flags.insert(ComputedValueFlags::IS_IN_PSEUDO_ELEMENT_SUBTREE);
         }
+
+        #[cfg(feature = "servo")]
+        {
+            if self.style.inherited_flags().contains(ComputedValueFlags::CAN_BE_FRAGMENTED) ||
+                self.style.get_parent_column().is_multicol()
+            {
+                self.style.flags.insert(ComputedValueFlags::CAN_BE_FRAGMENTED);
+            }
+        }
     }
 
     /// Adjust the style for text style.
     ///
     /// The adjustments here are a subset of the adjustments generally, because
     /// text only inherits properties.
     ///
     /// Note that this, for Gecko, comes through Servo_ComputedValues_Inherit.
--- a/servo/components/style/style_resolver.rs
+++ b/servo/components/style/style_resolver.rs
@@ -592,13 +592,18 @@ where
                 self.context.shared.quirks_mode(),
                 Some(&self.context.thread_local.rule_cache),
                 &mut conditions,
             );
 
         self.context
             .thread_local
             .rule_cache
-            .insert_if_possible(&values, pseudo, &conditions);
+            .insert_if_possible(
+                &self.context.shared.guards,
+                &values,
+                pseudo,
+                &conditions
+            );
 
         values
     }
 }
--- a/servo/etc/ci/performance/runner.py
+++ b/servo/etc/ci/performance/runner.py
@@ -276,20 +276,22 @@ def save_result_csv(results, filename, m
         'requestStart',
         'responseEnd',
         'responseStart',
         'secureConnectionStart',
         'unloadEventEnd',
         'unloadEventStart',
     ]
 
+    successes = [r for r in results if r['domComplete'] != -1]
+
     with open(filename, 'w', encoding='utf-8') as csvfile:
         writer = csv.DictWriter(csvfile, fieldnames)
         writer.writeheader()
-        writer.writerows(results)
+        writer.writerows(successes)
 
 
 def format_result_summary(results):
     failures = list(filter(lambda x: x['domComplete'] == -1, results))
     result_log = """
 ========================================
 Total {total} tests; {suc} succeeded, {fail} failed.
 
--- a/taskcluster/ci/docker-image/kind.yml
+++ b/taskcluster/ci/docker-image/kind.yml
@@ -30,14 +30,16 @@ jobs:
   lint:
     symbol: I(lnt)
   android-build:
     symbol: I(agb)
   index-task:
     symbol: I(idx)
   funsize-update-generator:
     symbol: I(pg)
+  google-play-strings:
+    symbol: I(gps)
   funsize-balrog-submitter:
     symbol: I(fbs)
   beet-mover:
     symbol: I(bm)
   update-verify:
     symbol: I(uv)
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/google-play-strings/kind.yml
@@ -0,0 +1,54 @@
+# 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/.
+
+loader: taskgraph.loader.transform:loader
+
+transforms:
+   - taskgraph.transforms.google_play_strings:transforms
+   - taskgraph.transforms.task:transforms
+
+jobs:
+   google-play-strings:
+      description: Download strings to display on Google Play from https://l10n.mozilla-community.org/stores_l10n/
+      attributes:
+         build_type: google_play_strings
+         build_platform: android-nightly
+         nightly: true
+      shipping-phase: promote
+      shipping-product: fennec
+      worker-type: aws-provisioner-v1/taskcluster-generic
+      worker:
+         implementation: docker-worker
+         os: linux
+         docker-image: {in-tree: google-play-strings}
+         chain-of-trust: true
+         max-run-time: 600
+         artifacts:
+            - name: 'public/google_play_strings.json'
+              # XXX The folder depends on the one defined in the Dockerfile
+              path: /builds/worker/google_play_strings.json
+              type: 'file'
+         env:
+            # TODO Use the branch name instead of the android package name
+            PACKAGE_NAME:
+               by-project:
+                  mozilla-central: org.mozilla.fennec_aurora
+                  mozilla-beta: org.mozilla.firefox_beta
+                  mozilla-release: org.mozilla.firefox_beta
+                  default: org.mozilla.fennec_aurora  # Fetches strings for mozilla-central
+            # XXX The folder depends on the one defined in the Dockerfile
+            GOOGLE_PLAY_STRING_FILE: /builds/worker/google_play_strings.json
+         command:
+            - bash
+            - -cx
+            - >
+              python3 ./mozapkpublisher/get_l10n_strings.py
+              --package-name "${PACKAGE_NAME}"
+              --output-file "${GOOGLE_PLAY_STRING_FILE}"
+      treeherder:
+         symbol: pub(gps)
+         platform: Android/opt
+         tier: 2
+         kind: other
+      run-on-projects: ['maple', 'mozilla-central', 'mozilla-beta', 'mozilla-release']
--- a/taskcluster/ci/push-apk/kind.yml
+++ b/taskcluster/ci/push-apk/kind.yml
@@ -1,43 +1,43 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 loader: taskgraph.loader.push_apk:loader
 
 transforms:
-    - taskgraph.transforms.push_apk:transforms
-    - taskgraph.transforms.task:transforms
+   - taskgraph.transforms.push_apk:transforms
+   - taskgraph.transforms.task:transforms
 
 kind-dependencies:
-    - build-signing
-    - push-apk-breakpoint
+   - build-signing
+   - google-play-strings
+   - push-apk-breakpoint
 
 jobs:
-    push-apk/opt:
-        description: Publishes APK onto Google Play Store
-        attributes:
-            build_platform: android-nightly
-            nightly: true
-        shipping-phase: ship
-        shipping-product: fennec
-        worker-type:
-            by-project:
-                mozilla-central: scriptworker-prov-v1/pushapk-v1
-                mozilla-beta: scriptworker-prov-v1/pushapk-v1
-                mozilla-release: scriptworker-prov-v1/pushapk-v1
-                default: scriptworker-prov-v1/dep-pushapk
-        worker:
-            upstream-artifacts:  # see transforms
-            google-play-track:  # see transforms
-            implementation: push-apk
-            commit:  # see transforms
-        scopes:  # see transforms
-        treeherder:
-            symbol: pub(gp)
-            platform: Android/opt
-            tier: 2
-            kind: other
-        run-on-projects: ['mozilla-central', 'mozilla-beta', 'mozilla-release']
-        deadline-after: 5 days
-        extra:
-            product: fennec
+   push-apk/opt:
+      description: Publishes APK onto Google Play Store
+      attributes:
+         build_platform: android-nightly
+         nightly: true
+      shipping-phase: ship
+      shipping-product: fennec
+      worker-type:
+         by-project:
+            mozilla-central: scriptworker-prov-v1/pushapk-v1
+            mozilla-beta: scriptworker-prov-v1/pushapk-v1
+            mozilla-release: scriptworker-prov-v1/pushapk-v1
+            default: scriptworker-prov-v1/dep-pushapk
+      worker:
+         upstream-artifacts:  # see transforms
+         google-play-track:  # see transforms
+         implementation: push-apk
+         commit:  # see transforms
+      requires: all-resolved
+      scopes:  # see transforms
+      treeherder:
+         symbol: pub(gp)
+         platform: Android/opt
+         tier: 2
+         kind: other
+      run-on-projects: ['mozilla-central', 'mozilla-beta', 'mozilla-release', 'maple']
+      deadline-after: 5 days
new file mode 100644
--- /dev/null
+++ b/taskcluster/docker/google-play-strings/Dockerfile
@@ -0,0 +1,19 @@
+FROM          ubuntu:16.04
+MAINTAINER    Johan Lorenzo <jlorenzo+tc@mozilla.com>
+
+RUN mkdir /builds
+RUN groupadd -g 500 worker
+RUN useradd -u 500 -g 500 -d /builds/worker -s /bin/bash -m worker
+
+RUN apt-get update
+RUN apt-get install --yes git python3-setuptools build-essential libssl-dev libffi-dev python3-dev
+
+WORKDIR /builds/worker/
+RUN git clone https://github.com/mozilla-releng/mozapkpublisher
+WORKDIR /builds/worker/mozapkpublisher
+RUN python3 setup.py develop
+
+RUN chown -R worker:worker /builds/worker
+
+# Set a default command useful for debugging
+CMD ["/bin/bash", "--login"]
--- a/taskcluster/docs/kinds.rst
+++ b/taskcluster/docs/kinds.rst
@@ -212,16 +212,21 @@ and sign it via the signing scriptworker
 additional detached signature.
 
 beetmover-checksums
 -------------------
 Beetmover, takes specific artifact checksums and pushes it to a location outside
 of Taskcluster's task artifacts (archive.mozilla.org as one place) and in the
 process determines the final location and "pretty" names it (version product name)
 
+google-play-strings
+-------------------
+Download strings to display on Google Play from https://l10n.mozilla-community.org/stores_l10n/.
+Artifact is then used by push-apk.
+
 push-apk-breakpoint
 -------------------
 Decides whether or not APKs should be published onto Google Play Store. Jobs of this
 kind depend on all the signed multi-locales (aka "multi") APKs for a given release,
 in order to make the decision.
 
 push-apk
 --------
new file mode 100644
--- /dev/null
+++ b/taskcluster/taskgraph/transforms/google_play_strings.py
@@ -0,0 +1,64 @@
+# 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/.
+"""
+Transform the push-apk kind into an actual task description.
+"""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+import functools
+
+from taskgraph.transforms.base import TransformSequence
+from taskgraph.transforms.task import task_description_schema
+from taskgraph.util.schema import resolve_keyed_by, Schema
+from taskgraph.util.push_apk import fill_labels_tranform, validate_jobs_schema_transform_partial
+
+from voluptuous import Required
+
+
+transforms = TransformSequence()
+
+# Voluptuous uses marker objects as dictionary *keys*, but they are not
+# comparable, so we cast all of the keys back to regular strings
+task_description_schema = {str(k): v for k, v in task_description_schema.schema.iteritems()}
+
+google_play_description_schema = Schema({
+    Required('name'): basestring,
+    Required('label'): task_description_schema['label'],
+    Required('description'): task_description_schema['description'],
+    Required('job-from'): task_description_schema['job-from'],
+    Required('attributes'): task_description_schema['attributes'],
+    Required('treeherder'): task_description_schema['treeherder'],
+    Required('run-on-projects'): task_description_schema['run-on-projects'],
+    Required('shipping-phase'): task_description_schema['shipping-phase'],
+    Required('shipping-product'): task_description_schema['shipping-product'],
+    Required('worker-type'): task_description_schema['worker-type'],
+    Required('worker'): object,
+})
+
+validate_jobs_schema_transform = functools.partial(
+    validate_jobs_schema_transform_partial,
+    google_play_description_schema,
+    'GooglePlayStrings'
+)
+
+transforms.add(fill_labels_tranform)
+transforms.add(validate_jobs_schema_transform)
+
+
+@transforms.add
+def set_worker_data(config, jobs):
+    for job in jobs:
+        worker = job['worker']
+
+        env = worker.setdefault('env', {})
+        resolve_keyed_by(
+            env, 'PACKAGE_NAME', item_name=job['name'],
+            project=config.params['project']
+        )
+
+        cot = job.setdefault('extra', {}).setdefault('chainOfTrust', {})
+        cot.setdefault('inputs', {})['docker-image'] = {'task-reference': '<docker-image>'}
+
+        yield job
--- a/taskcluster/taskgraph/transforms/push_apk.py
+++ b/taskcluster/taskgraph/transforms/push_apk.py
@@ -23,32 +23,32 @@ from voluptuous import Optional, Require
 transforms = TransformSequence()
 
 # Voluptuous uses marker objects as dictionary *keys*, but they are not
 # comparable, so we cast all of the keys back to regular strings
 task_description_schema = {str(k): v for k, v in task_description_schema.schema.iteritems()}
 
 
 push_apk_description_schema = Schema({
-    # the dependent task (object) for this beetmover job, used to inform beetmover.
     Required('dependent-tasks'): object,
     Required('name'): basestring,
-    Required('label'): basestring,
-    Required('description'): basestring,
-    Required('job-from'): basestring,
-    Required('attributes'): object,
-    Required('treeherder'): object,
-    Required('run-on-projects'): list,
+    Required('label'): task_description_schema['label'],
+    Required('description'): task_description_schema['description'],
+    Required('job-from'): task_description_schema['job-from'],
+    Required('attributes'): task_description_schema['attributes'],
+    Required('treeherder'): task_description_schema['treeherder'],
+    Required('run-on-projects'): task_description_schema['run-on-projects'],
     Required('worker-type'): optionally_keyed_by('project', basestring),
     Required('worker'): object,
     Required('scopes'): None,
+    Required('requires'): task_description_schema['requires'],
     Required('deadline-after'): basestring,
     Required('shipping-phase'): task_description_schema['shipping-phase'],
     Required('shipping-product'): task_description_schema['shipping-product'],
-    Optional('extra'): object,
+    Optional('extra'): task_description_schema['extra'],
 })
 
 validate_jobs_schema_transform = functools.partial(
     validate_jobs_schema_transform_partial,
     push_apk_description_schema,
     'PushApk'
 )
 
@@ -78,13 +78,26 @@ def make_task_description(config, jobs):
 
         yield job
 
 
 transforms.add(delete_non_required_fields_transform)
 
 
 def generate_upstream_artifacts(dependencies):
-    return [{
+    apks = [{
         'taskId': {'task-reference': '<{}>'.format(task_kind)},
         'taskType': 'signing',
         'paths': ['public/build/target.apk'],
-    } for task_kind in dependencies.keys() if 'breakpoint' not in task_kind]
+    } for task_kind in dependencies.keys()
+      if task_kind not in ('push-apk-breakpoint', 'google-play-strings')
+    ]
+
+    google_play_strings = [{
+        'taskId': {'task-reference': '<{}>'.format(task_kind)},
+        'taskType': 'build',
+        'paths': ['public/google_play_strings.json'],
+        'optional': True,
+    } for task_kind in dependencies.keys()
+      if 'google-play-strings' in task_kind
+    ]
+
+    return apks + google_play_strings
--- a/taskcluster/taskgraph/transforms/task.py
+++ b/taskcluster/taskgraph/transforms/task.py
@@ -88,16 +88,18 @@ task_description_schema = Schema({
     # relative path (from config.path) to the file task was defined in
     Optional('job-from'): basestring,
 
     # dependencies of this task, keyed by name; these are passed through
     # verbatim and subject to the interpretation of the Task's get_dependencies
     # method.
     Optional('dependencies'): {basestring: object},
 
+    Optional('requires'): Any('all-completed', 'all-resolved'),
+
     # expiration and deadline times, relative to task creation, with units
     # (e.g., "14 days").  Defaults are set based on the project.
     Optional('expires-after'): basestring,
     Optional('deadline-after'): basestring,
 
     # custom routes for this task; the default treeherder routes will be added
     # automatically
     Optional('routes'): [basestring],
@@ -562,16 +564,19 @@ task_description_schema = Schema({
             # taskId of the task with the artifact
             Required('taskId'): taskref_or_string,
 
             # type of signing task (for CoT)
             Required('taskType'): basestring,
 
             # Paths to the artifacts to sign
             Required('paths'): [basestring],
+
+            # Artifact is optional to run the task
+            Optional('optional', default=False): bool,
         }],
 
         # "Invalid" is a noop for try and other non-supported branches
         Required('google-play-track'): Any('production', 'beta', 'alpha', 'rollout', 'invalid'),
         Required('commit', default=False): bool,
         Optional('rollout-percentage'): int,
     }),
 })
@@ -1417,16 +1422,19 @@ def build_task(config, tasks):
                     config.params['head_rev'],
                     config.path),
             },
             'extra': extra,
             'tags': tags,
             'priority': task['priority'],
         }
 
+        if task.get('requires', None):
+            task_def['requires'] = task['requires']
+
         if task_th:
             # link back to treeherder in description
             th_push_link = 'https://treeherder.mozilla.org/#/jobs?repo={}&revision={}'.format(
                 config.params['project'], treeherder_rev)
             task_def['metadata']['description'] += ' ([Treeherder push]({}))'.format(
                 th_push_link)
 
         # add the payload and adjust anything else as required (e.g., scopes)
--- a/testing/mozbase/mozversion/setup.py
+++ b/testing/mozbase/mozversion/setup.py
@@ -1,17 +1,17 @@
 # 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/.
 
 from __future__ import absolute_import
 
 from setuptools import setup
 
-PACKAGE_VERSION = '1.4'
+PACKAGE_VERSION = '1.5'
 
 
 setup(name='mozversion',
       version=PACKAGE_VERSION,
       description='Library to get version information for applications',
       long_description="see https://firefox-source-docs.mozilla.org/mozbase/index.html",
       classifiers=['Programming Language :: Python :: 2.7',
                    'Programming Language :: Python :: 3'],
--- a/toolkit/components/telemetry/TelemetryUtils.jsm
+++ b/toolkit/components/telemetry/TelemetryUtils.jsm
@@ -27,16 +27,17 @@ const IS_CONTENT_PROCESS = (function() {
 this.TelemetryUtils = {
   Preferences: Object.freeze({
     // General Preferences
     ArchiveEnabled: "toolkit.telemetry.archive.enabled",
     CachedClientId: "toolkit.telemetry.cachedClientID",
     FirstRun: "toolkit.telemetry.reportingpolicy.firstRun",
     FirstShutdownPingEnabled: "toolkit.telemetry.firstShutdownPing.enabled",
     HealthPingEnabled: "toolkit.telemetry.healthping.enabled",
+    HybridContentEnabled: "toolkit.telemetry.hybridContent.enabled",
     OverrideOfficialCheck: "toolkit.telemetry.send.overrideOfficialCheck",
     OverridePreRelease: "toolkit.telemetry.testing.overridePreRelease",
     Server: "toolkit.telemetry.server",
     ShutdownPingSender: "toolkit.telemetry.shutdownPingSender.enabled",
     ShutdownPingSenderFirstSession: "toolkit.telemetry.shutdownPingSender.enabledFirstSession",
     TelemetryEnabled: "toolkit.telemetry.enabled",
     Unified: "toolkit.telemetry.unified",
     UpdatePing: "toolkit.telemetry.updatePing.enabled",
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/docs/collection/hybrid-content.rst
@@ -0,0 +1,274 @@
+========================
+Hybrid Content Telemetry
+========================
+
+Hybrid content is web content that is loaded as part of Firefox, appears as part of
+Firefox to the user and is primarily intended to be used in Firefox. This can be
+either a page that ships with Firefox or that can be loaded dynamically from our hosted
+services. Hybrid content telemetry allows Mozilla pages to check whether data
+collection is enabled and to submit Telemetry data.
+
+.. important::
+
+    Every new data collection in Firefox (including hybrid content) needs a `data collection review <https://wiki.mozilla.org/Firefox/Data_Collection#Requesting_Approval>`_ from a data collection peer. Just set the feedback? flag for one of the data peers. They try to reply within a business day.
+
+The recorded data will be sent to Mozilla servers by Firefox, if the collection is enabled, with the :doc:`main-ping <../data/main-ping>`.
+
+Adding content data collection
+==============================
+Telemetry can be sent from web content by:
+
+1. granting the web content's host privileges in the Firefox codebase;
+2. including the ``HybridContentTelemetry-lib.js`` file in the page;
+3. registering the probes after the library is loaded;
+4. using the API to send Telemetry.
+
+Granting the privileges
+-----------------------
+For security/privacy reasons `Mozilla.ContentTelemetry` will only work on a list of allowed secure origins. The list of allowed origins can be found in `browser/app/permissions <https://dxr.mozilla.org/mozilla-central/source/browser/app/permissions>`_ . A host needs to be given the ``hc_telemetry`` permission in order to be whitelisted.
+
+Example:
+
+.. code-block:: csv
+
+  origin  hc_telemetry  1 https://discovery.addons.mozilla.org
+
+Adding an entry to the ``permissions`` file requires riding the trains. If "go-faster" content requires
+granting permissions to a Mozilla page, it can do so by using the `permission manager <https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIPermissionManager>`_
+
+.. code-block:: js
+
+  function addonInit() {
+    // The following code must be called before attempting to load a page that uses
+    // hybrid content telemetry on https://example.mozilla.org.
+    let hostURI = Services.io.newURI("https://example.mozilla.org");
+    Services.perms.add(hostURI, "hc_telemetry", Services.perms.ALLOW_ACTION);
+  }
+
+  function addonCleanup() {
+    // The permission must be removed if no longer needed (e.g. the add-on is shut down).
+    let hostURI = Services.io.newURI("https://example.mozilla.org");
+    Services.perms.remove(hostURI, "hc_telemetry");
+  }
+
+.. important::
+
+    Granted permissions do not disappear when a "go-faster" add-on is uninstalled but are cleared when the browser is closed. If permissions need to be cleaned without closing the browser, it must be done manually. Moreover, permissions are keyed by origin: ``http://mozilla.com`` and ``https://mozilla.com`` are different things.
+
+Including the library
+---------------------
+To use hybrid content telemetry the relative content JS library needs to be included in the page. We don't have a CDN hosted version that can be readily included in the page. For this reason, each consumer will need to fetch the latest version of the library from `here <https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/telemetry/hybrid-content/HybridContentTelemetry-lib.js>`_ and add it to the page repository. Then this file can be deployed along with the page.
+
+Example:
+
+.. code-block:: html
+
+  <!DOCTYPE html>
+  <html>
+    <head>
+      <!-- Other head stuff -->
+      <script type="application/javascript" src="HybridContentTelemetry-lib.js"></script>
+    </head>
+    <body> <!-- Other body stuff --> </body>
+  </html>
+
+Registering the probes
+----------------------
+Probe registration can happen at any time after the library is loaded in the page, but registering early enough ensures that the definition is available once a recording attempt is made.
+
+Example:
+
+.. code-block:: html
+
+  <!DOCTYPE html>
+  <html>
+    <head>
+      <!-- Other head stuff -->
+      <script type="application/javascript">
+        window.onload = function() {
+          if (!Mozilla || !Mozilla.ContentTelemetry) {
+            // .. uh-oh, was library loaded? Report the error.
+            return;
+          }
+          // Register the probe.
+          Mozilla.ContentTelemetry.registerEvents("page.interaction", {
+            "click": {
+              methods: ["click"],
+              objects: ["red_button", "blue_button"],
+            }
+          });
+        };
+      </script>
+    </head>
+    <body> <!-- Other body stuff --> </body>
+  </html>
+
+Recording the data
+------------------
+Data recording can happen at any time after a probe has been registered. The data will be recorded and sent by Firefox if permitted by the Telemetry :doc:`preferences <../internals/preferences>`.
+
+Example:
+
+.. code-block:: html
+
+  <!DOCTYPE html>
+  <html>
+    <head>
+      <!-- Other head stuff -->
+      <script type="application/javascript">
+        function triggerEvent() {
+          if (!Mozilla || !Mozilla.ContentTelemetry) {
+            // .. uh-oh, was library loaded? Report the error.
+            return;
+          }
+          Mozilla.ContentTelemetry.recordEvent("page.interaction", "click", "red_button");
+        };
+      </script>
+    </head>
+    <body>
+      <!-- Other body stuff -->
+      <div id="content">
+        <button id='event-recording' onclick="triggerEvent();">
+          Trigger Recording
+        </button>
+      </div>
+    </body>
+  </html>
+
+Checking if upload is enabled
+-----------------------------
+Mozilla pages can check if data upload is enabled, as reported by Telemetry :doc:`preferences <../internals/preferences>`. This is useful for pages which are not using Telemetry to collect data, but
+need to comply to our data policy for the collection.
+
+Example:
+
+.. code-block:: html
+
+  <!DOCTYPE html>
+  <html>
+    <head>
+      <!-- Other head stuff -->
+      <script type="application/javascript">
+        function recordData() {
+          if (!Mozilla || !Mozilla.ContentTelemetry) {
+            // .. uh-oh, was library loaded? Report the error.
+            return;
+          }
+
+          if (!Mozilla.ContentTelemetry.canUpload()) {
+            // User has opted-out of Telemetry. No collection must take place.
+            return;
+          }
+
+          // ... perform the collection without Telemetry below this point.
+        };
+      </script>
+    </head>
+    <body>
+      <!-- Other body stuff -->
+      <div id="content">
+        <button id='event-recording' onclick="recordData();">
+          Trigger Recording
+        </button>
+      </div>
+    </body>
+  </html>
+
+The API
+=======
+The hybrid content API is available to the web content through the inclusion of the `HybridContentTelemetry-lib.js <https://dxr.mozilla.org/mozilla-central/source/toolkit/components/telemetry/hybrid-content/HybridContentTelemetry-lib.js>`_ library.
+
+The initial implementation of the API allows the registration and the recording of events.
+
+JS API
+------
+Authorized content can use the following functions:
+
+.. code-block:: js
+
+  Mozilla.ContentTelemetry.canUpload();
+  Mozilla.ContentTelemetry.registerEvents(category, eventData);
+  Mozilla.ContentTelemetry.recordEvent(category, method, object, value, extra);
+
+These functions will not throw. If an unsupported operation is performed (e.g. recording an unknown event) an error will be logged to the browser console.
+
+.. note::
+
+    Data collected using this API will always respect the user Telemetry preferences: if a user has chosen to not send Telemetry data to Mozilla servers, Telemetry from hybrid content pages will not be sent either.
+    Like other Telemetry data, it will still be recorded locally and available through ``about:telemetry``.
+
+``Mozilla.ContentTelemetry.canUpload()``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: js
+
+  Mozilla.ContentTelemetry.canUpload();
+
+This function returns true if the browser is allowed to send collected data to Mozilla servers (i.e. ``datareporting.healthreport.uploadEnabled`` is ``true``), false otherwise. See :doc:`preferences <../internals/preferences>`.
+
+.. note::
+
+    The page should use this function to check if it is allowed to collect data. This is only needed in case the Telemetry system is not be being used for collection. If Telemetry is used, then this is taken care of internally by the Telemetry API. The page should not cache the returned value: users can opt in or out from the Data Collection at any time and so the returned value may change.
+
+Example:
+
+.. code-block:: js
+
+  if (Mozilla.ContentTelemetry.canUpload()) {
+    // ... perform the data collection here using another measurement system.
+  }
+
+``Mozilla.ContentTelemetry.registerEvents()``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: js
+
+  Mozilla.ContentTelemetry.registerEvents(category, eventData);
+
+Register new dynamic events from the content. This accepts the same parameters and is subject to the same limitation as ``Services.telemetry.registerEvents()``. See the `events` documentation for the definitive reference.
+
+.. note::
+
+    Make sure to call this before recording events, as soon as the library is loaded (e.g. `window load event <https://developer.mozilla.org/en-US/docs/Web/Events/load>`_). This will make sure that the definition will be ready when recording.
+
+The data recorded into events registered with this function will end up in the ``dynamic`` process section of the main ping.
+
+Example:
+
+.. code-block:: js
+
+  Mozilla.ContentTelemetry.registerEvents("page.interaction", {
+    "click": {
+      methods: ["click"],
+      objects: ["red_button", "blue_button"],
+    }
+  });
+  // Now events can be recorded.
+  Mozilla.ContentTelemetry.recordEvent("page.interaction", "click", "red_button");
+
+``Mozilla.ContentTelemetry.recordEvent()``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: js
+
+  Mozilla.ContentTelemetry.recordEvent(category, method, object, value, extra);
+
+Record a registered event. This accepts the same parameters and is subject to the same limitation as ``Services.telemetry.recordEvent()``. See the `events` documentation for the definitive reference.
+
+Example:
+
+.. code-block:: js
+
+  Mozilla.ContentTelemetry.recordEvent("ui", "click", "reload-btn");
+  // event: [543345, "ui", "click", "reload-btn"]
+  Mozilla.ContentTelemetry.recordEvent("ui", "search", "search-bar", "google");
+  // event: [89438, "ui", "search", "search-bar", "google"]
+  Mozilla.ContentTelemetry.recordEvent("ui", "completion", "search-bar", "yahoo",
+                                       {"querylen": "7", "results": "23"});
+  // event: [982134, "ui", "completion", "search-bar", "yahoo",
+  //           {"qerylen": "7", "results": "23"}]
+
+Version History
+===============
+
+- Firefox 59: Initial hybrid content telemetry support (`bug 1417473 <https://bugzilla.mozilla.org/show_bug.cgi?id=1417473>`_).
--- a/toolkit/components/telemetry/docs/collection/index.rst
+++ b/toolkit/components/telemetry/docs/collection/index.rst
@@ -18,28 +18,30 @@ The current data collection possibilitie
 * :doc:`events` can record richer data on individual occurrences of specific actions
 * ``TelemetryLog`` allows collecting ordered event entries up to a limit of 1000 entries (note: this does not have supporting analysis tools)
 * :doc:`measuring elapsed time <measuring-time>`
 * :doc:`custom pings <custom-pings>`
 * :doc:`stack capture <stack-capture>` allow recording application call stacks
 * :doc:`Use counters <use-counters>` measure the usage of web platform features
 * :doc:`Experiment annotations <experiments>`
 * :doc:`Remote content uptake <uptake>`
+* :doc:`hybrid content telemety <hybrid-content>` allows recording telemetry from semi-privileged hosted content
 
 .. toctree::
    :maxdepth: 2
    :titlesonly:
    :hidden:
    :glob:
 
    scalars
    histograms
    events
    measuring-time
    custom-pings
    stack-capture
    experiments
    uptake
+   hybrid-content
    *
 
 Browser Usage Telemetry
 ~~~~~~~~~~~~~~~~~~~~~~~
 For more information, see :ref:`browserusagetelemetry`.
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/hybrid-content/HybridContentTelemetry-lib.js
@@ -0,0 +1,85 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+if (typeof Mozilla == "undefined") {
+  var Mozilla = {};
+}
+
+(function($) {
+  "use strict";
+
+  var _canUpload = false;
+
+  if (typeof Mozilla.ContentTelemetry == "undefined") {
+    /**
+     * Library that exposes an event-based Web API for communicating with the
+     * desktop browser chrome. It can be used for recording Telemetry data from
+     * authorized web content pages.
+     *
+     * <p>For security/privacy reasons `Mozilla.ContentTelemetry` will only work
+     * on a list of allowed secure origins. The list of allowed origins can be
+     * found in
+     * {@link https://dxr.mozilla.org/mozilla-central/source/browser/app/permissions|
+     * browser/app/permissions}.</p>
+     *
+     * @since 59
+     * @namespace
+     */
+    Mozilla.ContentTelemetry = {};
+  }
+
+  function _sendMessageToChrome(name, data) {
+    var event = new CustomEvent("mozTelemetry", {
+      bubbles: true,
+      detail: {
+        name,
+        data: data || {}
+      }
+    });
+
+    document.dispatchEvent(event);
+  }
+
+  /**
+   * This internal function is used to register the policy handler. This is
+   * needed by pages that do not want to use Telemetry but still need to
+   * respect user Privacy choices.
+   */
+  function _registerInternalPolicyHandler() {
+    // Register the handler that will update the policy boolean.
+    function policyChangeHandler(updatedPref) {
+      if (!("detail" in updatedPref) ||
+          !("canUpload" in updatedPref.detail) ||
+          typeof updatedPref.detail.canUpload != "boolean") {
+        return;
+      }
+      _canUpload = updatedPref.detail.canUpload;
+    }
+    document.addEventListener("mozTelemetryPolicyChange", policyChangeHandler);
+
+    // Make sure the chrome is initialized.
+    _sendMessageToChrome("init");
+  }
+
+  Mozilla.ContentTelemetry.canUpload = function() {
+    return _canUpload;
+  };
+
+  Mozilla.ContentTelemetry.registerEvents = function(category, eventData) {
+    _sendMessageToChrome("registerEvents", { category, eventData });
+  };
+
+  Mozilla.ContentTelemetry.recordEvent = function(category, method, object, value, extra) {
+    _sendMessageToChrome("recordEvent", { category, method, object, value, extra });
+  };
+
+  // Register the policy handler so that |canUpload| is always up to date.
+  _registerInternalPolicyHandler();
+})();
+
+// Make this library Require-able.
+/* eslint-env commonjs */
+if (typeof module !== "undefined" && module.exports) {
+  module.exports = Mozilla.ContentTelemetry;
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/hybrid-content/HybridContentTelemetry.jsm
@@ -0,0 +1,124 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["HybridContentTelemetry"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/TelemetryUtils.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+let HybridContentTelemetry = {
+  _logger: null,
+  _observerInstalled: false,
+
+  get _log() {
+    if (!this._logger) {
+      this._logger =
+        Log.repository.getLoggerWithMessagePrefix("Toolkit.Telemetry",
+                                                  "HybridContentTelemetry::");
+    }
+
+    return this._logger;
+  },
+
+  /**
+   * Lazily initialized observer for the Telemetry upload preference. This is
+   * only ever executed if a page uses hybrid content telemetry and has enough
+   * privileges to run it.
+   */
+  _lazyObserverInit() {
+    if (this._observerInstalled) {
+      // We only want to install the observers once, if needed.
+      return;
+    }
+    this._log.trace("_lazyObserverInit - installing the pref observers.");
+    XPCOMUtils.defineLazyPreferenceGetter(this, "_uploadEnabled",
+                                          TelemetryUtils.Preferences.FhrUploadEnabled,
+                                          false, /* aDefaultValue */
+                                          () => this._broadcastPolicyUpdate());
+    this._observerInstalled = true;
+  },
+
+  /**
+   * This is the handler for the async "HybridContentTelemetry:onTelemetryMessage"
+   * message. This function is getting called by the listener in nsBrowserGlue.js.
+   */
+  onTelemetryMessage(aMessage, aData) {
+    if (!this._hybridContentEnabled) {
+      this._log.trace("onTelemetryMessage - hybrid content telemetry is disabled.");
+      return;
+    }
+
+    this._log.trace("onTelemetryMessage - Message received, dispatching API call.");
+    if (!aData ||
+        !("data" in aData) ||
+        !("name" in aData) ||
+        typeof aData.name != "string" ||
+        typeof aData.data != "object") {
+      this._log.error("onTelemetryMessage - received a malformed message.");
+      return;
+    }
+    this._dispatchAPICall(aData.name, aData.data, aMessage);
+  },
+
+  /**
+   * Broadcast the upload policy state to the pages using hybrid
+   * content telemetry.
+   */
+  _broadcastPolicyUpdate() {
+    this._log.trace(`_broadcastPolicyUpdate - New value is ${this._uploadEnabled}.`);
+    Services.mm.broadcastAsyncMessage("HybridContentTelemetry:PolicyChanged",
+                                      {canUpload: this._uploadEnabled});
+  },
+
+  /**
+   * Dispatches the calls to the Telemetry service.
+   * @param {String} aEndpoint The name of the api endpoint to call.
+   * @param {Object} aData An object containing the data to pass to the API.
+   * @param {Object} aOriginalMessage The message object coming from the listener.
+   */
+  _dispatchAPICall(aEndpoint, aData, aOriginalMessage) {
+    this._log.info(`_dispatchAPICall - processing "${aEndpoint}".`);
+
+    // We don't really care too much about validating the passed parameters.
+    // Telemetry will take care of that for us so there is little gain from
+    // duplicating that logic here. Just make sure we don't throw and report
+    // any error.
+    try {
+      switch (aEndpoint) {
+        case "init":
+            this._lazyObserverInit();
+            this._broadcastPolicyUpdate();
+            break;
+        case "registerEvents":
+            Services.telemetry.registerEvents(aData.category, aData.eventData);
+          break;
+        case "recordEvent":
+            // Don't pass "undefined" for the optional |value| and |extra|:
+            // the Telemetry API expects them to be "null" if something is being
+            // passed.
+            let check = (data, key) => (key in data && typeof data[key] != "undefined");
+            Services.telemetry.recordEvent(aData.category,
+                                           aData.method,
+                                           aData.object,
+                                           check(aData, "value") ? aData.value : null,
+                                           check(aData, "extra") ? aData.extra : null);
+          break;
+        default:
+          this._log.error(`_dispatchAPICall - unknown "${aEndpoint}"" API call.`);
+      }
+    } catch (e) {
+      this._log.error(`_dispatchAPICall - error executing "${aEndpoint}".`, e);
+    }
+  },
+};
+
+XPCOMUtils.defineLazyPreferenceGetter(HybridContentTelemetry, "_hybridContentEnabled",
+                                      TelemetryUtils.Preferences.HybridContentEnabled,
+                                      false /* aDefaultValue */);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/hybrid-content/content-HybridContentTelemetry.js
@@ -0,0 +1,171 @@
+/* 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/. */
+
+/* eslint-env mozilla/frame-script */
+
+"use strict";
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/TelemetryUtils.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const TelemetryPrefs = TelemetryUtils.Preferences;
+
+// The permission to be granted to pages that want to use HCT.
+const HCT_PERMISSION = "hc_telemetry";
+// The message that chrome uses to communicate back to content, when the
+// upload policy changes.
+const HCT_POLICY_CHANGE_MSG = "HybridContentTelemetry:PolicyChanged";
+
+var HybridContentTelemetryListener = {
+  _logger: null,
+  _hasListener: false,
+
+  get _log() {
+    if (!this._logger) {
+      this._logger =
+        Log.repository.getLoggerWithMessagePrefix("Toolkit.Telemetry",
+                                                  "HybridContentTelemetryListener::");
+    }
+
+    return this._logger;
+  },
+
+  /**
+   * Verifies that the hybrid content telemetry request comes
+   * from a trusted origin.
+   * @oaram {nsIDomEvent} event Optional object containing the data for the event coming from
+   *                      the content. The "detail" section of this event holds the
+   *                      hybrid content specific payload. It looks like:
+   *                      {name: "apiEndpoint", data: { ... endpoint specific data ... }}
+   * @return {Boolean} true if we are on a trusted page, false otherwise.
+   */
+  isTrustedOrigin(aEvent) {
+    // Make sure that events only come from the toplevel frame. We need to check the
+    // event's target as |content| always represents the current top level window in
+    // the frame (or null). This means that a check like |content.top != content| will
+    // always be false. See nsIMessageManager.idl for more info.
+    if (aEvent &&
+        (!("ownerGlobal" in aEvent.target) ||
+          aEvent.target.ownerGlobal != content)) {
+      return false;
+    }
+
+    const principal =
+      aEvent ? aEvent.target.ownerGlobal.document.nodePrincipal : content.document.nodePrincipal;
+    if (principal.isSystemPrincipal) {
+      return true;
+    }
+
+    const allowedSchemes = ["https", "about"];
+    if (!allowedSchemes.includes(principal.URI.scheme)) {
+      return false;
+    }
+
+    // If this is not a system principal, it needs to have the
+    // HCT_PERMISSION to use this API.
+    let permission =
+      Services.perms.testPermissionFromPrincipal(principal, HCT_PERMISSION);
+    if (permission == Services.perms.ALLOW_ACTION) {
+      return true;
+    }
+
+    return false;
+  },
+
+  /**
+   * Handles incoming events from the content library.
+   * This verifies that:
+   *
+   * - the hybrid content telemetry is on;
+   * - the page is trusted and allowed to use it;
+   * - the CustomEvent coming from the "content" has the expected format and
+   *   is for a known API end-point.
+   *
+   * If the page is allowed to use the API and the event validates, the call
+   * is passed on to Telemetry.
+   *
+   * @param {nsIDomEvent} event An object containing the data for the event coming from
+   *                      the content. The "detail" section of this event holds the
+   *                      hybrid content specific payload. It looks like:
+   *                      {name: "apiEndpoint", data: { ... endpoint specific data ... }}
+   */
+  handleEvent(event) {
+    if (!this._hybridContentEnabled) {
+      this._log.trace("handleEvent - hybrid content telemetry is disabled.");
+      return;
+    }
+
+    if (!this.isTrustedOrigin(event)) {
+      this._log.warn("handleEvent - accessing telemetry from an untrusted origin.");
+      return;
+    }
+
+    if (!event ||
+        !("detail" in event) ||
+        !("name" in event.detail) ||
+        !("data" in event.detail) ||
+        typeof event.detail.name != "string" ||
+        typeof event.detail.data != "object") {
+      this._log.error("handleEvent - received a malformed message.");
+      return;
+    }
+
+    // We add the message listener here to guarantee it only gets added
+    // for trusted origins.
+    // Note that the name of the message must match the name of the one
+    // in HybridContentTelemetry.jsm.
+    if (!this._hasListener) {
+      addMessageListener(HCT_POLICY_CHANGE_MSG, this);
+      this._hasListener = true;
+    }
+
+    // Note that the name of the async message must match the name of
+    // the message in the related listener in nsBrowserGlue.js.
+    sendAsyncMessage("HybridContentTelemetry:onTelemetryMessage", {
+      name: event.detail.name,
+      data: event.detail.data,
+    });
+  },
+
+  /**
+   * Handles the HCT_POLICY_CHANGE_MSG message coming from chrome and
+   * passes it back to the content.
+   * @param {Object} aMessage The incoming message. See nsIMessageListener docs.
+   */
+  receiveMessage(aMessage) {
+    if (!this.isTrustedOrigin()) {
+      this._log.warn("receiveMessage - accessing telemetry from an untrusted origin.");
+      return;
+    }
+
+    if (aMessage.name != HCT_POLICY_CHANGE_MSG ||
+        !("data" in aMessage) ||
+        !("canUpload" in aMessage.data) ||
+        typeof aMessage.data.canUpload != "boolean") {
+      this._log.warn("receiveMessage - received an unexpected message.");
+      return;
+    }
+
+    // Finally send the message down to the page.
+    content.document.dispatchEvent(
+      new content.document.defaultView.CustomEvent("mozTelemetryPolicyChange", {
+        bubbles: true,
+        detail: {canUpload: aMessage.data.canUpload}
+      })
+    );
+  },
+};
+
+XPCOMUtils.defineLazyPreferenceGetter(HybridContentTelemetryListener, "_hybridContentEnabled",
+                                      TelemetryUtils.Preferences.HybridContentEnabled,
+                                      false /* aDefaultValue */);
+
+// The following function installs the handler for "mozTelemetry"
+// events in the chrome. Please note that the name of the message (i.e.
+// "mozTelemetry") needs to match the one in HybridContentTelemetry-lib.js.
+addEventListener("mozTelemetry", HybridContentTelemetryListener, false, true);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/hybrid-content/jar.mn
@@ -0,0 +1,6 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+toolkit.jar:
+       content/global/content-HybridContentTelemetry.js
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/hybrid-content/moz.build
@@ -0,0 +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/.
+
+EXTRA_JS_MODULES += [
+    'HybridContentTelemetry.jsm',
+]
+
+JAR_MANIFESTS += ['jar.mn']
--- a/toolkit/components/telemetry/moz.build
+++ b/toolkit/components/telemetry/moz.build
@@ -6,16 +6,17 @@
 
 HAS_MISC_RULE = True
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 DIRS = [
+    'hybrid-content',
     'pingsender',
 ]
 
 DEFINES['MOZ_APP_VERSION'] = '"%s"' % CONFIG['MOZ_APP_VERSION']
 
 LOCAL_INCLUDES += [
     '/xpcom/build',
     '/xpcom/threads',
--- a/toolkit/components/telemetry/tests/browser/browser.ini
+++ b/toolkit/components/telemetry/tests/browser/browser.ini
@@ -1,8 +1,12 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 [browser_TelemetryGC.js]
 [browser_UpdatePingSuccess.js]
 [browser_DynamicScalars.js]
 skip-if = !e10s # e10s specific test for definition broadcasting across processes.
+[browser_HybridContentTelemetry.js]
+support-files =
+  ../../hybrid-content/HybridContentTelemetry-lib.js
+  hybrid_content.html
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/tests/browser/browser_HybridContentTelemetry.js
@@ -0,0 +1,370 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/
+*/
+
+"use strict";
+
+const { ContentTaskUtils } = Cu.import("resource://testing-common/ContentTaskUtils.jsm", {});
+const { TelemetryUtils } = Cu.import("resource://gre/modules/TelemetryUtils.jsm", {});
+const { ObjectUtils } = Cu.import("resource://gre/modules/ObjectUtils.jsm", {});
+
+const HC_PERMISSION = "hc_telemetry";
+
+async function waitForProcessesEvents(aProcesses,
+                                      aAdditionalCondition = data => true) {
+  await ContentTaskUtils.waitForCondition(() => {
+    const events =
+      Services.telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+    return aProcesses.every(p => Object.keys(events).includes(p))
+           && aAdditionalCondition(events);
+  });
+}
+
+/**
+ * Wait for a specific event to appear in the given process data.
+ * @param {String} aProcess the name of the process we expect the event to appear.
+ * @param {Array} aEventData the event data to look for.
+ * @return {Promise} Resolved when the event is found or rejected if the search
+ *         times out.
+ */
+async function waitForEvent(aProcess, aEventData) {
+  await waitForProcessesEvents([aProcess], events => {
+    let processEvents = events[aProcess].map(e => e.slice(1));
+    if (processEvents.length == 0) {
+      return false;
+    }
+
+    return processEvents.find(e => ObjectUtils.deepEqual(e, aEventData));
+  });
+}
+
+/**
+ * Remove the trailing null/undefined from an event definition.
+ * This is useful for comparing the sample events (that might
+ * contain null/undefined) to the data from the snapshot (which might
+ * filter them).
+ */
+function removeTrailingInvalidEntry(aEvent) {
+  while (aEvent[aEvent.length - 1] === undefined ||
+         aEvent[aEvent.length - 1] === null) {
+    aEvent.pop();
+  }
+  return aEvent;
+}
+
+add_task(async function test_setup() {
+  // Make sure the newly spawned content processes will have extended Telemetry and
+  // hybrid content telemetry enabled.
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      [TelemetryUtils.Preferences.OverridePreRelease, true],
+      [TelemetryUtils.Preferences.HybridContentEnabled, true],
+      [TelemetryUtils.Preferences.LogLevel, "Trace"]
+    ]
+  });
+  // And take care of the already initialized one as well.
+  let canRecordExtended = Services.telemetry.canRecordExtended;
+  Services.telemetry.canRecordExtended = true;
+  registerCleanupFunction(() => Services.telemetry.canRecordExtended = canRecordExtended);
+});
+
+add_task(async function test_untrusted_http_origin() {
+  Services.telemetry.clearEvents();
+
+  // Install a custom handler that intercepts hybrid content telemetry messages
+  // and makes the test fail. We don't expect any message from non secure contexts.
+  const messageName = "HybridContentTelemetry:onTelemetryMessage";
+  let makeTestFail = () => ok(false, `Received an unexpected ${messageName}.`);
+  Services.mm.addMessageListener(messageName, makeTestFail);
+
+  // Try to use the API on a non-secure host.
+  const testHost = "http://example.org";
+  let testHttpUri = Services.io.newURI(testHost);
+  Services.perms.add(testHttpUri, HC_PERMISSION, Services.perms.ALLOW_ACTION);
+  let url = getRootDirectory(gTestPath) + "hybrid_content.html";
+  url = url.replace("chrome://mochitests/content", testHost);
+  let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+
+  // Try to use the API. Also record a content event from outside HCT: we'll
+  // use this event to know when we can stop waiting for the hybrid content data.
+  const TEST_CONTENT_EVENT = ["telemetry.test", "main_and_content", "object1"];
+  Services.telemetry.setEventRecordingEnabled("telemetry.test", true);
+  await ContentTask.spawn(newTab.linkedBrowser, [TEST_CONTENT_EVENT],
+                          ([testContentEvent]) => {
+    // Call the hybrid content telemetry API.
+    let contentWin = Components.utils.waiveXrays(content);
+    contentWin.testRegisterEvents(testContentEvent[0], JSON.stringify({}));
+    // Record from the usual telemetry API a "canary" event.
+    Services.telemetry.recordEvent(...testContentEvent);
+  });
+
+  // Let's support both e10s/non-e10s testing.
+  const processName = Services.appinfo.browserTabsRemoteAutostart ? "content" : "parent";
+  await waitForEvent(processName, TEST_CONTENT_EVENT);
+
+  // This is needed otherwise the test will fail due to missing test passes.
+  ok(true, "The untrusted HTTP page was not able to use the API.");
+
+  // Finally clean up the listener.
+  await BrowserTestUtils.removeTab(newTab);
+  Services.perms.remove(testHttpUri, HC_PERMISSION);
+  Services.mm.removeMessageListener(messageName, makeTestFail);
+  Services.telemetry.setEventRecordingEnabled("telemetry.test", false);
+});
+
+add_task(async function test_secure_non_whitelisted_origin() {
+  Services.telemetry.clearEvents();
+
+  // Install a custom handler that intercepts hybrid content telemetry messages
+  // and makes the test fail. We don't expect any message from non whitelisted pages.
+  const messageName = "HybridContentTelemetry:onTelemetryMessage";
+  let makeTestFail = () => ok(false, `Received an unexpected ${messageName}.`);
+  Services.mm.addMessageListener(messageName, makeTestFail);
+
+  // Try to use the API on a secure host but don't give the page enough privileges.
+  const testHost = "https://example.org";
+  let url = getRootDirectory(gTestPath) + "hybrid_content.html";
+  url = url.replace("chrome://mochitests/content", testHost);
+  let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+
+  // Try to use the API. Also record a content event from outside HCT: we'll
+  // use this event to know when we can stop waiting for the hybrid content data.
+  const TEST_CONTENT_EVENT = ["telemetry.test", "main_and_content", "object1"];
+  Services.telemetry.setEventRecordingEnabled("telemetry.test", true);
+  await ContentTask.spawn(newTab.linkedBrowser, [TEST_CONTENT_EVENT],
+                          ([testContentEvent]) => {
+    // Call the hybrid content telemetry API.
+    let contentWin = Components.utils.waiveXrays(content);
+    contentWin.testRegisterEvents(testContentEvent[0], JSON.stringify({}));
+    // Record from the usual telemetry API a "canary" event.
+    Services.telemetry.recordEvent(...testContentEvent);
+  });
+
+  // Let's support both e10s/non-e10s testing.
+  const processName = Services.appinfo.browserTabsRemoteAutostart ? "content" : "parent";
+  await waitForEvent(processName, TEST_CONTENT_EVENT);
+
+  // This is needed otherwise the test will fail due to missing test passes.
+  ok(true, "The HTTPS page without permission was not able to use the API.");
+
+  // Finally clean up the listener.
+  await BrowserTestUtils.removeTab(newTab);
+  Services.mm.removeMessageListener(messageName, makeTestFail);
+  Services.telemetry.setEventRecordingEnabled("telemetry.test", false);
+});
+
+add_task(async function test_trusted_disabled_hybrid_telemetry() {
+  Services.telemetry.clearEvents();
+
+  // This test requires hybrid content telemetry to be disabled.
+  await SpecialPowers.pushPrefEnv({
+    set: [[TelemetryUtils.Preferences.HybridContentEnabled, false]]
+  });
+
+  // Install a custom handler that intercepts hybrid content telemetry messages
+  // and makes the test fail. We don't expect any message when the API is disabled.
+  const messageName = "HybridContentTelemetry:onTelemetryMessage";
+  let makeTestFail = () => ok(false, `Received an unexpected ${messageName}.`);
+  Services.mm.addMessageListener(messageName, makeTestFail);
+
+  // Try to use the API on a secure host.
+  const testHost = "https://example.org";
+  let testHttpsUri = Services.io.newURI(testHost);
+  Services.perms.add(testHttpsUri, HC_PERMISSION, Services.perms.ALLOW_ACTION);
+  let url = getRootDirectory(gTestPath) + "hybrid_content.html";
+  url = url.replace("chrome://mochitests/content", testHost);
+  let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+
+  // Try to use the API. Also record a content event from outside HCT: we'll
+  // use this event to know when we can stop waiting for the hybrid content data.
+  const TEST_CONTENT_EVENT = ["telemetry.test", "main_and_content", "object1"];
+  Services.telemetry.setEventRecordingEnabled("telemetry.test", true);
+  await ContentTask.spawn(newTab.linkedBrowser, [TEST_CONTENT_EVENT],
+                          ([testContentEvent]) => {
+    // Call the hybrid content telemetry API.
+    let contentWin = Components.utils.waiveXrays(content);
+    contentWin.testRegisterEvents(testContentEvent[0], JSON.stringify({}));
+    // Record from the usual telemetry API a "canary" event.
+    Services.telemetry.recordEvent(...testContentEvent);
+  });
+
+  // Let's support both e10s/non-e10s testing.
+  const processName = Services.appinfo.browserTabsRemoteAutostart ? "content" : "parent";
+  await waitForEvent(processName, TEST_CONTENT_EVENT);
+
+  // This is needed otherwise the test will fail due to missing test passes.
+  ok(true, "There were no unintended hybrid content API usages.");
+
+  // Finally clean up the listener.
+  await SpecialPowers.popPrefEnv();
+  await BrowserTestUtils.removeTab(newTab);
+  Services.perms.remove(testHttpsUri, HC_PERMISSION);
+  Services.mm.removeMessageListener(messageName, makeTestFail);
+  Services.telemetry.setEventRecordingEnabled("telemetry.test", false);
+});
+
+add_task(async function test_hybrid_content_with_iframe() {
+  Services.telemetry.clearEvents();
+
+  // Open a trusted page that can use in the HCT in a new tab.
+  const testOuterPageHost = "https://example.com";
+  let testHttpsUri = Services.io.newURI(testOuterPageHost);
+  Services.perms.add(testHttpsUri, HC_PERMISSION, Services.perms.ALLOW_ACTION);
+  let url = getRootDirectory(gTestPath) + "hybrid_content.html";
+  let outerUrl = url.replace("chrome://mochitests/content", testOuterPageHost);
+  let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, outerUrl);
+
+  // Install a custom handler that intercepts hybrid content telemetry messages
+  // and makes the test fail. This needs to be done after the tab is opened.
+  const messageName = "HybridContentTelemetry:onTelemetryMessage";
+  let makeTestFail = () => ok(false, `Received an unexpected ${messageName}.`);
+  Services.mm.addMessageListener(messageName, makeTestFail);
+
+  // Enable recording the canary event.
+  const TEST_CONTENT_EVENT = ["telemetry.test", "main_and_content", "object1"];
+  Services.telemetry.setEventRecordingEnabled("telemetry.test", true);
+
+  // Add an iframe to the test page. The URI in the iframe should not be able
+  // to use HCT to the the missing privileges.
+  const testHost = "https://example.org";
+  let iframeUrl = url.replace("chrome://mochitests/content", testHost);
+  await ContentTask.spawn(newTab.linkedBrowser,
+                          [iframeUrl, TEST_CONTENT_EVENT],
+                          async function([iframeUrl, testContentEvent]) {
+    let doc = content.document;
+    let iframe = doc.createElement("iframe");
+    let promiseIframeLoaded = ContentTaskUtils.waitForEvent(iframe, "load", false);
+    iframe.src = iframeUrl;
+    doc.body.insertBefore(iframe, doc.body.firstChild);
+    await promiseIframeLoaded;
+
+    // Call the hybrid content telemetry API.
+    let contentWin = Components.utils.waiveXrays(iframe.contentWindow);
+    contentWin.testRegisterEvents(testContentEvent[0], JSON.stringify({}));
+
+    // Record from the usual telemetry API a "canary" event.
+    Services.telemetry.recordEvent(...testContentEvent);
+  });
+
+  // Let's support both e10s/non-e10s testing.
+  const processName = Services.appinfo.browserTabsRemoteAutostart ? "content" : "parent";
+  await waitForEvent(processName, TEST_CONTENT_EVENT);
+
+  // This is needed otherwise the test will fail due to missing test passes.
+  ok(true, "There were no unintended hybrid content API usages from the iframe.");
+
+  // Cleanup permissions and remove the tab.
+  await BrowserTestUtils.removeTab(newTab);
+  Services.mm.removeMessageListener(messageName, makeTestFail);
+  Services.perms.remove(testHttpsUri, HC_PERMISSION);
+  Services.telemetry.setEventRecordingEnabled("telemetry.test", false);
+});
+
+add_task(async function test_hybrid_content_recording() {
+  const testHost = "https://example.org";
+  const TEST_EVENT_CATEGORY = "telemetry.test.hct";
+  const RECORDED_TEST_EVENTS = [
+    [TEST_EVENT_CATEGORY, "test1", "object1"],
+    [TEST_EVENT_CATEGORY, "test2", "object1", null, {"key1": "foo", "key2": "bar"}],
+    [TEST_EVENT_CATEGORY, "test2", "object1", "some value"],
+    [TEST_EVENT_CATEGORY, "test1", "object1", null, null],
+    [TEST_EVENT_CATEGORY, "test1", "object1", "", null],
+  ];
+  const NON_RECORDED_TEST_EVENTS = [
+    [TEST_EVENT_CATEGORY, "unknown", "unknown"],
+  ];
+
+  Services.telemetry.clearEvents();
+
+  // Give the test host enough privileges to use the API and open the test page.
+  let testHttpsUri = Services.io.newURI(testHost);
+  Services.perms.add(testHttpsUri, HC_PERMISSION, Services.perms.ALLOW_ACTION);
+  let url = getRootDirectory(gTestPath) + "hybrid_content.html";
+  url = url.replace("chrome://mochitests/content", testHost);
+  let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+
+  // Register some events and record them in Telemetry.
+  await ContentTask.spawn(newTab.linkedBrowser,
+                          [TEST_EVENT_CATEGORY, RECORDED_TEST_EVENTS, NON_RECORDED_TEST_EVENTS],
+                          ([eventCategory, recordedTestEvents, nonRecordedTestEvents]) => {
+    let contentWin = Components.utils.waiveXrays(content);
+
+    // If we tried to call contentWin.Mozilla.ContentTelemetry.* functions
+    // and pass non-string parameters, |waiveXrays| would complain and not
+    // let us access them. To work around this, we generate test functions
+    // in the test HTML file and unwrap the passed JSON blob there.
+    contentWin.testRegisterEvents(eventCategory, JSON.stringify({
+      // Event with only required fields.
+      "test1": {
+        methods: ["test1"],
+        objects: ["object1"],
+      },
+      // Event with extra_keys.
+      "test2": {
+        methods: ["test2", "test2b"],
+        objects: ["object1"],
+        extra_keys: ["key1", "key2"],
+      },
+    }));
+
+    // Record some valid events.
+    recordedTestEvents.forEach(e => contentWin.testRecordEvents(JSON.stringify(e)));
+
+    // Test recording an unknown event. The Telemetry API itself is supposed to throw,
+    // but we catch that in hybrid content telemetry and log an error message.
+    nonRecordedTestEvents.forEach(e => contentWin.testRecordEvents(JSON.stringify(e)));
+  });
+
+  // Wait for the data to be in the snapshot, then get the Telemetry data.
+  await waitForProcessesEvents(["dynamic"]);
+  let snapshot =
+      Services.telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+
+  // Check that the dynamically register events made it to the snapshot.
+  ok("dynamic" in snapshot,
+     "The snapshot must contain the 'dynamic' process section");
+  let dynamicEvents = snapshot.dynamic.map(e => e.slice(1));
+  is(dynamicEvents.length, RECORDED_TEST_EVENTS.length, "Should match expected event count.");
+  for (let i = 0; i < RECORDED_TEST_EVENTS.length; ++i) {
+    SimpleTest.isDeeply(dynamicEvents[i],
+                        removeTrailingInvalidEntry(RECORDED_TEST_EVENTS[i]),
+                        "Should have recorded the expected event.");
+  }
+
+  // Cleanup permissions and remove the tab.
+  await BrowserTestUtils.removeTab(newTab);
+  Services.perms.remove(testHttpsUri, HC_PERMISSION);
+});
+
+add_task(async function test_can_upload() {
+  const testHost = "https://example.org";
+
+  await SpecialPowers.pushPrefEnv({set: [[TelemetryUtils.Preferences.FhrUploadEnabled, false]]});
+
+  // Give the test host enough privileges to use the API and open the test page.
+  let testHttpsUri = Services.io.newURI(testHost);
+  Services.perms.add(testHttpsUri, HC_PERMISSION, Services.perms.ALLOW_ACTION);
+  let url = getRootDirectory(gTestPath) + "hybrid_content.html";
+  url = url.replace("chrome://mochitests/content", testHost);
+  let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+
+  // Check that CanUpload reports the correct value.
+  await ContentTask.spawn(newTab.linkedBrowser, {}, () => {
+    let contentWin = Components.utils.waiveXrays(content);
+    // We don't need to pass any parameter, we can safely call Mozilla.ContentTelemetry.
+    let canUpload = contentWin.Mozilla.ContentTelemetry.canUpload();
+    ok(!canUpload, "CanUpload must report 'false' if the preference has that value.");
+  });
+
+  // Flip the pref and check again.
+  await SpecialPowers.pushPrefEnv({set: [[TelemetryUtils.Preferences.FhrUploadEnabled, true]]});
+  await ContentTask.spawn(newTab.linkedBrowser, {}, () => {
+    let contentWin = Components.utils.waiveXrays(content);
+    let canUpload = contentWin.Mozilla.ContentTelemetry.canUpload();
+    ok(canUpload, "CanUpload must report 'true' if the preference has that value.");
+  });
+
+  // Cleanup permissions and remove the tab.
+  await BrowserTestUtils.removeTab(newTab);
+  Services.perms.remove(testHttpsUri, HC_PERMISSION);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/tests/browser/hybrid_content.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8" />
+    <title>Hybrid content telemetry test</title>
+    <script type="application/javascript" src="HybridContentTelemetry-lib.js">
+    </script>
+    <script type="application/javascript">
+      /**
+       * The following functions are simply wrapping API calls
+       * to make sure we don't have waiveXRays complaining about
+       * "same-origin" problems in tests.
+       */
+      function testRegisterEvents(category, dataAsString) {
+        // eslint-disable-next-line no-undef
+        Mozilla.ContentTelemetry.registerEvents(category, JSON.parse(dataAsString));
+      }
+
+      function testRecordEvents(paramsAsString) {
+        // eslint-disable-next-line no-undef
+        Mozilla.ContentTelemetry.recordEvent(...JSON.parse(paramsAsString));
+      }
+    </script>
+  </head>
+  <body>
+    <h1>Hybrid Content Telemetry tests</h1>
+    <p>Because Firefox is...</p>
+    <p>Never gonna let you down</p>
+    <p>Never gonna give you up</p>
+  </body>
+</html>
--- a/toolkit/content/aboutTelemetry.js
+++ b/toolkit/content/aboutTelemetry.js
@@ -2498,17 +2498,17 @@ function displayRichPingData(ping, updat
   let payloadOption = payloadSelect.selectedOptions.item(0);
   let payloadIndex = payloadOption.getAttribute("value");
 
   if (payloadIndex > 0) {
     payload = ping.payload.childPayloads[payloadIndex - 1];
   }
 
   // Show chrome hang stacks
-  ChromeHangs.render(payload);
+  ChromeHangs.render(payload.chromeHangs);
 
   // Show telemetry log.
   TelLog.render(payload);
 
   // Show simple measurements
   SimpleMeasurements.render(payload);
 
 }
--- a/toolkit/themes/shared/in-content/common.inc.css
+++ b/toolkit/themes/shared/in-content/common.inc.css
@@ -165,20 +165,16 @@ html|button {
 *|button,
 html|select,
 xul|colorpicker[type="button"],
 xul|menulist {
   -moz-appearance: none;
   min-height: 30px;
   color: var(--in-content-text-color);
   border: 1px solid var(--in-content-box-border-color);
-  -moz-border-top-colors: none !important;
-  -moz-border-right-colors: none !important;
-  -moz-border-bottom-colors: none !important;
-  -moz-border-left-colors: none !important;
   border-radius: 2px;
   background-color: var(--in-content-page-background);
   margin: 4px 8px;
   /* Ensure font-size isn't overridden by widget styling (e.g. in forms.css) */
   font-size: 1em;
 }
 
 xul|button,
@@ -468,20 +464,16 @@ xul|button[type="menu"] > xul|menupopup 
 html|input[type="email"],
 html|input[type="tel"],
 html|input[type="text"],
 html|textarea,
 xul|textbox {
   -moz-appearance: none;
   color: var(--in-content-text-color);
   border: 1px solid var(--in-content-box-border-color);
-  -moz-border-top-colors: none !important;
-  -moz-border-right-colors: none !important;
-  -moz-border-bottom-colors: none !important;
-  -moz-border-left-colors: none !important;
   border-radius: 2px;
   background-color: var(--in-content-box-background);
 }
 
 xul|textbox {
   min-height: 30px;
   padding-right: 10px;
   padding-left: 10px;
--- a/tools/lint/eslint/setup_helper.py
+++ b/tools/lint/eslint/setup_helper.py
@@ -4,17 +4,17 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from filecmp import dircmp
 import json
 import os
 import platform
 import re
-import shutil
+from mozfile.mozfile import remove as mozfileremove
 import subprocess
 import sys
 from distutils.version import LooseVersion
 sys.path.append(os.path.join(
     os.path.dirname(__file__), "..", "..", "..", "third_party", "python", "which"))
 import which
 
 NODE_MIN_VERSION = "6.9.1"
@@ -75,17 +75,21 @@ def eslint_setup(should_clobber=False):
     # npm sometimes fails to respect cwd when it is run using check_call so
     # we manually switch folders here instead.
     project_root = get_project_root()
     os.chdir(project_root)
 
     if should_clobber:
         node_modules_path = os.path.join(project_root, "node_modules")
         print("Clobbering node_modules...")
-        shutil.rmtree(node_modules_path)
+        if sys.platform.startswith('win') and have_winrm():
+            process = subprocess.Popen(['winrm', '-rf', node_modules_path])
+            process.wait()
+        else:
+            mozfileremove(node_modules_path)
 
     npm_path = get_node_or_npm_path("npm")
     if not npm_path:
         return 1
 
     extra_parameters = ["--loglevel=error"]
 
     # Install ESLint and external plugins
@@ -379,8 +383,19 @@ def check_node_executables_valid():
     if not node_path:
         return False
 
     npm_path = get_node_or_npm_path("npm", LooseVersion(NPM_MIN_VERSION))
     if not npm_path:
         return False
 
     return True
+
+
+def have_winrm():
+    # `winrm -h` should print 'winrm version ...' and exit 1
+    try:
+        p = subprocess.Popen(['winrm.exe', '-h'],
+                             stdout=subprocess.PIPE,
+                             stderr=subprocess.STDOUT)
+        return p.wait() == 1 and p.stdout.read().startswith('winrm')
+    except:
+        return False