merge mozilla-central to mozilla-inbound. r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Tue, 01 Aug 2017 11:26:31 +0200
changeset 423309 c893a197feb671331d469c231d5fc7d40e5fe62f
parent 423308 d7d20b3b2f303590edba39d9d86943e6e757a243 (current diff)
parent 423277 51ffb9283f0c7c00e08eb8c39b33fbee218c370d (diff)
child 423310 e62c93a8c93b83f21e417fc61ee4f163ea48cb8f
push id1517
push userjlorenzo@mozilla.com
push dateThu, 14 Sep 2017 16:50:54 +0000
treeherdermozilla-release@3b41fd564418 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone56.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-central to mozilla-inbound. r=merge a=merge
toolkit/components/extensions/ext-c-downloads.js
toolkit/components/extensions/ext-c-permissions.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -725,21 +725,17 @@ pref("browser.preferences.instantApply",
 #else
 pref("browser.preferences.instantApply", true);
 #endif
 
 // Toggling Search bar on and off in about:preferences
 pref("browser.preferences.search", true);
 
 // Use the new in-content about:preferences in Nightly only for now
-#if defined(NIGHTLY_BUILD)
 pref("browser.preferences.useOldOrganization", false);
-#else
-pref("browser.preferences.useOldOrganization", true);
-#endif
 
 // Once the Storage Management is completed.
 // (The Storage Management-related prefs are browser.storageManager.* )
 // The Offline(Appcache) Group section in about:preferences will be hidden.
 // And the task to clear appcache will be done by Storage Management.
 #if defined(NIGHTLY_BUILD)
 pref("browser.preferences.offlineGroup.enabled", false);
 #else
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -885,17 +885,17 @@ var gPopupBlockerObserver = {
     } catch (e) { }
 
     var bundlePreferences = document.getElementById("bundle_preferences");
     var params = { blockVisible: false,
                    sessionVisible: false,
                    allowVisible: true,
                    prefilledHost: prefillValue,
                    permissionType: "popup",
-                   windowTitle: bundlePreferences.getString("popuppermissionstitle"),
+                   windowTitle: bundlePreferences.getString("popuppermissionstitle2"),
                    introText: bundlePreferences.getString("popuppermissionstext") };
     var existingWindow = Services.wm.getMostRecentWindow("Browser:Permissions");
     if (existingWindow) {
       existingWindow.initWithParams(params);
       existingWindow.focus();
     } else
       window.openDialog("chrome://browser/content/preferences/permissions.xul",
                         "_blank", "resizable,dialog=no,centerscreen", params);
--- a/browser/components/preferences/in-content-new/privacy.js
+++ b/browser/components/preferences/in-content-new/privacy.js
@@ -245,46 +245,46 @@ var gPrivacyPane = {
     setEventListener("submitHealthReportBox", "command",
                      gPrivacyPane.updateSubmitHealthReport);
 
     let bundlePrefs = document.getElementById("bundlePreferences");
     let signonBundle = document.getElementById("signonBundle");
     let pkiBundle = document.getElementById("pkiBundle");
     appendSearchKeywords("passwordExceptions", [
       bundlePrefs.getString("savedLoginsExceptions_title"),
-      bundlePrefs.getString("savedLoginsExceptions_desc"),
+      bundlePrefs.getString("savedLoginsExceptions_desc2"),
     ]);
     appendSearchKeywords("showPasswords", [
       signonBundle.getString("loginsDescriptionAll"),
     ]);
     appendSearchKeywords("trackingProtectionExceptions", [
       bundlePrefs.getString("trackingprotectionpermissionstitle"),
-      bundlePrefs.getString("trackingprotectionpermissionstext"),
+      bundlePrefs.getString("trackingprotectionpermissionstext2"),
     ]);
     appendSearchKeywords("changeBlockList", [
       bundlePrefs.getString("blockliststitle"),
       bundlePrefs.getString("blockliststext"),
     ]);
     appendSearchKeywords("popupPolicyButton", [
-      bundlePrefs.getString("popuppermissionstitle"),
+      bundlePrefs.getString("popuppermissionstitle2"),
       bundlePrefs.getString("popuppermissionstext"),
     ]);
     appendSearchKeywords("notificationsPolicyButton", [
       bundlePrefs.getString("notificationspermissionstitle"),
       bundlePrefs.getString("notificationspermissionstext4"),
     ]);
     appendSearchKeywords("addonExceptions", [
-      bundlePrefs.getString("addons_permissions_title"),
+      bundlePrefs.getString("addons_permissions_title2"),
       bundlePrefs.getString("addonspermissionstext"),
     ]);
     appendSearchKeywords("viewSecurityDevicesButton", [
       pkiBundle.getString("enable_fips"),
     ]);
     appendSearchKeywords("siteDataSettings", [
-      bundlePrefs.getString("siteDataSettings.description"),
+      bundlePrefs.getString("siteDataSettings2.description"),
       bundlePrefs.getString("removeAllCookies.label"),
       bundlePrefs.getString("removeSelectedCookies.label"),
     ]);
 
     // Notify observers that the UI is now ready
     Components.classes["@mozilla.org/observer-service;1"]
               .getService(Components.interfaces.nsIObserverService)
               .notifyObservers(window, "privacy-pane-loaded");
@@ -569,17 +569,17 @@ var gPrivacyPane = {
    * Displays fine-grained, per-site preferences for tracking protection.
    */
   showTrackingProtectionExceptions() {
     let bundlePreferences = document.getElementById("bundlePreferences");
     let params = {
       permissionType: "trackingprotection",
       hideStatusColumn: true,
       windowTitle: bundlePreferences.getString("trackingprotectionpermissionstitle"),
-      introText: bundlePreferences.getString("trackingprotectionpermissionstext"),
+      introText: bundlePreferences.getString("trackingprotectionpermissionstext2"),
     };
     gSubDialog.open("chrome://browser/content/preferences/permissions.xul",
                     null, params);
   },
 
   /**
    * Displays the available block lists for tracking protection.
    */
@@ -825,17 +825,17 @@ var gPrivacyPane = {
   /**
    * Displays the popup exceptions dialog where specific site popup preferences
    * can be set.
    */
   showPopupExceptions() {
     var bundlePreferences = document.getElementById("bundlePreferences");
     var params = { blockVisible: false, sessionVisible: false, allowVisible: true,
                    prefilledHost: "", permissionType: "popup" }
-    params.windowTitle = bundlePreferences.getString("popuppermissionstitle");
+    params.windowTitle = bundlePreferences.getString("popuppermissionstitle2");
     params.introText = bundlePreferences.getString("popuppermissionstext");
 
     gSubDialog.open("chrome://browser/content/preferences/permissions.xul",
                     "resizable=yes", params);
   },
 
    // UTILITY FUNCTIONS
 
@@ -870,17 +870,17 @@ var gPrivacyPane = {
     var params = {
       blockVisible: true,
       sessionVisible: false,
       allowVisible: false,
       hideStatusColumn: true,
       prefilledHost: "",
       permissionType: "login-saving",
       windowTitle: bundlePrefs.getString("savedLoginsExceptions_title"),
-      introText: bundlePrefs.getString("savedLoginsExceptions_desc")
+      introText: bundlePrefs.getString("savedLoginsExceptions_desc2")
     };
 
     gSubDialog.open("chrome://browser/content/preferences/permissions.xul",
                     null, params);
   },
 
   /**
    * Initializes master password UI: the "use master password" checkbox, selects
@@ -1068,17 +1068,17 @@ var gPrivacyPane = {
   /**
    * Displays the exceptions lists for add-on installation warnings.
    */
   showAddonExceptions() {
     var bundlePrefs = document.getElementById("bundlePreferences");
 
     var params = this._addonParams;
     if (!params.windowTitle || !params.introText) {
-      params.windowTitle = bundlePrefs.getString("addons_permissions_title");
+      params.windowTitle = bundlePrefs.getString("addons_permissions_title2");
       params.introText = bundlePrefs.getString("addonspermissionstext");
     }
 
     gSubDialog.open("chrome://browser/content/preferences/permissions.xul",
                     null, params);
   },
 
   /**
--- a/browser/components/preferences/in-content-new/privacy.xul
+++ b/browser/components/preferences/in-content-new/privacy.xul
@@ -180,17 +180,17 @@
 
 <!-- Passwords -->
 <groupbox id="passwordsGroup" orient="vertical" data-category="panePrivacy" hidden="true">
   <caption><label>&formsAndPasswords.label;</label></caption>
 
   <vbox id="passwordSettings">
     <hbox id="savePasswordsBox">
       <checkbox id="savePasswords"
-                label="&rememberLogins1.label;" accesskey="&rememberLogins1.accesskey;"
+                label="&rememberLogins2.label;" accesskey="&rememberLogins2.accesskey;"
                 preference="signon.rememberSignons"
                 onsyncfrompreference="return gPrivacyPane.readSavePasswords();"
                 flex="1" />
       <!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
       <hbox>
         <button id="passwordExceptions"
                 class="accessory-button"
                 label="&passwordExceptions.label;"
@@ -245,17 +245,17 @@
                                                                                             &rememberActions.post.label;"/>
           <menuitem label="&historyHeader.dontremember.label;" value="dontremember" searchkeywords="&dontrememberDescription.label;
                                                                                                     &dontrememberActions.pre.label;
                                                                                                     &dontrememberActions.clearHistory.label;
                                                                                                     &dontrememberActions.post.label;"/>
           <menuitem label="&historyHeader.custom.label;" value="custom" searchkeywords="&privateBrowsingPermanent2.label;
                                                                                         &rememberHistory2.label;
                                                                                         &rememberSearchForm.label;
-                                                                                        &acceptCookies.label;
+                                                                                        &acceptCookies2.label;
                                                                                         &cookieExceptions.label;
                                                                                         &acceptThirdParty.pre.label;
                                                                                         &acceptThirdParty.always.label;
                                                                                         &acceptThirdParty.visited.label;
                                                                                         &acceptThirdParty.never.label;
                                                                                         &keepUntil.label;
                                                                                         &expire.label;
                                                                                         &close.label;
@@ -304,19 +304,19 @@
                     label="&rememberHistory2.label;"
                     accesskey="&rememberHistory2.accesskey;"
                     preference="places.history.enabled"/>
           <checkbox id="rememberForms"
                     label="&rememberSearchForm.label;"
                     accesskey="&rememberSearchForm.accesskey;"
                     preference="browser.formfill.enable"/>
           <hbox id="cookiesBox">
-            <checkbox id="acceptCookies" label="&acceptCookies.label;"
+            <checkbox id="acceptCookies" label="&acceptCookies2.label;"
                       preference="network.cookie.cookieBehavior"
-                      accesskey="&acceptCookies.accesskey;"
+                      accesskey="&acceptCookies2.accesskey;"
                       onsyncfrompreference="return gPrivacyPane.readAcceptCookies();"
                       onsynctopreference="return gPrivacyPane.writeAcceptCookies();"
                       flex="1" />
             <button id="cookieExceptions"
                     class="accessory-button"
                     label="&cookieExceptions.label;" accesskey="&cookieExceptions.accesskey;"
                     preference="pref.privacy.disable_button.cookie_exceptions"/>
           </hbox>
@@ -492,18 +492,18 @@
         <!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
         <hbox>
           <button id="trackingProtectionExceptions"
                   class="accessory-button"
                   hidden="true"
                   label="&trackingProtectionExceptions.label;"
                   accesskey="&trackingProtectionExceptions.accesskey;"
                   preference="pref.privacy.disable_button.tracking_protection_exceptions"
-                  searchkeywords="&removepermission.label;
-                                  &removeallpermissions.label;
+                  searchkeywords="&removepermission2.label;
+                                  &removeallpermissions2.label;
                                   &button.cancel.label;
                                   &button.ok.label;"/>
         </hbox>
         <!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
         <hbox>
           <button id="changeBlockList"
                   class="accessory-button"
                   label="&changeBlockList2.label;"
@@ -537,27 +537,27 @@
   <grid>
     <columns>
       <column flex="1"/>
       <column/>
     </columns>
     <rows>
       <row id="notificationsPolicyRow" align="center">
         <description flex="1">
-          <label id="notificationsPolicy">&notificationsPolicyDesc3.label;</label>
+          <label id="notificationsPolicy">&notificationsPolicyDesc4.label;</label>
           <label id="notificationsPolicyLearnMore"
                  class="learnMore text-link">&notificationsPolicyLearnMore.label;</label>
         </description>
         <hbox pack="end">
           <button id="notificationsPolicyButton"
                   class="accessory-button"
                   label="&notificationsPolicyButton.label;"
                   accesskey="&notificationsPolicyButton.accesskey;"
-                  searchkeywords="&removepermission.label;
-                                  &removeallpermissions.label;
+                  searchkeywords="&removepermission2.label;
+                                  &removeallpermissions2.label;
                                   &button.cancel.label;
                                   &button.ok.label;"/>
         </hbox>
       </row>
     </rows>
   </grid>
   <vbox id="notificationsDoNotDisturbBox" hidden="true">
     <checkbox id="notificationsDoNotDisturb" label="&notificationsDoNotDisturb.label;"
@@ -579,31 +579,31 @@
               label="&popupExceptions.label;"
               accesskey="&popupExceptions.accesskey;"
               searchkeywords="&address.label; &button.cancel.label; &button.ok.label;"/>
     </hbox>
   </hbox>
 
   <hbox id="addonInstallBox">
     <checkbox id="warnAddonInstall"
-              label="&warnOnAddonInstall.label;"
-              accesskey="&warnOnAddonInstall.accesskey;"
+              label="&warnOnAddonInstall2.label;"
+              accesskey="&warnOnAddonInstall2.accesskey;"
               preference="xpinstall.whitelist.required"
               onsyncfrompreference="return gPrivacyPane.readWarnAddonInstall();"
               flex="1" />
     <!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
     <hbox>
       <button id="addonExceptions"
               class="accessory-button"
               label="&addonExceptions.label;"
               accesskey="&addonExceptions.accesskey;"
               searchkeywords="&address.label;
                               &allow.label;
-                              &removepermission.label;
-                              &removeallpermissions.label;
+                              &removepermission2.label;
+                              &removeallpermissions2.label;
                               &button.cancel.label;
                               &button.ok.label;"/>
     </hbox>
   </hbox>
 </groupbox>
 
 <hbox id="dataCollectionCategory"
       class="subcategory"
--- a/browser/components/preferences/in-content/content.js
+++ b/browser/components/preferences/in-content/content.js
@@ -139,17 +139,17 @@ var gContentPane = {
   /**
    * Displays the popup exceptions dialog where specific site popup preferences
    * can be set.
    */
   showPopupExceptions() {
     var bundlePreferences = document.getElementById("bundlePreferences");
     var params = { blockVisible: false, sessionVisible: false, allowVisible: true,
                    prefilledHost: "", permissionType: "popup" }
-    params.windowTitle = bundlePreferences.getString("popuppermissionstitle");
+    params.windowTitle = bundlePreferences.getString("popuppermissionstitle2");
     params.introText = bundlePreferences.getString("popuppermissionstext");
 
     gSubDialog.open("chrome://browser/content/preferences/permissions.xul",
                     "resizable=yes", params);
   },
 
   // FONTS
 
--- a/browser/components/preferences/in-content/content.xul
+++ b/browser/components/preferences/in-content/content.xul
@@ -64,17 +64,17 @@
   <grid>
     <columns>
       <column flex="1"/>
       <column/>
     </columns>
     <rows>
       <row id="notificationsPolicyRow" align="center">
         <hbox align="start">
-          <label id="notificationsPolicy">&notificationsPolicyDesc3.label;</label>
+          <label id="notificationsPolicy">&notificationsPolicyDesc4.label;</label>
           <label id="notificationsPolicyLearnMore"
                  class="learnMore text-link"
                  value="&notificationsPolicyLearnMore.label;"/>
         </hbox>
         <hbox pack="end">
           <button id="notificationsPolicyButton" label="&notificationsPolicyButton.label;"
                   accesskey="&notificationsPolicyButton.accesskey;"/>
         </hbox>
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -464,17 +464,17 @@ var gPrivacyPane = {
    * Displays fine-grained, per-site preferences for tracking protection.
    */
   showTrackingProtectionExceptions() {
     let bundlePreferences = document.getElementById("bundlePreferences");
     let params = {
       permissionType: "trackingprotection",
       hideStatusColumn: true,
       windowTitle: bundlePreferences.getString("trackingprotectionpermissionstitle"),
-      introText: bundlePreferences.getString("trackingprotectionpermissionstext"),
+      introText: bundlePreferences.getString("trackingprotectionpermissionstext2"),
     };
     gSubDialog.open("chrome://browser/content/preferences/permissions.xul",
                     null, params);
   },
 
   /**
    * Displays container panel for customising and adding containers.
    */
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -201,19 +201,19 @@
                       accesskey="&rememberHistory2.accesskey;"
                       preference="places.history.enabled"/>
             <checkbox id="rememberForms"
                       label="&rememberSearchForm.label;"
                       accesskey="&rememberSearchForm.accesskey;"
                       preference="browser.formfill.enable"/>
           </vbox>
           <hbox id="cookiesBox">
-            <checkbox id="acceptCookies" label="&acceptCookies.label;"
+            <checkbox id="acceptCookies" label="&acceptCookies2.label;"
                       preference="network.cookie.cookieBehavior"
-                      accesskey="&acceptCookies.accesskey;"
+                      accesskey="&acceptCookies2.accesskey;"
                       onsyncfrompreference="return gPrivacyPane.readAcceptCookies();"
                       onsynctopreference="return gPrivacyPane.writeAcceptCookies();"/>
             <spacer flex="1" />
             <button id="cookieExceptions"
                     label="&cookieExceptions.label;" accesskey="&cookieExceptions.accesskey;"
                     preference="pref.privacy.disable_button.cookie_exceptions"/>
           </hbox>
           <hbox id="acceptThirdPartyRow"
--- a/browser/components/preferences/in-content/security.js
+++ b/browser/components/preferences/in-content/security.js
@@ -65,17 +65,17 @@ var gSecurityPane = {
   /**
    * Displays the exceptions lists for add-on installation warnings.
    */
   showAddonExceptions() {
     var bundlePrefs = document.getElementById("bundlePreferences");
 
     var params = this._addonParams;
     if (!params.windowTitle || !params.introText) {
-      params.windowTitle = bundlePrefs.getString("addons_permissions_title");
+      params.windowTitle = bundlePrefs.getString("addons_permissions_title2");
       params.introText = bundlePrefs.getString("addonspermissionstext");
     }
 
     gSubDialog.open("chrome://browser/content/preferences/permissions.xul",
                     null, params);
   },
 
   /**
@@ -127,17 +127,17 @@ var gSecurityPane = {
     var params = {
       blockVisible: true,
       sessionVisible: false,
       allowVisible: false,
       hideStatusColumn: true,
       prefilledHost: "",
       permissionType: "login-saving",
       windowTitle: bundlePrefs.getString("savedLoginsExceptions_title"),
-      introText: bundlePrefs.getString("savedLoginsExceptions_desc")
+      introText: bundlePrefs.getString("savedLoginsExceptions_desc2")
     };
 
     gSubDialog.open("chrome://browser/content/preferences/permissions.xul",
                     null, params);
   },
 
   /**
    * Initializes master password UI: the "use master password" checkbox, selects
--- a/browser/components/preferences/in-content/security.xul
+++ b/browser/components/preferences/in-content/security.xul
@@ -57,18 +57,18 @@
 </hbox>
 
 <!-- addons, forgery (phishing) UI -->
 <groupbox id="addonsPhishingGroup" data-category="paneSecurity" hidden="true">
   <caption><label>&general.label;</label></caption>
 
   <hbox id="addonInstallBox">
     <checkbox id="warnAddonInstall"
-              label="&warnOnAddonInstall.label;"
-              accesskey="&warnOnAddonInstall.accesskey;"
+              label="&warnOnAddonInstall2.label;"
+              accesskey="&warnOnAddonInstall2.accesskey;"
               preference="xpinstall.whitelist.required"
               onsyncfrompreference="return gSecurityPane.readWarnAddonInstall();"/>
     <spacer flex="1"/>
     <button id="addonExceptions"
             label="&addonExceptions.label;"
             accesskey="&addonExceptions.accesskey;"/>
   </hbox>
 
@@ -95,17 +95,17 @@
   <grid id="passwordGrid">
     <columns>
       <column flex="1"/>
       <column/>
     </columns>
     <rows id="passwordRows">
       <row id="savePasswordsBox">
         <checkbox id="savePasswords"
-                  label="&rememberLogins.label;" accesskey="&rememberLogins.accesskey;"
+                  label="&rememberLogins2.label;" accesskey="&rememberLogins2.accesskey;"
                   preference="signon.rememberSignons"
                   onsyncfrompreference="return gSecurityPane.readSavePasswords();"/>
         <button id="passwordExceptions"
                 label="&passwordExceptions.label;"
                 accesskey="&passwordExceptions.accesskey;"
                 preference="pref.privacy.disable_button.view_passwords_exceptions"/>
       </row>
       <row id="showPasswordRow">
--- a/browser/components/preferences/permissions.xul
+++ b/browser/components/preferences/permissions.xul
@@ -47,34 +47,34 @@
               oncommand="gPermissionManager.addPermission(nsIPermissionManager.ALLOW_ACTION);"/>
     </hbox>
     <separator class="thin"/>
     <tree id="permissionsTree" flex="1" style="height: 18em;"
           hidecolumnpicker="true"
           onkeypress="gPermissionManager.onPermissionKeyPress(event)"
           onselect="gPermissionManager.onPermissionSelected();">
       <treecols>
-        <treecol id="siteCol" label="&treehead.sitename.label;" flex="3"
+        <treecol id="siteCol" label="&treehead.sitename2.label;" flex="3"
                  data-field-name="origin" persist="width"/>
         <splitter class="tree-splitter"/>
         <treecol id="statusCol" label="&treehead.status.label;" flex="1"
                  data-field-name="capability" persist="width"/>
       </treecols>
       <treechildren/>
     </tree>
   </vbox>
   <vbox>
     <hbox class="actionButtons" align="left" flex="1">
       <button id="removePermission" disabled="true"
-              accesskey="&removepermission.accesskey;"
-              icon="remove" label="&removepermission.label;"
+              accesskey="&removepermission2.accesskey;"
+              icon="remove" label="&removepermission2.label;"
               oncommand="gPermissionManager.onPermissionDeleted();"/>
       <button id="removeAllPermissions"
-              icon="clear" label="&removeallpermissions.label;"
-              accesskey="&removeallpermissions.accesskey;"
+              icon="clear" label="&removeallpermissions2.label;"
+              accesskey="&removeallpermissions2.accesskey;"
               oncommand="gPermissionManager.onAllPermissionsDeleted();"/>
     </hbox>
     <spacer flex="1"/>
     <hbox class="actionButtons" align="right" flex="1">
       <button oncommand="close();" icon="close"
               label="&button.cancel.label;" accesskey="&button.cancel.accesskey;" />
       <button id="btnApplyChanges" oncommand="gPermissionManager.onApplyChanges();" icon="save"
               label="&button.ok.label;" accesskey="&button.ok.accesskey;"/>
--- a/browser/components/preferences/siteDataSettings.js
+++ b/browser/components/preferences/siteDataSettings.js
@@ -42,17 +42,17 @@ let gSiteDataSettings = {
       let sortCol = document.querySelector("treecol[data-isCurrentSortCol=true]");
       this._sortSites(this._sites, sortCol);
       this._buildSitesList(this._sites);
       Services.obs.notifyObservers(null, "sitedata-settings-init");
     });
 
     let brandShortName = document.getElementById("bundle_brand").getString("brandShortName");
     let settingsDescription = document.getElementById("settingsDescription");
-    settingsDescription.textContent = this._prefStrBundle.getFormattedString("siteDataSettings.description", [brandShortName]);
+    settingsDescription.textContent = this._prefStrBundle.getFormattedString("siteDataSettings2.description", [brandShortName]);
 
     setEventListener("hostCol", "click", this.onClickTreeCol);
     setEventListener("usageCol", "click", this.onClickTreeCol);
     setEventListener("statusCol", "click", this.onClickTreeCol);
     setEventListener("cancel", "command", this.close);
     setEventListener("save", "command", this.saveChanges);
     setEventListener("searchBox", "command", this.onCommandSearch);
     setEventListener("removeAll", "command", this.onClickRemoveAll);
--- a/browser/components/preferences/translation.xul
+++ b/browser/components/preferences/translation.xul
@@ -59,17 +59,17 @@
     <vbox class="contentPane" flex="1">
       <label id="languagesLabel" control="permissionsTree">&noTranslationForSites.label;</label>
       <separator class="thin"/>
       <tree id="sitesTree" flex="1" style="height: 12em;"
             hidecolumnpicker="true"
             onkeypress="gTranslationExceptions.onSiteKeyPress(event)"
             onselect="gTranslationExceptions.onSiteSelected();">
         <treecols>
-          <treecol id="siteCol" label="&treehead.siteName.label;" flex="1"/>
+          <treecol id="siteCol" label="&treehead.siteName2.label;" flex="1"/>
         </treecols>
         <treechildren/>
       </tree>
     </vbox>
   </vbox>
   <hbox align="end">
     <hbox class="actionButtons" flex="1">
       <button id="removeSite" disabled="true"
--- a/browser/extensions/e10srollout/bootstrap.js
+++ b/browser/extensions/e10srollout/bootstrap.js
@@ -136,46 +136,46 @@ function defineCohort() {
   }
 
   let eligibleForMulti = false;
   if (userOptedOut.e10s || userOptedOut.multi) {
     // If we detected that the user opted out either for multi or e10s, then
     // the proper prefs must already be set.
     setCohort("optedOut");
   } else if (userOptedIn.e10s) {
+    eligibleForMulti = true;
     setCohort("optedIn");
-    eligibleForMulti = true;
   } else if (temporaryDisqualification != "") {
     // Users who are disqualified by the backend (from multiprocessBlockPolicy)
     // can be put into either the test or control groups, because e10s will
     // still be denied by the backend, which is useful so that the E10S_STATUS
     // telemetry probe can be correctly set.
 
     // For these volatile disqualification reasons, however, we must not try
     // to activate e10s because the backend doesn't know about it. E10S_STATUS
     // here will be accumulated as "2 - Disabled", which is fine too.
-    setCohort(`temp-disqualified-${temporaryDisqualification}`);
     Services.prefs.clearUserPref(PREF_TOGGLE_E10S);
     Services.prefs.clearUserPref(PREF_E10S_PROCESSCOUNT + ".web");
+    setCohort(`temp-disqualified-${temporaryDisqualification}`);
   } else if (!disqualified && testThreshold < 1.0 &&
              temporaryQualification != "") {
     // Users who are qualified for e10s and on channels where some population
     // would not receive e10s can be pushed into e10s anyway via a temporary
     // qualification which overrides the user sample value when non-empty.
+    Services.prefs.setBoolPref.set(PREF_TOGGLE_E10S, true);
+    eligibleForMulti = true;
     setCohort(`temp-qualified-${temporaryQualification}`);
+  } else if (testGroup) {
     Services.prefs.setBoolPref(PREF_TOGGLE_E10S, true);
     eligibleForMulti = true;
-  } else if (testGroup) {
     setCohort(`${cohortPrefix}test`);
-    Services.prefs.setBoolPref(PREF_TOGGLE_E10S, true);
-    eligibleForMulti = true;
   } else {
-    setCohort(`${cohortPrefix}control`);
     Services.prefs.clearUserPref(PREF_TOGGLE_E10S);
     Services.prefs.clearUserPref(PREF_E10S_PROCESSCOUNT + ".web");
+    setCohort(`${cohortPrefix}control`);
   }
 
   // Now determine if this user should be in the e10s-multi experiment.
   // - We only run the experiment on channels defined in MULTI_EXPERIMENT.
   // - If this experiment doesn't allow addons and we have a cohort prefix
   //   (i.e. there's at least one addon installed) we stop here.
   // - We decided above whether this user qualifies for the experiment.
   // - If the user already opted into multi, then their prefs are already set
@@ -202,20 +202,19 @@ function defineCohort() {
 
   // The user is in the multi experiment!
   // Decide how many content processes to use for this user.
   let buckets = MULTI_EXPERIMENT[updateChannel].buckets;
 
   let multiUserSample = getUserSample(true);
   for (let sampleName of Object.getOwnPropertyNames(buckets)) {
     if (multiUserSample < buckets[sampleName]) {
-      setCohort(`${cohortPrefix}multiBucket${sampleName}`);
-
       // NB: Coerce sampleName to an integer because this is an integer pref.
       Services.prefs.setIntPref(PREF_E10S_PROCESSCOUNT + ".web", +sampleName);
+      setCohort(`${cohortPrefix}multiBucket${sampleName}`);
       break;
     }
   }
 }
 
 function shutdown(data, reason) {
 }
 
--- a/browser/extensions/e10srollout/install.rdf.in
+++ b/browser/extensions/e10srollout/install.rdf.in
@@ -5,17 +5,17 @@
 
 #filter substitution
 
 <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
      xmlns:em="http://www.mozilla.org/2004/em-rdf#">
 
   <Description about="urn:mozilla:install-manifest">
     <em:id>e10srollout@mozilla.org</em:id>
-    <em:version>2.0</em:version>
+    <em:version>2.1</em:version>
     <em:type>2</em:type>
     <em:bootstrap>true</em:bootstrap>
     <em:multiprocessCompatible>true</em:multiprocessCompatible>
 
     <!-- Target Application this theme can install into,
         with minimum and maximum supported versions. -->
     <em:targetApplication>
       <Description>
--- a/browser/extensions/formautofill/FormAutofillHandler.jsm
+++ b/browser/extensions/formautofill/FormAutofillHandler.jsm
@@ -150,16 +150,17 @@ FormAutofillHandler.prototype = {
 
   getFieldDetailByName(fieldName) {
     return this.fieldDetails.find(detail => detail.fieldName == fieldName);
   },
 
   _cacheValue: {
     allFieldNames: null,
     oneLineStreetAddress: null,
+    matchingSelectOption: null,
   },
 
   get allFieldNames() {
     if (!this._cacheValue.allFieldNames) {
       this._cacheValue.allFieldNames = this.fieldDetails.map(record => record.fieldName);
     }
     return this._cacheValue.allFieldNames;
   },
@@ -193,19 +194,58 @@ FormAutofillHandler.prototype = {
             profile[f] = FormAutofillUtils.toOneLineAddress(waitForConcat);
           }
           waitForConcat = [];
         }
       }
     }
   },
 
+  _matchSelectOptions(profile) {
+    if (!this._cacheValue.matchingSelectOption) {
+      this._cacheValue.matchingSelectOption = new WeakMap();
+    }
+
+    for (let fieldName in profile) {
+      let fieldDetail = this.getFieldDetailByName(fieldName);
+      if (!fieldDetail) {
+        continue;
+      }
+
+      let element = fieldDetail.elementWeakRef.get();
+      if (!(element instanceof Ci.nsIDOMHTMLSelectElement)) {
+        continue;
+      }
+
+      let cache = this._cacheValue.matchingSelectOption.get(element) || {};
+      let value = profile[fieldName];
+      if (cache[value] && cache[value].get()) {
+        continue;
+      }
+
+      let option = FormAutofillUtils.findSelectOption(element, profile, fieldName);
+      if (option) {
+        cache[value] = Cu.getWeakReference(option);
+        this._cacheValue.matchingSelectOption.set(element, cache);
+      } else {
+        if (cache[value]) {
+          delete cache[value];
+          this._cacheValue.matchingSelectOption.set(element, cache);
+        }
+        // Delete the field so the phishing hint won't treat it as a "also fill"
+        // field.
+        delete profile[fieldName];
+      }
+    }
+  },
+
   getAdaptedProfiles(originalProfiles) {
     for (let profile of originalProfiles) {
       this._addressTransformer(profile);
+      this._matchSelectOptions(profile);
     }
     return originalProfiles;
   },
 
   /**
    * Processes form fields that can be autofilled, and populates them with the
    * profile provided by backend.
    *
@@ -232,17 +272,18 @@ FormAutofillHandler.prototype = {
 
       let value = profile[fieldDetail.fieldName];
       if (element instanceof Ci.nsIDOMHTMLInputElement && !element.value && value) {
         if (element !== focusedInput) {
           element.setUserInput(value);
         }
         this.changeFieldState(fieldDetail, "AUTO_FILLED");
       } else if (element instanceof Ci.nsIDOMHTMLSelectElement) {
-        let option = FormAutofillUtils.findSelectOption(element, profile, fieldDetail.fieldName);
+        let cache = this._cacheValue.matchingSelectOption.get(element) || {};
+        let option = cache[value] && cache[value].get();
         if (!option) {
           continue;
         }
         // Do not change value or dispatch events if the option is already selected.
         // Use case for multiple select is not considered here.
         if (!option.selected) {
           option.selected = true;
           element.dispatchEvent(new element.ownerGlobal.UIEvent("input", {bubbles: true}));
@@ -321,27 +362,31 @@ FormAutofillHandler.prototype = {
       // Skip the field that is null
       if (!element) {
         continue;
       }
 
       if (element instanceof Ci.nsIDOMHTMLSelectElement) {
         // Unlike text input, select element is always previewed even if
         // the option is already selected.
-        let option = FormAutofillUtils.findSelectOption(element, profile, fieldDetail.fieldName);
-        element.previewValue = option ? option.text : "";
-        this.changeFieldState(fieldDetail, option ? "PREVIEW" : "NORMAL");
-      } else {
-        // Skip the field if it already has text entered
-        if (element.value) {
-          continue;
+        if (value) {
+          let cache = this._cacheValue.matchingSelectOption.get(element) || {};
+          let option = cache[value] && cache[value].get();
+          if (option) {
+            value = option.text || "";
+          } else {
+            value = "";
+          }
         }
-        element.previewValue = value;
-        this.changeFieldState(fieldDetail, value ? "PREVIEW" : "NORMAL");
+      } else if (element.value) {
+        // Skip the field if it already has text entered.
+        continue;
       }
+      element.previewValue = value;
+      this.changeFieldState(fieldDetail, value ? "PREVIEW" : "NORMAL");
     }
   },
 
   /**
    * Clear preview text and background highlight of all fields.
    */
   clearPreviewedFormFields() {
     log.debug("clear previewed fields in:", this.form);
--- a/browser/extensions/formautofill/test/unit/test_autofillFormFields.js
+++ b/browser/extensions/formautofill/test/unit/test_autofillFormFields.js
@@ -318,29 +318,30 @@ function do_test(testcases, testFn) {
 
         let doc = MockDocument.createTestDocument("http://localhost:8080/test/",
                                                   testcase.document);
         let form = doc.querySelector("form");
         let formLike = FormLikeFactory.createFromForm(form);
         let handler = new FormAutofillHandler(formLike);
         let promises = [];
 
-        handler.address.fieldDetails = testcase.addressFieldDetails;
+        handler.fieldDetails = handler.address.fieldDetails = testcase.addressFieldDetails;
         handler.address.fieldDetails.forEach((field, index) => {
           let element = doc.querySelectorAll("input, select")[index];
           field.elementWeakRef = Cu.getWeakReference(element);
           if (!testcase.profileData[field.fieldName]) {
             // Avoid waiting for `change` event of a input with a blank value to
             // be filled.
             return;
           }
           promises.push(...testFn(testcase, element));
         });
 
-        handler.autofillFormFields(testcase.profileData);
+        let [adaptedProfile] = handler.getAdaptedProfiles([testcase.profileData]);
+        handler.autofillFormFields(adaptedProfile);
         Assert.equal(handler.address.filledRecordGUID, testcase.profileData.guid,
                      "Check if filledRecordGUID is set correctly");
         await Promise.all(promises);
       });
     })();
   }
 }
 
--- a/browser/extensions/formautofill/test/unit/test_getAdaptedProfiles.js
+++ b/browser/extensions/formautofill/test/unit/test_getAdaptedProfiles.js
@@ -7,32 +7,36 @@
 Cu.import("resource://formautofill/FormAutofillHandler.jsm");
 
 const DEFAULT_PROFILE = {
   "guid": "123",
   "street-address": "2 Harrison St\nline2\nline3",
   "address-line1": "2 Harrison St",
   "address-line2": "line2",
   "address-line3": "line3",
+  "address-level1": "CA",
+  "country": "US",
 };
 
 const TESTCASES = [
   {
     description: "Form with street-address",
     document: `<form>
                <input id="street-addr" autocomplete="street-address">
                </form>`,
     profileData: [Object.assign({}, DEFAULT_PROFILE)],
     expectedResult: [{
       "guid": "123",
       "street-address": "2 Harrison St line2 line3",
       "-moz-street-address-one-line": "2 Harrison St line2 line3",
       "address-line1": "2 Harrison St",
       "address-line2": "line2",
       "address-line3": "line3",
+      "address-level1": "CA",
+      "country": "US",
     }],
   },
   {
     description: "Form with street-address, address-line[1, 2, 3]",
     document: `<form>
                <input id="street-addr" autocomplete="street-address">
                <input id="line1" autocomplete="address-line1">
                <input id="line2" autocomplete="address-line2">
@@ -41,32 +45,36 @@ const TESTCASES = [
     profileData: [Object.assign({}, DEFAULT_PROFILE)],
     expectedResult: [{
       "guid": "123",
       "street-address": "2 Harrison St line2 line3",
       "-moz-street-address-one-line": "2 Harrison St line2 line3",
       "address-line1": "2 Harrison St",
       "address-line2": "line2",
       "address-line3": "line3",
+      "address-level1": "CA",
+      "country": "US",
     }],
   },
   {
     description: "Form with street-address, address-line1",
     document: `<form>
                <input id="street-addr" autocomplete="street-address">
                <input id="line1" autocomplete="address-line1">
                </form>`,
     profileData: [Object.assign({}, DEFAULT_PROFILE)],
     expectedResult: [{
       "guid": "123",
       "street-address": "2 Harrison St line2 line3",
       "-moz-street-address-one-line": "2 Harrison St line2 line3",
       "address-line1": "2 Harrison St line2 line3",
       "address-line2": "line2",
       "address-line3": "line3",
+      "address-level1": "CA",
+      "country": "US",
     }],
   },
   {
     description: "Form with street-address, address-line[1, 2]",
     document: `<form>
                <input id="street-addr" autocomplete="street-address">
                <input id="line1" autocomplete="address-line1">
                <input id="line2" autocomplete="address-line2">
@@ -74,16 +82,18 @@ const TESTCASES = [
     profileData: [Object.assign({}, DEFAULT_PROFILE)],
     expectedResult: [{
       "guid": "123",
       "street-address": "2 Harrison St line2 line3",
       "-moz-street-address-one-line": "2 Harrison St line2 line3",
       "address-line1": "2 Harrison St",
       "address-line2": "line2 line3",
       "address-line3": "line3",
+      "address-level1": "CA",
+      "country": "US",
     }],
   },
   {
     description: "Form with street-address, address-line[1, 3]",
     document: `<form>
                <input id="street-addr" autocomplete="street-address">
                <input id="line1" autocomplete="address-line1">
                <input id="line3" autocomplete="address-line3">
@@ -91,16 +101,129 @@ const TESTCASES = [
     profileData: [Object.assign({}, DEFAULT_PROFILE)],
     expectedResult: [{
       "guid": "123",
       "street-address": "2 Harrison St line2 line3",
       "-moz-street-address-one-line": "2 Harrison St line2 line3",
       "address-line1": "2 Harrison St",
       "address-line2": "line2 line3",
       "address-line3": "line3",
+      "address-level1": "CA",
+      "country": "US",
+    }],
+  },
+  {
+    description: "Form with exact matching options in select",
+    document: `<form>
+               <select autocomplete="address-level1">
+                 <option id="option-address-level1-XX" value="XX">Dummy</option>
+                 <option id="option-address-level1-CA" value="CA">California</option>
+               </select>
+               </form>`,
+    profileData: [Object.assign({}, DEFAULT_PROFILE)],
+    expectedResult: [{
+      "guid": "123",
+      "street-address": "2 Harrison St\nline2\nline3",
+      "-moz-street-address-one-line": "2 Harrison St line2 line3",
+      "address-line1": "2 Harrison St",
+      "address-line2": "line2",
+      "address-line3": "line3",
+      "address-level1": "CA",
+      "country": "US",
+    }],
+    expectedOptionElements: [{
+      "address-level1": "option-address-level1-CA",
+    }],
+  },
+  {
+    description: "Form with inexact matching options in select",
+    document: `<form>
+               <select autocomplete="address-level1">
+                 <option id="option-address-level1-XX" value="XX">Dummy</option>
+                 <option id="option-address-level1-OO" value="OO">California</option>
+               </select>
+               </form>`,
+    profileData: [Object.assign({}, DEFAULT_PROFILE)],
+    expectedResult: [{
+      "guid": "123",
+      "street-address": "2 Harrison St\nline2\nline3",
+      "-moz-street-address-one-line": "2 Harrison St line2 line3",
+      "address-line1": "2 Harrison St",
+      "address-line2": "line2",
+      "address-line3": "line3",
+      "address-level1": "CA",
+      "country": "US",
+    }],
+    expectedOptionElements: [{
+      "address-level1": "option-address-level1-OO",
+    }],
+  },
+  {
+    description: "Form with value-omitted options in select",
+    document: `<form>
+               <select autocomplete="address-level1">
+                 <option id="option-address-level1-1" value="">Dummy</option>
+                 <option id="option-address-level1-2" value="">California</option>
+               </select>
+               </form>`,
+    profileData: [Object.assign({}, DEFAULT_PROFILE)],
+    expectedResult: [{
+      "guid": "123",
+      "street-address": "2 Harrison St\nline2\nline3",
+      "-moz-street-address-one-line": "2 Harrison St line2 line3",
+      "address-line1": "2 Harrison St",
+      "address-line2": "line2",
+      "address-line3": "line3",
+      "address-level1": "CA",
+      "country": "US",
+    }],
+    expectedOptionElements: [{
+      "address-level1": "option-address-level1-2",
+    }],
+  },
+  {
+    description: "Form with options with the same value in select",
+    document: `<form>
+               <select autocomplete="address-level1">
+                 <option id="option-address-level1-same1" value="same">Dummy</option>
+                 <option id="option-address-level1-same2" value="same">California</option>
+               </select>
+               </form>`,
+    profileData: [Object.assign({}, DEFAULT_PROFILE)],
+    expectedResult: [{
+      "guid": "123",
+      "street-address": "2 Harrison St\nline2\nline3",
+      "-moz-street-address-one-line": "2 Harrison St line2 line3",
+      "address-line1": "2 Harrison St",
+      "address-line2": "line2",
+      "address-line3": "line3",
+      "address-level1": "CA",
+      "country": "US",
+    }],
+    expectedOptionElements: [{
+      "address-level1": "option-address-level1-same2",
+    }],
+  },
+  {
+    description: "Form without matching options in select",
+    document: `<form>
+               <select autocomplete="address-level1">
+                 <option id="option-address-level1-dummy1" value="">Dummy</option>
+                 <option id="option-address-level1-dummy2" value="">Dummy 2</option>
+               </select>
+               </form>`,
+    profileData: [Object.assign({}, DEFAULT_PROFILE)],
+    expectedResult: [{
+      "guid": "123",
+      "street-address": "2 Harrison St\nline2\nline3",
+      "-moz-street-address-one-line": "2 Harrison St line2 line3",
+      "address-line1": "2 Harrison St",
+      "address-line2": "line2",
+      "address-line3": "line3",
+      "country": "US",
     }],
   },
 ];
 
 for (let testcase of TESTCASES) {
   add_task(async function() {
     do_print("Starting testcase: " + testcase.description);
 
@@ -108,11 +231,27 @@ for (let testcase of TESTCASES) {
                                               testcase.document);
     let form = doc.querySelector("form");
     let formLike = FormLikeFactory.createFromForm(form);
     let handler = new FormAutofillHandler(formLike);
 
     handler.collectFormFields();
     let adaptedAddresses = handler.getAdaptedProfiles(testcase.profileData);
     Assert.deepEqual(adaptedAddresses, testcase.expectedResult);
+
+    if (testcase.expectedOptionElements) {
+      testcase.expectedOptionElements.forEach((expectedOptionElement, i) => {
+        for (let field in expectedOptionElement) {
+          let select = form.querySelector(`[autocomplete=${field}]`);
+          let expectedOption = doc.getElementById(expectedOptionElement[field]);
+          Assert.notEqual(expectedOption, null);
+
+          let value = testcase.profileData[i][field];
+          let cache = handler._cacheValue.matchingSelectOption.get(select);
+          let targetOption = cache[value] && cache[value].get();
+          Assert.notEqual(targetOption, null);
+
+          Assert.equal(targetOption, expectedOption);
+        }
+      });
+    }
   });
 }
-
--- a/browser/extensions/moz.build
+++ b/browser/extensions/moz.build
@@ -1,15 +1,16 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DIRS += [
+    'activity-stream',
     'aushelper',
     'clicktoplay-rollout',
     'e10srollout',
     'followonsearch',
     'onboarding',
     'pdfjs',
     'pocket',
     'screenshots',
@@ -31,14 +32,8 @@ if CONFIG['MOZ_DEV_EDITION'] or CONFIG['
         'webcompat-reporter',
     ]
 
 # Only include mortar system add-ons if we locally enable it
 if CONFIG['MOZ_MORTAR']:
     DIRS += [
         'mortar',
     ]
-
-# Nightly-only system add-ons
-if CONFIG['NIGHTLY_BUILD']:
-    DIRS += [
-        'activity-stream',
-    ]
--- a/browser/locales/en-US/chrome/browser/preferences-old/content.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences-old/content.dtd
@@ -4,17 +4,17 @@
 
 <!ENTITY  popups.label                "Pop-ups">
 
 <!ENTITY  blockPopups.label           "Block pop-up windows">
 <!ENTITY  blockPopups.accesskey       "B">
 
 <!ENTITY  notificationsPolicy.label            "Notifications">
 <!ENTITY  notificationsPolicyLearnMore.label   "Learn more">
-<!ENTITY  notificationsPolicyDesc3.label       "Choose which sites are allowed to send you notifications">
+<!ENTITY  notificationsPolicyDesc4.label       "Choose which websites are allowed to send you notifications">
 <!ENTITY  notificationsPolicyButton.accesskey  "h">
 <!ENTITY  notificationsPolicyButton.label      "Choose…">
 <!ENTITY  notificationsDoNotDisturb.label      "Do not disturb me">
 <!ENTITY  notificationsDoNotDisturb.accesskey  "n">
 <!ENTITY  notificationsDoNotDisturbDetails.value "No notification will be shown until you restart &brandShortName;">
 
 <!ENTITY  popupExceptions.label       "Exceptions…">
 <!ENTITY  popupExceptions.accesskey   "E">
--- a/browser/locales/en-US/chrome/browser/preferences-old/preferences.properties
+++ b/browser/locales/en-US/chrome/browser/preferences-old/preferences.properties
@@ -14,30 +14,30 @@ phishBeforeText=Selecting this option wi
 labelDefaultFont=Default (%S)
 
 veryLargeMinimumFontTitle=Large minimum font size
 veryLargeMinimumFontWarning=You have selected a very large minimum font size (more than 24 pixels). This may make it difficult or impossible to use some important configuration pages like this one.
 acceptVeryLargeMinimumFont=Keep my changes anyway
 
 #### Permissions Manager
 
-trackingprotectionpermissionstext=You have disabled Tracking Protection on these sites.
+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_title=Allowed Sites - Add-ons Installation
+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.
-popuppermissionstitle=Allowed Sites - Pop-ups
+popuppermissionstitle2=Allowed Websites - Pop-ups
 notificationspermissionstext4=Control which websites are always or never allowed to send you notifications. If you remove a site, it will need to request permission again.
 notificationspermissionstitle=Notification Permissions
 invalidURI=Please enter a valid hostname
 invalidURITitle=Invalid Hostname Entered
 savedLoginsExceptions_title=Exceptions - Saved Logins
-savedLoginsExceptions_desc=Logins for the following sites will not be saved:
+savedLoginsExceptions_desc2=Logins for the following websites will not be saved:
 
 #### Block List Manager
 
 blockliststext=You can choose which list Firefox will use to block Web elements that may track your browsing activity.
 blockliststitle=Block Lists
 # LOCALIZATION NOTE (mozNameTemplate): This template constructs the name of the
 # block list in the block lists dialog. It combines the list name and
 # description.
@@ -46,17 +46,17 @@ blockliststitle=Block Lists
 mozNameTemplate=%1$S %2$S
 # LOCALIZATION NOTE (mozstdName, etc.): These labels appear in the tracking
 # protection block lists dialog, mozNameTemplate is used to create the final
 # string. Note that in the future these two strings (name, desc) could be
 # displayed on two different lines.
 mozstdName=Disconnect.me basic protection (Recommended).
 mozstdDesc=Allows some trackers so websites function properly.
 mozfullName=Disconnect.me strict protection.
-mozfullDesc=Blocks known trackers. Some sites may not function properly.
+mozfullDesc2=Blocks known trackers. Some websites may not function properly.
 # LOCALIZATION NOTE (blocklistChangeRequiresRestart): %S = brandShortName
 blocklistChangeRequiresRestart=%S must restart to change block lists.
 
 #### Master Password
 
 pw_change2empty_in_fips_mode=You are currently in FIPS mode. FIPS requires a non-empty Master Password.
 pw_change_failed_title=Password Change Failed
 
@@ -186,18 +186,18 @@ actualAppCacheSize=Your application cach
 totalSiteDataSize=Your stored site data is currently using %1$S %2$S of disk space
 loadingSiteDataSize=Calculating site data size…
 clearSiteDataPromptTitle=Clear all cookies and site data
 clearSiteDataPromptText=Selecting ‘Clear Now’ will clear all cookies and site data stored by Firefox. This may sign you out of websites and remove offline web content.
 clearSiteDataNow=Clear Now
 persistent=Persistent
 siteUsage=%1$S %2$S
 acceptRemove=Remove
-# LOCALIZATION NOTE (siteDataSettings.description): %S = brandShortName
-siteDataSettings.description=The following websites store site data on your computer. %S keeps data from sites with persistent storage until you delete it, and deletes data from sites with non-persistent storage as space is needed.
+# LOCALIZATION NOTE (siteDataSettings2.description): %S = brandShortName
+siteDataSettings2.description=The following websites store site data on your computer. %S keeps data from websites with persistent storage until you delete it, and deletes data from websites with non-persistent storage as space is needed.
 # LOCALIZATION NOTE (removeAllSiteData, removeAllSiteDataShown):
 # removeAllSiteData and removeAllSiteDataShown are both used on the same one button,
 # never displayed together and can share the same accesskey.
 # When only partial sites are shown as a result of keyword search,
 # removeAllShown is displayed as button label.
 # removeAll is displayed when no keyword search and all sites are shown.
 removeAllSiteData.label=Remove All
 removeAllSiteData.accesskey=e
--- a/browser/locales/en-US/chrome/browser/preferences-old/privacy.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences-old/privacy.dtd
@@ -38,18 +38,18 @@
 <!ENTITY  locbar.openpage.label         "Open tabs">
 <!ENTITY  locbar.openpage.accesskey     "O">
 <!ENTITY  locbar.searches.label         "Related searches from the default search engine">
 <!ENTITY  locbar.searches.accesskey     "d">
 
 <!ENTITY  suggestionSettings.label      "Change preferences for search engine suggestions…">
 <!ENTITY  suggestionSettings.accesskey  "g">
 
-<!ENTITY  acceptCookies.label           "Accept cookies from sites">
-<!ENTITY  acceptCookies.accesskey       "A">
+<!ENTITY  acceptCookies2.label          "Accept cookies from websites">
+<!ENTITY  acceptCookies2.accesskey      "A">
 
 <!ENTITY  acceptThirdParty.pre.label      "Accept third-party cookies:">
 <!ENTITY  acceptThirdParty.pre.accesskey  "y">
 <!ENTITY  acceptThirdParty.always.label   "Always">
 <!ENTITY  acceptThirdParty.never.label    "Never">
 <!ENTITY  acceptThirdParty.visited.label  "From visited">
 
 <!ENTITY  keepUntil.label               "Keep until:">
--- a/browser/locales/en-US/chrome/browser/preferences-old/security.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences-old/security.dtd
@@ -1,16 +1,16 @@
 <!-- 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/. -->
 
 <!ENTITY  general.label                 "General">
 
-<!ENTITY  warnOnAddonInstall.label        "Warn you when sites try to install add-ons">
-<!ENTITY  warnOnAddonInstall.accesskey    "W">
+<!ENTITY  warnOnAddonInstall2.label       "Warn you when websites try to install add-ons">
+<!ENTITY  warnOnAddonInstall2.accesskey   "W">
 
 <!-- LOCALIZATION NOTE (enableSafeBrowsing.label, blockDownloads.label, blockUncommonUnwanted.label):
   It is important that wording follows the guidelines outlined on this page:
   https://developers.google.com/safe-browsing/developers_guide_v2#AcceptableUsage
 -->
 <!ENTITY  enableSafeBrowsing.label        "Block dangerous and deceptive content">
 <!ENTITY  enableSafeBrowsing.accesskey    "B">
 
@@ -21,18 +21,18 @@
 <!ENTITY  blockUncommonAndUnwanted.accesskey "C">
 
 <!ENTITY  addonExceptions.label         "Exceptions…">
 <!ENTITY  addonExceptions.accesskey     "E">
 
 
 <!ENTITY  logins.label                  "Logins">
 
-<!ENTITY  rememberLogins.label          "Remember logins for sites">
-<!ENTITY  rememberLogins.accesskey      "R">
+<!ENTITY  rememberLogins2.label         "Remember logins for websites">
+<!ENTITY  rememberLogins2.accesskey     "R">
 <!ENTITY  passwordExceptions.label      "Exceptions…">
 <!ENTITY  passwordExceptions.accesskey  "x">
 
 <!ENTITY  useMasterPassword.label        "Use a master password">
 <!ENTITY  useMasterPassword.accesskey    "U">
 <!ENTITY  changeMasterPassword.label     "Change Master Password…">
 <!ENTITY  changeMasterPassword.accesskey "M">
 
--- a/browser/locales/en-US/chrome/browser/preferences/content.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/content.dtd
@@ -3,17 +3,17 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <!ENTITY  permissions.label           "Permissions">
 
 <!ENTITY  blockPopups.label           "Block pop-up windows">
 <!ENTITY  blockPopups.accesskey       "B">
 
 <!ENTITY  notificationsPolicyLearnMore.label   "Learn more">
-<!ENTITY  notificationsPolicyDesc3.label       "Choose which sites are allowed to send you notifications">
+<!ENTITY  notificationsPolicyDesc4.label       "Choose which websites are allowed to send you notifications">
 <!ENTITY  notificationsPolicyButton.accesskey  "h">
 <!ENTITY  notificationsPolicyButton.label      "Choose…">
 <!ENTITY  notificationsDoNotDisturb.label      "Do not disturb me">
 <!ENTITY  notificationsDoNotDisturb.accesskey  "n">
 <!ENTITY  notificationsDoNotDisturbDetails.value "No notification will be shown until you restart &brandShortName;">
 
 <!ENTITY  popupExceptions.label       "Exceptions…">
 <!ENTITY  popupExceptions.accesskey   "E">
--- a/browser/locales/en-US/chrome/browser/preferences/permissions.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/permissions.dtd
@@ -1,21 +1,21 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <!ENTITY window.title                 "Exceptions">
 <!ENTITY window.width                 "45em">
 
-<!ENTITY treehead.sitename.label      "Site">
+<!ENTITY treehead.sitename2.label     "Website">
 <!ENTITY treehead.status.label        "Status">
-<!ENTITY removepermission.label       "Remove Site">
-<!ENTITY removepermission.accesskey   "R">
-<!ENTITY removeallpermissions.label   "Remove All Sites">
-<!ENTITY removeallpermissions.accesskey "e">
+<!ENTITY removepermission2.label      "Remove Website">
+<!ENTITY removepermission2.accesskey  "R">
+<!ENTITY removeallpermissions2.label  "Remove All Websites">
+<!ENTITY removeallpermissions2.accesskey "e">
 <!ENTITY address.label                "Address of website:">
 <!ENTITY address.accesskey            "d">
 <!ENTITY block.label                  "Block">
 <!ENTITY block.accesskey              "B">
 <!ENTITY session.label                "Allow for Session">
 <!ENTITY session.accesskey            "S">
 <!ENTITY allow.label                  "Allow">
 <!ENTITY allow.accesskey              "A">
--- a/browser/locales/en-US/chrome/browser/preferences/preferences.properties
+++ b/browser/locales/en-US/chrome/browser/preferences/preferences.properties
@@ -14,30 +14,30 @@ phishBeforeText=Selecting this option wi
 labelDefaultFont=Default (%S)
 
 veryLargeMinimumFontTitle=Large minimum font size
 veryLargeMinimumFontWarning=You have selected a very large minimum font size (more than 24 pixels). This may make it difficult or impossible to use some important configuration pages like this one.
 acceptVeryLargeMinimumFont=Keep my changes anyway
 
 #### Permissions Manager
 
-trackingprotectionpermissionstext=You have disabled Tracking Protection on these sites.
+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_title=Allowed Sites - Add-ons Installation
+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.
-popuppermissionstitle=Allowed Sites - Pop-ups
+popuppermissionstitle2=Allowed Websites - Pop-ups
 notificationspermissionstext4=Control which websites are always or never allowed to send you notifications. If you remove a site, it will need to request permission again.
 notificationspermissionstitle=Notification Permissions
 invalidURI=Please enter a valid hostname
 invalidURITitle=Invalid Hostname Entered
 savedLoginsExceptions_title=Exceptions - Saved Logins
-savedLoginsExceptions_desc=Logins for the following sites will not be saved:
+savedLoginsExceptions_desc2=Logins for the following websites will not be saved:
 
 #### Block List Manager
 
 blockliststext=You can choose which list Firefox will use to block Web elements that may track your browsing activity.
 blockliststitle=Block Lists
 # LOCALIZATION NOTE (mozNameTemplate): This template constructs the name of the
 # block list in the block lists dialog. It combines the list name and
 # description.
@@ -46,17 +46,17 @@ blockliststitle=Block Lists
 mozNameTemplate=%1$S %2$S
 # LOCALIZATION NOTE (mozstdName, etc.): These labels appear in the tracking
 # protection block lists dialog, mozNameTemplate is used to create the final
 # string. Note that in the future these two strings (name, desc) could be
 # displayed on two different lines.
 mozstdName=Disconnect.me basic protection (Recommended).
 mozstdDesc=Allows some trackers so websites function properly.
 mozfullName=Disconnect.me strict protection.
-mozfullDesc=Blocks known trackers. Some sites may not function properly.
+mozfullDesc2=Blocks known trackers. Some websites may not function properly.
 # LOCALIZATION NOTE (blocklistChangeRequiresRestart): %S = brandShortName
 blocklistChangeRequiresRestart=%S must restart to change block lists.
 
 #### Master Password
 
 pw_change2empty_in_fips_mode=You are currently in FIPS mode. FIPS requires a non-empty Master Password.
 pw_change_failed_title=Password Change Failed
 
@@ -186,18 +186,18 @@ actualAppCacheSize=Your application cach
 totalSiteDataSize=Your stored site data is currently using %1$S %2$S of disk space
 loadingSiteDataSize=Calculating site data size…
 clearSiteDataPromptTitle=Clear all cookies and site data
 clearSiteDataPromptText=Selecting ‘Clear Now’ will clear all cookies and site data stored by Firefox. This may sign you out of websites and remove offline web content.
 clearSiteDataNow=Clear Now
 persistent=Persistent
 siteUsage=%1$S %2$S
 acceptRemove=Remove
-# LOCALIZATION NOTE (siteDataSettings.description): %S = brandShortName
-siteDataSettings.description=The following websites store site data on your computer. %S keeps data from sites with persistent storage until you delete it, and deletes data from sites with non-persistent storage as space is needed.
+# LOCALIZATION NOTE (siteDataSettings2.description): %S = brandShortName
+siteDataSettings2.description=The following websites store site data on your computer. %S keeps data from websites with persistent storage until you delete it, and deletes data from websites with non-persistent storage as space is needed.
 # LOCALIZATION NOTE (removeAllSiteData, removeAllSiteDataShown):
 # removeAllSiteData and removeAllSiteDataShown are both used on the same one button,
 # never displayed together and can share the same accesskey.
 # When only partial sites are shown as a result of keyword search,
 # removeAllShown is displayed as button label.
 # removeAll is displayed when no keyword search and all sites are shown.
 removeAllSiteData.label=Remove All
 removeAllSiteData.accesskey=e
--- a/browser/locales/en-US/chrome/browser/preferences/privacy.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/privacy.dtd
@@ -41,18 +41,18 @@
 <!ENTITY  locbar.bookmarks.accesskey    "k">
 <!ENTITY  locbar.openpage.label         "Open tabs">
 <!ENTITY  locbar.openpage.accesskey     "O">
 <!ENTITY  locbar.searches.label         "Related searches from the default search engine">
 <!ENTITY  locbar.searches.accesskey     "d">
 
 <!ENTITY  suggestionSettings2.label     "Change preferences for search engine suggestions">
 
-<!ENTITY  acceptCookies.label           "Accept cookies from sites">
-<!ENTITY  acceptCookies.accesskey       "A">
+<!ENTITY  acceptCookies2.label          "Accept cookies from websites">
+<!ENTITY  acceptCookies2.accesskey      "A">
 
 <!ENTITY  acceptThirdParty.pre.label      "Accept third-party cookies:">
 <!ENTITY  acceptThirdParty.pre.accesskey  "y">
 <!ENTITY  acceptThirdParty.always.label   "Always">
 <!ENTITY  acceptThirdParty.never.label    "Never">
 <!ENTITY  acceptThirdParty.visited.label  "From visited">
 
 <!ENTITY  keepUntil.label               "Keep until:">
--- a/browser/locales/en-US/chrome/browser/preferences/security.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/security.dtd
@@ -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/. -->
 
 <!ENTITY  security.label                 "Security">
 <!ENTITY  phishingProtection.label       "Phishing Protection">
 
-<!ENTITY  warnOnAddonInstall.label        "Warn you when sites try to install add-ons">
-<!ENTITY  warnOnAddonInstall.accesskey    "W">
+<!ENTITY  warnOnAddonInstall2.label       "Warn you when websites try to install add-ons">
+<!ENTITY  warnOnAddonInstall2.accesskey   "W">
 
 <!-- LOCALIZATION NOTE (enableSafeBrowsing.label, blockDownloads.label, blockUncommonUnwanted.label):
   It is important that wording follows the guidelines outlined on this page:
   https://developers.google.com/safe-browsing/developers_guide_v2#AcceptableUsage
 -->
 <!ENTITY  enableSafeBrowsing.label        "Block dangerous and deceptive content">
 <!ENTITY  enableSafeBrowsing.accesskey    "B">
 
@@ -22,18 +22,18 @@
 <!ENTITY  blockUncommonAndUnwanted.accesskey "C">
 
 <!ENTITY  addonExceptions.label         "Exceptions…">
 <!ENTITY  addonExceptions.accesskey     "E">
 
 
 <!ENTITY  formsAndPasswords.label       "Forms &amp; Passwords">
 
-<!ENTITY  rememberLogins1.label          "Remember logins and passwords for sites">
-<!ENTITY  rememberLogins1.accesskey      "R">
+<!ENTITY  rememberLogins2.label         "Remember logins and passwords for websites">
+<!ENTITY  rememberLogins2.accesskey     "R">
 <!ENTITY  passwordExceptions.label      "Exceptions…">
 <!ENTITY  passwordExceptions.accesskey  "x">
 
 <!ENTITY  useMasterPassword.label        "Use a master password">
 <!ENTITY  useMasterPassword.accesskey    "U">
 <!ENTITY  changeMasterPassword.label     "Change Master Password…">
 <!ENTITY  changeMasterPassword.accesskey "M">
 
--- a/browser/locales/en-US/chrome/browser/preferences/translation.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/translation.dtd
@@ -9,16 +9,16 @@
 <!ENTITY noTranslationForLanguages.label  "Translation will not be offered for the following languages:">
 <!ENTITY treehead.languageName.label      "Languages">
 <!ENTITY removeLanguage.label             "Remove Language">
 <!ENTITY removeLanguage.accesskey         "R">
 <!ENTITY removeAllLanguages.label         "Remove All Languages">
 <!ENTITY removeAllLanguages.accesskey     "e">
 
 <!ENTITY noTranslationForSites.label      "Translation will not be offered for the following sites:">
-<!ENTITY treehead.siteName.label          "Sites">
+<!ENTITY treehead.siteName2.label         "Websites">
 <!ENTITY removeSite.label                 "Remove Site">
 <!ENTITY removeSite.accesskey             "S">
 <!ENTITY removeAllSites.label             "Remove All Sites">
 <!ENTITY removeAllSites.accesskey         "i">
 
 <!ENTITY button.close.label               "Close">
 <!ENTITY button.close.accesskey           "C">
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -695,27 +695,28 @@ groupbox {
 }
 
 .search-tooltip-parent {
   position: relative;
 }
 
 menulist[indicator=true] > menupopup menuitem:not([image]) > .menu-iconic-left {
   display: -moz-box;
+  min-width: auto; /* Override the min-width defined in menu.css */
+  margin-inline-end: 6px;
+}
+
+menulist[indicator=true] > menupopup menuitem:not([image]) > .menu-iconic-left > .menu-iconic-icon {
   width: 8px;
-  min-width: auto; /* Override the min-width defined in menu.css */
   height: 10px;
-  margin-inline-end: 6px;
+  margin: 0;
 }
 
 menulist[indicator=true] > menupopup menuitem[indicator=true]:not([image]) > .menu-iconic-left > .menu-iconic-icon {
   list-style-image: url(chrome://browser/skin/preferences/in-content-new/search-arrow-indicator.svg);
-  width: 8px;
-  height: 10px;
-  margin: 0;
 }
 
 menulist[indicator=true] > menupopup menuitem[indicator=true]:not([image]) > .menu-iconic-left > .menu-iconic-icon:-moz-locale-dir(rtl) {
   transform: scaleX(-1);
 }
 
 .menu-iconic-highlightable-text {
   margin: 0; /* Align with the margin of xul:label.menu-iconic-text */
--- a/devtools/shared/css/lexer.js
+++ b/devtools/shared/css/lexer.js
@@ -1061,24 +1061,26 @@ Scanner.prototype = {
    * the special lexical rules for URL tokens in a nonstandard context.
    */
   NextURL: function (aToken) {
     this.SkipWhitespace();
 
     // aToken.mIdent may be "url" at this point; clear that out
     aToken.mIdent.length = 0;
 
+    let hasString = false;
     let ch = this.Peek();
     // Do we have a string?
     if (ch == QUOTATION_MARK || ch == APOSTROPHE) {
       this.ScanString(aToken);
       if (aToken.mType == eCSSToken_Bad_String) {
         aToken.mType = eCSSToken_Bad_URL;
         return;
       }
+      hasString = true;
     } else {
       // Otherwise, this is the start of a non-quoted url (which may be empty).
       aToken.mSymbol = 0;
       this.GatherText(IS_URL_CHAR, aToken.mIdent);
     }
 
     // Consume trailing whitespace and then look for a close parenthesis.
     this.SkipWhitespace();
@@ -1087,16 +1089,35 @@ Scanner.prototype = {
     if (ch < 0 || ch == RIGHT_PARENTHESIS) {
       this.Advance();
       aToken.mType = eCSSToken_URL;
       if (ch < 0) {
         this.AddEOFCharacters(eEOFCharacters_CloseParen);
       }
     } else {
       aToken.mType = eCSSToken_Bad_URL;
+      if (!hasString) {
+        // Consume until before the next right parenthesis, which follows
+        // how <bad-url-token> is consumed in CSS Syntax 3 spec.
+        // Note that, we only do this when "url(" is not followed by a
+        // string, because in the spec, "url(" followed by a string is
+        // handled as a url function rather than a <url-token>, so the
+        // rest of content before ")" should be consumed in balance,
+        // which will be done by the parser.
+        // The closing ")" is not consumed here. It is left to the parser
+        // so that the parser can handle both cases.
+        do {
+          if (IsVertSpace(ch)) {
+            this.AdvanceLine();
+          } else {
+            this.Advance();
+          }
+          ch = this.Peek();
+        } while (ch >= 0 && ch != RIGHT_PARENTHESIS);
+      }
     }
   },
 
   /**
    * Primary scanner entry point.  Consume one token and fill in
    * |aToken| accordingly.  Will skip over any number of comments first,
    * and will also skip over rather than return whitespace and comment
    * tokens, depending on the value of |aSkip|.
--- a/devtools/shared/tests/unit/test_csslexer.js
+++ b/devtools/shared/tests/unit/test_csslexer.js
@@ -123,18 +123,17 @@ var LEX_TESTS = [
   ["23px", ["dimension:px"]],
   ["23%", ["percentage"]],
   ["url(http://example.com)", ["url:http://example.com"]],
   ["url('http://example.com')", ["url:http://example.com"]],
   ["url(  'http://example.com'  )",
              ["url:http://example.com"]],
   // In CSS Level 3, this is an ordinary URL, not a BAD_URL.
   ["url(http://example.com", ["url:http://example.com"]],
-  // See bug 1153981 to understand why this gets a SYMBOL token.
-  ["url(http://example.com @", ["bad_url:http://example.com", "symbol:@"]],
+  ["url(http://example.com @", ["bad_url:http://example.com"]],
   ["quo\\ting", ["ident:quoting"]],
   ["'bad string\n", ["bad_string:bad string", "whitespace"]],
   ["~=", ["includes"]],
   ["|=", ["dashmatch"]],
   ["^=", ["beginsmatch"]],
   ["$=", ["endsmatch"]],
   ["*=", ["containsmatch"]],
 
new file mode 100644
--- /dev/null
+++ b/dom/security/test/cors/browser.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+support-files =
+  file_CrossSiteXHR_server.sjs
+  file_CrossSiteXHR_inner.html
+  file_cors_logging_test.html
+  head.js
+
+[browser_CORS-console-warnings.js]
new file mode 100644
--- /dev/null
+++ b/dom/security/test/cors/browser_CORS-console-warnings.js
@@ -0,0 +1,93 @@
+/*
+ * Description of the test:
+ *   Ensure that CORS warnings are printed to the web console.
+ *
+ *   This test uses the same tests as the plain mochitest, but needs access to
+ *   the console.
+ */
+'use strict';
+
+function console_observer(subject, topic, data) {
+  var message = subject.wrappedJSObject.arguments[0];
+  ok(false, message);
+};
+
+var webconsole = null;
+var messages_seen = 0;
+var expected_messages = 50;
+
+function on_new_message(event, new_messages) {
+  for (let message of new_messages) {
+    let elem = message.node;
+    let text = elem.textContent;
+    if (text.match('Cross-Origin Request Blocked:')) {
+      ok(true, "message is: " + text);
+      messages_seen++;
+    }
+  }
+}
+
+function do_cleanup() {
+  if (webconsole) {
+    webconsole.ui.off("new-messages", on_new_message);
+  }
+  yield unsetCookiePref();
+}
+
+/**
+ * Set e10s related preferences in the test environment.
+ * @return {Promise} promise that resolves when preferences are set.
+ */
+function setCookiePref() {
+  return new Promise(resolve =>
+    // accept all cookies so that the CORS requests will send the right cookies
+    SpecialPowers.pushPrefEnv({
+      set: [
+        ["network.cookie.cookieBehavior", 0],
+      ]
+    }, resolve));
+}
+
+/**
+ * Unset e10s related preferences in the test environment.
+ * @return {Promise} promise that resolves when preferences are unset.
+ */
+function unsetCookiePref() {
+  return new Promise(resolve => {
+    SpecialPowers.popPrefEnv(resolve);
+  });
+}
+
+//jscs:disable
+add_task(function*() {
+  //jscs:enable
+  // A longer timeout is necessary for this test than the plain mochitests
+  // due to opening a new tab with the web console.
+  requestLongerTimeout(4);
+  registerCleanupFunction(do_cleanup);
+  yield setCookiePref();
+
+  let test_uri = "http://mochi.test:8888/browser/dom/security/test/cors/file_cors_logging_test.html";
+
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+
+  let toolbox = yield openToolboxForTab(tab, "webconsole");
+  ok(toolbox, "Got toolbox");
+  let hud = toolbox.getCurrentPanel().hud;
+  ok(hud, "Got hud");
+
+  if (!webconsole) {
+    registerCleanupFunction(do_cleanup);
+    hud.ui.on("new-messages", on_new_message);
+    webconsole = hud;
+  }
+
+  BrowserTestUtils.loadURI(gBrowser, test_uri);
+
+  yield BrowserTestUtils.waitForLocationChange(gBrowser, test_uri+"#finished");
+
+  // Different OS combinations
+  ok(messages_seen > 0, "Saw " + messages_seen + " messages.");
+
+  yield BrowserTestUtils.removeTab(tab);
+});
new file mode 100644
--- /dev/null
+++ b/dom/security/test/cors/file_cors_logging_test.html
@@ -0,0 +1,1311 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
+  <title>Test for Cross Site XMLHttpRequest</title>
+</head>
+<body onload="initTest()">
+<p id="display">
+<iframe id=loader></iframe>
+</p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript;version=1.8">
+
+const runPreflightTests = 1;
+const runCookieTests = 1;
+const runRedirectTests = 1;
+
+var gen;
+
+function initTest() {
+  window.addEventListener("message", function(e) {
+    gen.next(e.data);
+  });
+
+  gen = runTest();
+
+  gen.next()
+}
+
+function initTestCallback() {
+}
+
+function* runTest() {
+  var loader = document.getElementById('loader');
+  var loaderWindow = loader.contentWindow;
+  loader.onload = function () { gen.next() };
+
+  // Test preflight-less requests
+  basePath = "/browser/dom/security/test/cors/file_CrossSiteXHR_server.sjs?"
+  baseURL = "http://mochi.test:8888" + basePath;
+
+  // Test preflighted requests
+  loader.src = "http://example.org/browser/dom/security/test/cors/file_CrossSiteXHR_inner.html";
+  origin = "http://example.org";
+  yield undefined;
+
+  tests =     [// Plain request
+               { pass: 1,
+                 method: "GET",
+                 noAllowPreflight: 1,
+               },
+
+               // undefined username
+               { pass: 1,
+                 method: "GET",
+                 noAllowPreflight: 1,
+                 username: undefined
+               },
+
+               // undefined username and password
+               { pass: 1,
+                 method: "GET",
+                 noAllowPreflight: 1,
+                 username: undefined,
+                 password: undefined
+               },
+
+               // nonempty username
+               { pass: 0,
+                 method: "GET",
+                 noAllowPreflight: 1,
+                 username: "user",
+               },
+
+               // nonempty password
+               // XXXbz this passes for now, because we ignore passwords
+               // without usernames in most cases.
+               { pass: 1,
+                 method: "GET",
+                 noAllowPreflight: 1,
+                 password: "password",
+               },
+
+               // Default allowed headers
+               { pass: 1,
+                 method: "GET",
+                 headers: { "Content-Type": "text/plain",
+                            "Accept": "foo/bar",
+                            "Accept-Language": "sv-SE" },
+                 noAllowPreflight: 1,
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "Content-Type": "foo/bar",
+                            "Accept": "foo/bar",
+                            "Accept-Language": "sv-SE" },
+                 noAllowPreflight: 1,
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "Content-Type": "foo/bar, text/plain" },
+                 noAllowPreflight: 1,
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "Content-Type": "foo/bar, text/plain, garbage" },
+                 noAllowPreflight: 1,
+               },
+
+               // Custom headers
+               { pass: 1,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue" },
+                 allowHeaders: "x-my-header",
+               },
+               { pass: 1,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue" },
+                 allowHeaders: "X-My-Header",
+               },
+               { pass: 1,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue",
+                            "long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header": "secondValue" },
+                 allowHeaders: "x-my-header, long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header",
+               },
+               { pass: 1,
+                 method: "GET",
+                 headers: { "x-my%-header": "myValue" },
+                 allowHeaders: "x-my%-header",
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue" },
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "x-my-header": "" },
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue" },
+                 allowHeaders: "",
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue" },
+                 allowHeaders: "y-my-header",
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue" },
+                 allowHeaders: "x-my-header y-my-header",
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue" },
+                 allowHeaders: "x-my-header, y-my-header z",
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue" },
+                 allowHeaders: "x-my-header, y-my-he(ader",
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "myheader": "" },
+                 allowMethods: "myheader",
+               },
+               { pass: 1,
+                 method: "GET",
+                 headers: { "User-Agent": "myValue" },
+                 allowHeaders: "User-Agent",
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "User-Agent": "myValue" },
+               },
+
+               // Multiple custom headers
+               { pass: 1,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue",
+                            "second-header": "secondValue",
+                            "third-header": "thirdValue" },
+                 allowHeaders: "x-my-header, second-header, third-header",
+               },
+               { pass: 1,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue",
+                            "second-header": "secondValue",
+                            "third-header": "thirdValue" },
+                 allowHeaders: "x-my-header,second-header,third-header",
+               },
+               { pass: 1,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue",
+                            "second-header": "secondValue",
+                            "third-header": "thirdValue" },
+                 allowHeaders: "x-my-header ,second-header ,third-header",
+               },
+               { pass: 1,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue",
+                            "second-header": "secondValue",
+                            "third-header": "thirdValue" },
+                 allowHeaders: "x-my-header , second-header , third-header",
+               },
+               { pass: 1,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue",
+                            "second-header": "secondValue" },
+                 allowHeaders: ",  x-my-header, , ,, second-header, ,   ",
+               },
+               { pass: 1,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue",
+                            "second-header": "secondValue" },
+                 allowHeaders: "x-my-header, second-header, unused-header",
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue",
+                            "y-my-header": "secondValue" },
+                 allowHeaders: "x-my-header",
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "x-my-header": "",
+                            "y-my-header": "" },
+                 allowHeaders: "x-my-header",
+               },
+
+               // HEAD requests
+               { pass: 1,
+                 method: "HEAD",
+                 noAllowPreflight: 1,
+               },
+
+               // HEAD with safe headers
+               { pass: 1,
+                 method: "HEAD",
+                 headers: { "Content-Type": "text/plain",
+                            "Accept": "foo/bar",
+                            "Accept-Language": "sv-SE" },
+                 noAllowPreflight: 1,
+               },
+               { pass: 0,
+                 method: "HEAD",
+                 headers: { "Content-Type": "foo/bar",
+                            "Accept": "foo/bar",
+                            "Accept-Language": "sv-SE" },
+                 noAllowPreflight: 1,
+               },
+               { pass: 0,
+                 method: "HEAD",
+                 headers: { "Content-Type": "foo/bar, text/plain" },
+                 noAllowPreflight: 1,
+               },
+               { pass: 0,
+                 method: "HEAD",
+                 headers: { "Content-Type": "foo/bar, text/plain, garbage" },
+                 noAllowPreflight: 1,
+               },
+
+               // HEAD with custom headers
+               { pass: 1,
+                 method: "HEAD",
+                 headers: { "x-my-header": "myValue" },
+                 allowHeaders: "x-my-header",
+               },
+               { pass: 0,
+                 method: "HEAD",
+                 headers: { "x-my-header": "myValue" },
+               },
+               { pass: 0,
+                 method: "HEAD",
+                 headers: { "x-my-header": "myValue" },
+                 allowHeaders: "",
+               },
+               { pass: 0,
+                 method: "HEAD",
+                 headers: { "x-my-header": "myValue" },
+                 allowHeaders: "y-my-header",
+               },
+               { pass: 0,
+                 method: "HEAD",
+                 headers: { "x-my-header": "myValue" },
+                 allowHeaders: "x-my-header y-my-header",
+               },
+
+               // POST tests
+               { pass: 1,
+                 method: "POST",
+                 body: "hi there",
+                 noAllowPreflight: 1,
+               },
+               { pass: 1,
+                 method: "POST",
+               },
+               { pass: 1,
+                 method: "POST",
+                 noAllowPreflight: 1,
+               },
+
+               // POST with standard headers
+               { pass: 1,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Content-Type": "text/plain" },
+                 noAllowPreflight: 1,
+               },
+               { pass: 1,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Content-Type": "multipart/form-data" },
+                 noAllowPreflight: 1,
+               },
+               { pass: 1,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Content-Type": "application/x-www-form-urlencoded" },
+                 noAllowPreflight: 1,
+               },
+               { pass: 0,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Content-Type": "foo/bar" },
+               },
+               { pass: 0,
+                 method: "POST",
+                 headers: { "Content-Type": "foo/bar" },
+               },
+               { pass: 1,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Content-Type": "text/plain",
+                            "Accept": "foo/bar",
+                            "Accept-Language": "sv-SE" },
+                 noAllowPreflight: 1,
+               },
+               { pass: 0,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Content-Type": "foo/bar, text/plain" },
+                 noAllowPreflight: 1,
+               },
+               { pass: 0,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Content-Type": "foo/bar, text/plain, garbage" },
+                 noAllowPreflight: 1,
+               },
+
+               // POST with custom headers
+               { pass: 1,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Accept": "foo/bar",
+                            "Accept-Language": "sv-SE",
+                            "x-my-header": "myValue" },
+                 allowHeaders: "x-my-header",
+               },
+               { pass: 1,
+                 method: "POST",
+                 headers: { "Content-Type": "text/plain",
+                            "x-my-header": "myValue" },
+                 allowHeaders: "x-my-header",
+               },
+               { pass: 1,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Content-Type": "text/plain",
+                            "x-my-header": "myValue" },
+                 allowHeaders: "x-my-header",
+               },
+               { pass: 1,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Content-Type": "foo/bar",
+                            "x-my-header": "myValue" },
+                 allowHeaders: "x-my-header, content-type",
+               },
+               { pass: 0,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Content-Type": "foo/bar" },
+                 noAllowPreflight: 1,
+               },
+               { pass: 0,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Content-Type": "foo/bar",
+                            "x-my-header": "myValue" },
+                 allowHeaders: "x-my-header",
+               },
+               { pass: 1,
+                 method: "POST",
+                 headers: { "x-my-header": "myValue" },
+                 allowHeaders: "x-my-header",
+               },
+               { pass: 1,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "x-my-header": "myValue" },
+                 allowHeaders: "x-my-header, $_%",
+               },
+
+               // Other methods
+               { pass: 1,
+                 method: "DELETE",
+                 allowMethods: "DELETE",
+               },
+               { pass: 0,
+                 method: "DELETE",
+                 allowHeaders: "DELETE",
+               },
+               { pass: 0,
+                 method: "DELETE",
+               },
+               { pass: 0,
+                 method: "DELETE",
+                 allowMethods: "",
+               },
+               { pass: 1,
+                 method: "DELETE",
+                 allowMethods: "POST, PUT, DELETE",
+               },
+               { pass: 1,
+                 method: "DELETE",
+                 allowMethods: "POST, DELETE, PUT",
+               },
+               { pass: 1,
+                 method: "DELETE",
+                 allowMethods: "DELETE, POST, PUT",
+               },
+               { pass: 1,
+                 method: "DELETE",
+                 allowMethods: "POST ,PUT ,DELETE",
+               },
+               { pass: 1,
+                 method: "DELETE",
+                 allowMethods: "POST,PUT,DELETE",
+               },
+               { pass: 1,
+                 method: "DELETE",
+                 allowMethods: "POST , PUT , DELETE",
+               },
+               { pass: 1,
+                 method: "DELETE",
+                 allowMethods: "  ,,  PUT ,,  ,    , DELETE  ,  ,",
+               },
+               { pass: 0,
+                 method: "DELETE",
+                 allowMethods: "PUT",
+               },
+               { pass: 0,
+                 method: "DELETE",
+                 allowMethods: "DELETEZ",
+               },
+               { pass: 0,
+                 method: "DELETE",
+                 allowMethods: "DELETE PUT",
+               },
+               { pass: 0,
+                 method: "DELETE",
+                 allowMethods: "DELETE, PUT Z",
+               },
+               { pass: 0,
+                 method: "DELETE",
+                 allowMethods: "DELETE, PU(T",
+               },
+               { pass: 0,
+                 method: "DELETE",
+                 allowMethods: "PUT DELETE",
+               },
+               { pass: 0,
+                 method: "DELETE",
+                 allowMethods: "PUT Z, DELETE",
+               },
+               { pass: 0,
+                 method: "DELETE",
+                 allowMethods: "PU(T, DELETE",
+               },
+               { pass: 0,
+                 method: "MYMETHOD",
+                 allowMethods: "myMethod",
+               },
+               { pass: 0,
+                 method: "PUT",
+                 allowMethods: "put",
+               },
+
+               // Progress events
+               { pass: 1,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Content-Type": "text/plain" },
+                 uploadProgress: "progress",
+               },
+               { pass: 0,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Content-Type": "text/plain" },
+                 uploadProgress: "progress",
+                 noAllowPreflight: 1,
+               },
+
+               // Status messages
+               { pass: 1,
+                 method: "GET",
+                 noAllowPreflight: 1,
+                 status: 404,
+                 statusMessage: "nothin' here",
+               },
+               { pass: 1,
+                 method: "GET",
+                 noAllowPreflight: 1,
+                 status: 401,
+                 statusMessage: "no can do",
+               },
+               { pass: 1,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Content-Type": "foo/bar" },
+                 allowHeaders: "content-type",
+                 status: 500,
+                 statusMessage: "server boo",
+               },
+               { pass: 1,
+                 method: "GET",
+                 noAllowPreflight: 1,
+                 status: 200,
+                 statusMessage: "Yes!!",
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "x-my-header": "header value" },
+                 allowHeaders: "x-my-header",
+                 preflightStatus: 400
+               },
+               { pass: 1,
+                 method: "GET",
+                 headers: { "x-my-header": "header value" },
+                 allowHeaders: "x-my-header",
+                 preflightStatus: 200
+               },
+               { pass: 1,
+                 method: "GET",
+                 headers: { "x-my-header": "header value" },
+                 allowHeaders: "x-my-header",
+                 preflightStatus: 204
+               },
+
+               // exposed headers
+               { pass: 1,
+                 method: "GET",
+                 responseHeaders: { "x-my-header": "x header" },
+                 exposeHeaders: "x-my-header",
+                 expectedResponseHeaders: ["x-my-header"],
+               },
+               { pass: 0,
+                 method: "GET",
+                 origin: "http://invalid",
+                 responseHeaders: { "x-my-header": "x header" },
+                 exposeHeaders: "x-my-header",
+                 expectedResponseHeaders: [],
+               },
+               { pass: 1,
+                 method: "GET",
+                 responseHeaders: { "x-my-header": "x header" },
+                 expectedResponseHeaders: [],
+               },
+               { pass: 1,
+                 method: "GET",
+                 responseHeaders: { "x-my-header": "x header" },
+                 exposeHeaders: "x-my-header y",
+                 expectedResponseHeaders: [],
+               },
+               { pass: 1,
+                 method: "GET",
+                 responseHeaders: { "x-my-header": "x header" },
+                 exposeHeaders: "y x-my-header",
+                 expectedResponseHeaders: [],
+               },
+               { pass: 1,
+                 method: "GET",
+                 responseHeaders: { "x-my-header": "x header" },
+                 exposeHeaders: "x-my-header, y-my-header z",
+                 expectedResponseHeaders: [],
+               },
+               { pass: 1,
+                 method: "GET",
+                 responseHeaders: { "x-my-header": "x header" },
+                 exposeHeaders: "x-my-header, y-my-hea(er",
+                 expectedResponseHeaders: [],
+               },
+               { pass: 1,
+                 method: "GET",
+                 responseHeaders: { "x-my-header": "x header",
+                                    "y-my-header": "y header" },
+                 exposeHeaders: "  ,  ,,y-my-header,z-my-header,  ",
+                 expectedResponseHeaders: ["y-my-header"],
+               },
+               { pass: 1,
+                 method: "GET",
+                 responseHeaders: { "Cache-Control": "cacheControl header",
+                                    "Content-Language": "contentLanguage header",
+                                    "Expires":"expires header",
+                                    "Last-Modified":"lastModified header",
+                                    "Pragma":"pragma header",
+                                    "Unexpected":"unexpected header" },
+                 expectedResponseHeaders: ["Cache-Control","Content-Language","Content-Type","Expires","Last-Modified","Pragma"],
+               },
+               // Check that sending a body in the OPTIONS response works
+               { pass: 1,
+                 method: "DELETE",
+                 allowMethods: "DELETE",
+                 preflightBody: "I'm a preflight response body",
+               },
+               ];
+
+  if (!runPreflightTests) {
+    tests = [];
+  }
+
+  for (test of tests) {
+    var req = {
+      url: baseURL + "allowOrigin=" + escape(test.origin || origin),
+      method: test.method,
+      headers: test.headers,
+      uploadProgress: test.uploadProgress,
+      body: test.body,
+      responseHeaders: test.responseHeaders,
+    };
+
+    if (test.pass) {
+       req.url += "&origin=" + escape(origin) +
+                  "&requestMethod=" + test.method;
+    }
+
+    if ("username" in test) {
+      req.username = test.username;
+    }
+
+    if ("password" in test) {
+      req.password = test.password;
+    }
+
+    if (test.noAllowPreflight)
+      req.url += "&noAllowPreflight";
+
+    if (test.pass && "headers" in test) {
+      function isUnsafeHeader(name) {
+        lName = name.toLowerCase();
+        return lName != "accept" &&
+               lName != "accept-language" &&
+               (lName != "content-type" ||
+                ["text/plain",
+                 "multipart/form-data",
+                 "application/x-www-form-urlencoded"]
+                   .indexOf(test.headers[name].toLowerCase()) == -1);
+      }
+      req.url += "&headers=" + escape(test.headers.toSource());
+      reqHeaders =
+        escape(Object.keys(test.headers)
+               .filter(isUnsafeHeader)
+               .map(s => s.toLowerCase())
+               .sort()
+               .join(","));
+      req.url += reqHeaders ? "&requestHeaders=" + reqHeaders : "";
+    }
+    if ("allowHeaders" in test)
+      req.url += "&allowHeaders=" + escape(test.allowHeaders);
+    if ("allowMethods" in test)
+      req.url += "&allowMethods=" + escape(test.allowMethods);
+    if (test.body)
+      req.url += "&body=" + escape(test.body);
+    if (test.status) {
+      req.url += "&status=" + test.status;
+      req.url += "&statusMessage=" + escape(test.statusMessage);
+    }
+    if (test.preflightStatus)
+      req.url += "&preflightStatus=" + test.preflightStatus;
+    if (test.responseHeaders)
+      req.url += "&responseHeaders=" + escape(test.responseHeaders.toSource());
+    if (test.exposeHeaders)
+      req.url += "&exposeHeaders=" + escape(test.exposeHeaders);
+    if (test.preflightBody)
+      req.url += "&preflightBody=" + escape(test.preflightBody);
+
+    loaderWindow.postMessage(req.toSource(), origin);
+    res = eval(yield);
+  }
+
+  // Test cookie behavior
+  tests = [{ pass: 1,
+             method: "GET",
+             withCred: 1,
+             allowCred: 1,
+           },
+           { pass: 0,
+             method: "GET",
+             withCred: 1,
+             allowCred: 0,
+           },
+           { pass: 0,
+             method: "GET",
+             withCred: 1,
+             allowCred: 1,
+             origin: "*",
+           },
+           { pass: 1,
+             method: "GET",
+             withCred: 0,
+             allowCred: 1,
+             origin: "*",
+           },
+           { pass: 1,
+             method: "GET",
+             setCookie: "a=1",
+             withCred: 1,
+             allowCred: 1,
+           },
+           { pass: 1,
+             method: "GET",
+             cookie: "a=1",
+             withCred: 1,
+             allowCred: 1,
+           },
+           { pass: 1,
+             method: "GET",
+             noCookie: 1,
+             withCred: 0,
+             allowCred: 1,
+           },
+           { pass: 0,
+             method: "GET",
+             noCookie: 1,
+             withCred: 1,
+             allowCred: 1,
+           },
+           { pass: 1,
+             method: "GET",
+             setCookie: "a=2",
+             withCred: 0,
+             allowCred: 1,
+           },
+           { pass: 1,
+             method: "GET",
+             cookie: "a=1",
+             withCred: 1,
+             allowCred: 1,
+           },
+           { pass: 1,
+             method: "GET",
+             setCookie: "a=2",
+             withCred: 1,
+             allowCred: 1,
+           },
+           { pass: 1,
+             method: "GET",
+             cookie: "a=2",
+             withCred: 1,
+             allowCred: 1,
+           },
+           ];
+
+  if (!runCookieTests) {
+    tests = [];
+  }
+
+  for (test of tests) {
+    req = {
+      url: baseURL + "allowOrigin=" + escape(test.origin || origin),
+      method: test.method,
+      headers: test.headers,
+      withCred: test.withCred,
+    };
+
+    if (test.allowCred)
+      req.url += "&allowCred";
+
+    if (test.setCookie)
+      req.url += "&setCookie=" + escape(test.setCookie);
+    if (test.cookie)
+      req.url += "&cookie=" + escape(test.cookie);
+    if (test.noCookie)
+      req.url += "&noCookie";
+
+    if ("allowHeaders" in test)
+      req.url += "&allowHeaders=" + escape(test.allowHeaders);
+    if ("allowMethods" in test)
+      req.url += "&allowMethods=" + escape(test.allowMethods);
+
+    loaderWindow.postMessage(req.toSource(), origin);
+
+    res = eval(yield);
+  }
+
+  // Make sure to clear cookies to avoid affecting other tests
+  document.cookie = "a=; path=/; expires=Thu, 01-Jan-1970 00:00:01 GMT"
+
+  // Test redirects
+
+  tests = [{ pass: 1,
+             method: "GET",
+             hops: [{ server: "http://example.com",
+                      allowOrigin: origin
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "GET",
+             hops: [{ server: "http://example.com",
+                      allowOrigin: origin
+                    },
+                    { server: "http://example.org",
+                      allowOrigin: origin
+                    },
+                    ],
+           },
+           { pass: 1,
+             method: "GET",
+             hops: [{ server: "http://example.com",
+                      allowOrigin: origin
+                    },
+                    { server: "http://example.org",
+                      allowOrigin: "*"
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "GET",
+             hops: [{ server: "http://example.com",
+                      allowOrigin: origin
+                    },
+                    { server: "http://example.org",
+                    },
+                    ],
+           },
+           { pass: 1,
+             method: "GET",
+             hops: [{ server: "http://example.org",
+                    },
+                    { server: "http://example.org",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "GET",
+             hops: [{ server: "http://example.org",
+                    },
+                    { server: "http://example.org",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin
+                    },
+                    { server: "http://example.org",
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "GET",
+             hops: [{ server: "http://example.com",
+                      allowOrigin: origin
+                    },
+                    { server: "http://test2.example.org:8000",
+                      allowOrigin: origin
+                    },
+                    { server: "http://sub2.xn--lt-uia.example.org",
+                      allowOrigin: origin
+                    },
+                    { server: "http://sub1.test1.example.org",
+                      allowOrigin: origin
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "GET",
+             hops: [{ server: "http://example.com",
+                      allowOrigin: origin
+                    },
+                    { server: "http://test2.example.org:8000",
+                      allowOrigin: origin
+                    },
+                    { server: "http://sub2.xn--lt-uia.example.org",
+                      allowOrigin: "*"
+                    },
+                    { server: "http://sub1.test1.example.org",
+                      allowOrigin: "*"
+                    },
+                    ],
+           },
+           { pass: 1,
+             method: "GET",
+             hops: [{ server: "http://example.com",
+                      allowOrigin: origin
+                    },
+                    { server: "http://test2.example.org:8000",
+                      allowOrigin: "*"
+                    },
+                    { server: "http://sub2.xn--lt-uia.example.org",
+                      allowOrigin: "*"
+                    },
+                    { server: "http://sub1.test1.example.org",
+                      allowOrigin: "*"
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "GET",
+             hops: [{ server: "http://example.com",
+                      allowOrigin: origin
+                    },
+                    { server: "http://test2.example.org:8000",
+                      allowOrigin: origin
+                    },
+                    { server: "http://sub2.xn--lt-uia.example.org",
+                      allowOrigin: "x"
+                    },
+                    { server: "http://sub1.test1.example.org",
+                      allowOrigin: origin
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "GET",
+             hops: [{ server: "http://example.com",
+                      allowOrigin: origin
+                    },
+                    { server: "http://test2.example.org:8000",
+                      allowOrigin: origin
+                    },
+                    { server: "http://sub2.xn--lt-uia.example.org",
+                      allowOrigin: "*"
+                    },
+                    { server: "http://sub1.test1.example.org",
+                      allowOrigin: origin
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "GET",
+             hops: [{ server: "http://example.com",
+                      allowOrigin: origin
+                    },
+                    { server: "http://test2.example.org:8000",
+                      allowOrigin: origin
+                    },
+                    { server: "http://sub2.xn--lt-uia.example.org",
+                      allowOrigin: "*"
+                    },
+                    { server: "http://sub1.test1.example.org",
+                    },
+                    ],
+           },
+           { pass: 1,
+             method: "POST",
+             body: "hi there",
+             headers: { "Content-Type": "text/plain" },
+             hops: [{ server: "http://example.org",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                    },
+                    ],
+           },
+           { pass: 1,
+             method: "POST",
+             body: "hi there",
+             headers: { "Content-Type": "text/plain",
+                        "my-header": "myValue",
+                      },
+             hops: [{ server: "http://example.org",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      allowHeaders: "my-header",
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "POST",
+             body: "hi there",
+             headers: { "Content-Type": "text/plain",
+                        "my-header": "myValue",
+                      },
+             hops: [{ server: "http://example.org",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      allowHeaders: "my-header",
+                    },
+                    { server: "http://sub1.test1.example.org",
+                      allowOrigin: origin,
+                      allowHeaders: "my-header",
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "POST",
+             body: "hi there",
+             headers: { "Content-Type": "text/plain",
+                        "my-header": "myValue",
+                      },
+             hops: [{ server: "http://example.org",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      allowHeaders: "my-header",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      allowHeaders: "my-header",
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "POST",
+             body: "hi there",
+             headers: { "Content-Type": "text/plain",
+                        "my-header": "myValue",
+                      },
+             hops: [{ server: "http://example.org",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      allowHeaders: "my-header",
+                    },
+                    { server: "http://example.org",
+                      allowOrigin: origin,
+                      allowHeaders: "my-header",
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "POST",
+             body: "hi there",
+             headers: { "Content-Type": "text/plain",
+                        "my-header": "myValue",
+                      },
+             hops: [{ server: "http://example.org",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      noAllowPreflight: 1,
+                    },
+                    ],
+           },
+           { pass: 1,
+             method: "DELETE",
+             hops: [{ server: "http://example.org",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      allowMethods: "DELETE",
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "DELETE",
+             hops: [{ server: "http://example.org",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      allowMethods: "DELETE",
+                    },
+                    { server: "http://sub1.test1.example.org",
+                      allowOrigin: origin,
+                      allowMethods: "DELETE",
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "DELETE",
+             hops: [{ server: "http://example.org",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      allowMethods: "DELETE",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      allowMethods: "DELETE",
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "DELETE",
+             hops: [{ server: "http://example.org",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      allowMethods: "DELETE",
+                    },
+                    { server: "http://example.org",
+                      allowOrigin: origin,
+                      allowMethods: "DELETE",
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "DELETE",
+             hops: [{ server: "http://example.org",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      allowMethods: "DELETE",
+                      noAllowPreflight: 1,
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "POST",
+             body: "hi there",
+             headers: { "Content-Type": "text/plain",
+                        "my-header": "myValue",
+                      },
+             hops: [{ server: "http://example.com",
+                      allowOrigin: origin,
+                    },
+                    { server: "http://sub1.test1.example.org",
+                      allowOrigin: origin,
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "DELETE",
+             hops: [{ server: "http://example.com",
+                      allowOrigin: origin,
+                    },
+                    { server: "http://sub1.test1.example.org",
+                      allowOrigin: origin,
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "POST",
+             body: "hi there",
+             headers: { "Content-Type": "text/plain",
+                        "my-header": "myValue",
+                      },
+             hops: [{ server: "http://example.com",
+                    },
+                    { server: "http://sub1.test1.example.org",
+                      allowOrigin: origin,
+                      allowHeaders: "my-header",
+                    },
+                    ],
+           },
+           { pass: 1,
+             method: "POST",
+             body: "hi there",
+             headers: { "Content-Type": "text/plain" },
+             hops: [{ server: "http://example.org",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "POST",
+             body: "hi there",
+             headers: { "Content-Type": "text/plain",
+                        "my-header": "myValue",
+                      },
+             hops: [{ server: "http://example.com",
+                      allowOrigin: origin,
+                      allowHeaders: "my-header",
+                    },
+                    { server: "http://example.org",
+                      allowOrigin: origin,
+                      allowHeaders: "my-header",
+                    },
+                    ],
+           },
+
+           // test redirects with different credentials settings
+           {
+             // Initialize by setting a cookies for same- and cross- origins.
+             pass: 1,
+             method: "GET",
+             hops: [{ server: origin,
+                      setCookie: escape("a=1"),
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      allowCred: 1,
+                      setCookie: escape("a=2"),
+                    },
+                    ],
+             withCred: 1,
+           },
+           { pass: 1,
+             method: "GET",
+             hops: [{ server: origin,
+                      cookie: escape("a=1"),
+                    },
+                    { server: origin,
+                      cookie: escape("a=1"),
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      noCookie: 1,
+                    },
+                    ],
+             withCred: 0,
+           },
+           { pass: 1,
+             method: "GET",
+             hops: [{ server: origin,
+                      cookie: escape("a=1"),
+                    },
+                    { server: origin,
+                      cookie: escape("a=1"),
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      allowCred: 1,
+                      cookie: escape("a=2"),
+                    },
+                    ],
+             withCred: 1,
+           },
+           // expected fail because allow-credentials CORS header is not set
+           { pass: 0,
+             method: "GET",
+             hops: [{ server: origin,
+                      cookie: escape("a=1"),
+                    },
+                    { server: origin,
+                      cookie: escape("a=1"),
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      cookie: escape("a=2"),
+                    },
+                    ],
+             withCred: 1,
+           },
+           { pass: 1,
+             method: "GET",
+             hops: [{ server: origin,
+                      cookie: escape("a=1"),
+                    },
+                    { server: origin,
+                      cookie: escape("a=1"),
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: '*',
+                      noCookie: 1,
+                    },
+                    ],
+             withCred: 0,
+           },
+           { pass: 0,
+             method: "GET",
+             hops: [{ server: origin,
+                      cookie: escape("a=1"),
+                    },
+                    { server: origin,
+                      cookie: escape("a=1"),
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: '*',
+                      allowCred: 1,
+                      cookie: escape("a=2"),
+                    },
+                    ],
+             withCred: 1,
+           },
+           ];
+
+  if (!runRedirectTests) {
+    tests = [];
+  }
+
+  for (test of tests) {
+    req = {
+      url: test.hops[0].server + basePath + "hop=1&hops=" +
+           escape(test.hops.toSource()),
+      method: test.method,
+      headers: test.headers,
+      body: test.body,
+      withCred: test.withCred,
+    };
+
+    if (test.pass) {
+      if (test.body)
+        req.url += "&body=" + escape(test.body);
+    }
+
+    loaderWindow.postMessage(req.toSource(), origin);
+
+    res = eval(yield);
+  }
+
+  document.location.href += "#finished";
+}
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/dom/security/test/cors/head.js
@@ -0,0 +1,69 @@
+'use strict';
+
+var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+function scopedCuImport(path) {
+  const scope = {};
+  Cu.import(path, scope);
+  return scope;
+}
+const {loader, require} = scopedCuImport("resource://devtools/shared/Loader.jsm");
+const {TargetFactory} = require("devtools/client/framework/target");
+const {Utils: WebConsoleUtils} =
+  require("devtools/client/webconsole/utils");
+let { gDevTools } = require("devtools/client/framework/devtools");
+loader.lazyGetter(this, "HUDService", () => require("devtools/client/webconsole/webconsole"));
+loader.lazyGetter(this, "HUDService", () => require("devtools/client/webconsole/hudservice"));
+let promise = require("promise");
+
+/**
+ * Open the toolbox in a given tab.
+ * @param {XULNode} tab The tab the toolbox should be opened in.
+ * @param {String} toolId Optional. The ID of the tool to be selected.
+ * @param {String} hostType Optional. The type of toolbox host to be used.
+ * @return {Promise} Resolves with the toolbox, when it has been opened.
+ */
+var openToolboxForTab = Task.async(function* (tab, toolId, hostType) {
+  info("Opening the toolbox");
+
+  let toolbox;
+  let target = TargetFactory.forTab(tab);
+  yield target.makeRemote();
+
+  // Check if the toolbox is already loaded.
+  toolbox = gDevTools.getToolbox(target);
+  if (toolbox) {
+    if (!toolId || (toolId && toolbox.getPanel(toolId))) {
+      info("Toolbox is already opened");
+      return toolbox;
+    }
+  }
+
+  // If not, load it now.
+  toolbox = yield gDevTools.showToolbox(target, toolId, hostType);
+
+  // Make sure that the toolbox frame is focused.
+  yield new Promise(resolve => waitForFocus(resolve, toolbox.win));
+
+  info("Toolbox opened and focused");
+
+  return toolbox;
+});
+
+/**
+ * Find multiple messages in the output.
+ *
+ * @param object hud
+ *        The web console.
+ * @param string text
+ *        A substring that can be found in the message.
+ * @param selector [optional]
+ *        The selector to use in finding the message.
+ */
+function findMessages(hud, text, selector = ".message") {
+  const messages = hud.ui.experimentalOutputNode.querySelectorAll(selector);
+  const elements = Array.prototype.filter.call(
+    messages,
+    (el) => el.textContent.includes(text)
+  );
+  return elements;
+}
--- a/dom/security/test/moz.build
+++ b/dom/security/test/moz.build
@@ -23,12 +23,13 @@ MOCHITEST_MANIFESTS += [
     'sri/mochitest.ini',
 ]
 
 MOCHITEST_CHROME_MANIFESTS += [
     'general/chrome.ini',
 ]
 
 BROWSER_CHROME_MANIFESTS += [
+    'cors/browser.ini',
     'csp/browser.ini',
     'general/browser.ini',
     'hsts/browser.ini',
 ]
--- a/gfx/layers/ipc/CompositorBridgeChild.cpp
+++ b/gfx/layers/ipc/CompositorBridgeChild.cpp
@@ -1086,17 +1086,17 @@ CompositorBridgeChild::WillEndTransactio
 {
   ResetShmemCounter();
 }
 
 PWebRenderBridgeChild*
 CompositorBridgeChild::AllocPWebRenderBridgeChild(const wr::PipelineId& aPipelineId,
                                                   const LayoutDeviceIntSize&,
                                                   TextureFactoryIdentifier*,
-                                                  uint32_t *aIdNamespace)
+                                                  wr::IdNamespace *aIdNamespace)
 {
   WebRenderBridgeChild* child = new WebRenderBridgeChild(aPipelineId);
   child->AddIPDLReference();
   return child;
 }
 
 bool
 CompositorBridgeChild::DeallocPWebRenderBridgeChild(PWebRenderBridgeChild* aActor)
--- a/gfx/layers/ipc/CompositorBridgeChild.h
+++ b/gfx/layers/ipc/CompositorBridgeChild.h
@@ -205,17 +205,17 @@ public:
   PAPZChild* AllocPAPZChild(const uint64_t& aLayersId) override;
   bool DeallocPAPZChild(PAPZChild* aActor) override;
 
   void WillEndTransaction();
 
   PWebRenderBridgeChild* AllocPWebRenderBridgeChild(const wr::PipelineId& aPipelineId,
                                                     const LayoutDeviceIntSize&,
                                                     TextureFactoryIdentifier*,
-                                                    uint32_t*) override;
+                                                    wr::IdNamespace*) override;
   bool DeallocPWebRenderBridgeChild(PWebRenderBridgeChild* aActor) override;
 
   uint64_t DeviceResetSequenceNumber() const {
     return mDeviceResetSequenceNumber;
   }
 
   wr::MaybeExternalImageId GetNextExternalImageId() override;
 
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -1673,17 +1673,17 @@ CompositorBridgeParent::RecvAdoptChild(c
   }
   return IPC_OK();
 }
 
 PWebRenderBridgeParent*
 CompositorBridgeParent::AllocPWebRenderBridgeParent(const wr::PipelineId& aPipelineId,
                                                     const LayoutDeviceIntSize& aSize,
                                                     TextureFactoryIdentifier* aTextureFactoryIdentifier,
-                                                    uint32_t* aIdNamespace)
+                                                    wr::IdNamespace* aIdNamespace)
 {
 #ifndef MOZ_BUILD_WEBRENDER
   // Extra guard since this in the parent process and we don't want a malicious
   // child process invoking this codepath before it's ready
   MOZ_RELEASE_ASSERT(false);
 #endif
   MOZ_ASSERT(wr::AsUint64(aPipelineId) == mRootLayerTreeID);
   MOZ_ASSERT(!mWrBridge);
@@ -1694,25 +1694,24 @@ CompositorBridgeParent::AllocPWebRenderB
   MOZ_ASSERT(mWidget);
   RefPtr<widget::CompositorWidget> widget = mWidget;
   RefPtr<wr::WebRenderAPI> api = wr::WebRenderAPI::Create(
     gfxPrefs::WebRenderProfilerEnabled(), this, Move(widget), aSize);
   RefPtr<AsyncImagePipelineManager> asyncMgr =
     new AsyncImagePipelineManager(WebRenderBridgeParent::AllocIdNameSpace());
   if (!api) {
     mWrBridge = WebRenderBridgeParent::CreateDestroyed();
-    *aIdNamespace = mWrBridge->GetIdNameSpace();
+    *aIdNamespace = mWrBridge->GetIdNamespace();
     *aTextureFactoryIdentifier = TextureFactoryIdentifier(LayersBackend::LAYERS_NONE);
     return mWrBridge;
   }
-  MOZ_ASSERT(api); // TODO have a fallback
   api->SetRootPipeline(aPipelineId);
   RefPtr<CompositorAnimationStorage> animStorage = GetAnimationStorage();
   mWrBridge = new WebRenderBridgeParent(this, aPipelineId, mWidget, nullptr, Move(api), Move(asyncMgr), Move(animStorage));
-  *aIdNamespace = mWrBridge->GetIdNameSpace();
+  *aIdNamespace = mWrBridge->GetIdNamespace();
 
   mCompositorScheduler = mWrBridge->CompositorScheduler();
   MOZ_ASSERT(mCompositorScheduler);
   mWrBridge.get()->AddRef(); // IPDL reference
   MonitorAutoLock lock(*sIndirectLayerTreesLock);
   MOZ_ASSERT(sIndirectLayerTrees[mRootLayerTreeID].mWrBridge == nullptr);
   sIndirectLayerTrees[mRootLayerTreeID].mWrBridge = mWrBridge;
   *aTextureFactoryIdentifier = mWrBridge->GetTextureFactoryIdentifier();
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -454,17 +454,17 @@ public:
   TimeDuration GetVsyncInterval() const {
     // the variable is called "rate" but really it's an interval
     return mVsyncRate;
   }
 
   PWebRenderBridgeParent* AllocPWebRenderBridgeParent(const wr::PipelineId& aPipelineId,
                                                       const LayoutDeviceIntSize& aSize,
                                                       TextureFactoryIdentifier* aTextureFactoryIdentifier,
-                                                      uint32_t* aIdNamespace) override;
+                                                      wr::IdNamespace* aIdNamespace) override;
   bool DeallocPWebRenderBridgeParent(PWebRenderBridgeParent* aActor) override;
   RefPtr<WebRenderBridgeParent> GetWebRenderBridgeParent() const;
   Maybe<TimeStamp> GetTestingTimeStamp() const;
 
   static void SetWebRenderProfilerEnabled(bool aEnabled);
 
   static CompositorBridgeParent* GetCompositorBridgeParentFromLayersId(const uint64_t& aLayersId);
 
--- a/gfx/layers/ipc/CrossProcessCompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CrossProcessCompositorBridgeParent.cpp
@@ -189,17 +189,17 @@ CrossProcessCompositorBridgeParent::Deal
   controller->Release();
   return true;
 }
 
 PWebRenderBridgeParent*
 CrossProcessCompositorBridgeParent::AllocPWebRenderBridgeParent(const wr::PipelineId& aPipelineId,
                                                                 const LayoutDeviceIntSize& aSize,
                                                                 TextureFactoryIdentifier* aTextureFactoryIdentifier,
-                                                                uint32_t *aIdNamespace)
+                                                                wr::IdNamespace *aIdNamespace)
 {
 #ifndef MOZ_BUILD_WEBRENDER
   // Extra guard since this in the parent process and we don't want a malicious
   // child process invoking this codepath before it's ready
   MOZ_RELEASE_ASSERT(false);
 #endif
   uint64_t layersId = wr::AsUint64(aPipelineId);
   // Check to see if this child process has access to this layer tree.
@@ -213,32 +213,32 @@ CrossProcessCompositorBridgeParent::Allo
   MOZ_ASSERT(sIndirectLayerTrees[layersId].mWrBridge == nullptr);
   WebRenderBridgeParent* parent = nullptr;
   CompositorBridgeParent* cbp = sIndirectLayerTrees[layersId].mParent;
   if (!cbp) {
     // This could happen when this function is called after CompositorBridgeParent destruction.
     // This was observed during Tab move between different windows.
     NS_WARNING("Created child without a matching parent?");
     parent = WebRenderBridgeParent::CreateDestroyed();
-    *aIdNamespace = parent->GetIdNameSpace();
+    *aIdNamespace = parent->GetIdNamespace();
     *aTextureFactoryIdentifier = TextureFactoryIdentifier(LayersBackend::LAYERS_NONE);
     return parent;
   }
   WebRenderBridgeParent* root = sIndirectLayerTrees[cbp->RootLayerTreeId()].mWrBridge.get();
 
   RefPtr<wr::WebRenderAPI> api = root->GetWebRenderAPI();
   RefPtr<AsyncImagePipelineManager> holder = root->AsyncImageManager();
   RefPtr<CompositorAnimationStorage> animStorage = cbp->GetAnimationStorage();
   parent = new WebRenderBridgeParent(this, aPipelineId, nullptr, root->CompositorScheduler(), Move(api), Move(holder), Move(animStorage));
 
   parent->AddRef(); // IPDL reference
   sIndirectLayerTrees[layersId].mCrossProcessParent = this;
   sIndirectLayerTrees[layersId].mWrBridge = parent;
   *aTextureFactoryIdentifier = parent->GetTextureFactoryIdentifier();
-  *aIdNamespace = parent->GetIdNameSpace();
+  *aIdNamespace = parent->GetIdNamespace();
 
   return parent;
 }
 
 bool
 CrossProcessCompositorBridgeParent::DeallocPWebRenderBridgeParent(PWebRenderBridgeParent* aActor)
 {
 #ifndef MOZ_BUILD_WEBRENDER
--- a/gfx/layers/ipc/CrossProcessCompositorBridgeParent.h
+++ b/gfx/layers/ipc/CrossProcessCompositorBridgeParent.h
@@ -143,17 +143,17 @@ public:
   virtual PAPZParent* AllocPAPZParent(const uint64_t& aLayersId) override;
   virtual bool DeallocPAPZParent(PAPZParent* aActor) override;
 
   virtual void UpdatePaintTime(LayerTransactionParent* aLayerTree, const TimeDuration& aPaintTime) override;
 
   PWebRenderBridgeParent* AllocPWebRenderBridgeParent(const wr::PipelineId& aPipelineId,
                                                       const LayoutDeviceIntSize& aSize,
                                                       TextureFactoryIdentifier* aTextureFactoryIdentifier,
-                                                      uint32_t* aIdNamespace) override;
+                                                      wr::IdNamespace* aIdNamespace) override;
   bool DeallocPWebRenderBridgeParent(PWebRenderBridgeParent* aActor) override;
 
   void ObserveLayerUpdate(uint64_t aLayersId, uint64_t aEpoch, bool aActive) override;
 
   bool IsRemote() const override {
     return true;
   }
 
--- a/gfx/layers/ipc/PCompositorBridge.ipdl
+++ b/gfx/layers/ipc/PCompositorBridge.ipdl
@@ -34,16 +34,17 @@ using mozilla::CSSIntRegion from "Units.
 using mozilla::LayoutDeviceIntPoint from "Units.h";
 using mozilla::LayoutDeviceIntRegion from "Units.h";
 using mozilla::LayoutDeviceIntSize from "Units.h";
 using class mozilla::TimeStamp from "mozilla/TimeStamp.h";
 using class mozilla::layers::FrameUniformityData from "mozilla/layers/FrameUniformityData.h";
 using mozilla::layers::TextureFlags from "mozilla/layers/CompositorTypes.h";
 using mozilla::layers::CompositorOptions from "mozilla/layers/CompositorOptions.h";
 using mozilla::wr::PipelineId from "mozilla/webrender/WebRenderTypes.h";
+using mozilla::wr::IdNamespace from "mozilla/webrender/WebRenderTypes.h";
 using base::ProcessId from "base/process.h";
 using mozilla::wr::MaybeExternalImageId from "mozilla/webrender/WebRenderTypes.h";
 
 namespace mozilla {
 namespace layers {
 
 
 /**
@@ -245,17 +246,17 @@ parent:
   async AllPluginsCaptured();
 
   async PTexture(SurfaceDescriptor aSharedData, LayersBackend aBackend, TextureFlags aTextureFlags, uint64_t id, uint64_t aSerial, MaybeExternalImageId aExternalImageId);
 
   sync SyncWithCompositor();
 
   // The pipelineId is the same as the layersId
   sync PWebRenderBridge(PipelineId pipelineId, LayoutDeviceIntSize aSize)
-    returns (TextureFactoryIdentifier textureFactoryIdentifier, uint32_t idNamespace); //XXX: use the WrIdNamespace type
+    returns (TextureFactoryIdentifier textureFactoryIdentifier, IdNamespace idNamespace);
 
   sync CheckContentOnlyTDR(uint32_t sequenceNum)
     returns (bool isContentOnlyTDR);
 
 child:
   // Send back Compositor Frame Metrics from APZCs so tiled layers can
   // update progressively.
   async SharedCompositorFrameMetrics(Handle metrics, CrossProcessMutexHandle mutex, uint64_t aLayersId, uint32_t aAPZCId);
--- a/gfx/layers/ipc/PWebRenderBridge.ipdl
+++ b/gfx/layers/ipc/PWebRenderBridge.ipdl
@@ -19,16 +19,17 @@ using struct mozilla::layers::Scrollable
 using struct mozilla::layers::TextureInfo from "mozilla/layers/CompositorTypes.h";
 using mozilla::layers::CompositableHandle from "mozilla/layers/LayersTypes.h";
 using mozilla::wr::ByteBuffer from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::ExternalImageId from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::ImageKey from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::FontKey from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::PipelineId from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::BuiltDisplayListDescriptor from "mozilla/webrender/webrender_ffi.h";
+using mozilla::wr::IdNamespace from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::layers::WebRenderScrollData from "mozilla/layers/WebRenderScrollData.h";
 
 namespace mozilla {
 namespace layers {
 
 sync protocol PWebRenderBridge
 {
   manager PCompositorBridge;
@@ -51,20 +52,20 @@ parent:
                    SurfaceFormat aFormat, ByteBuffer aBytes);
   async DeleteImage(ImageKey aImageKey);
   async DeleteCompositorAnimations(uint64_t[] aIds);
   async AddRawFont(FontKey aFontKey, ByteBuffer aBytes, uint32_t aFontIndex);
   async DeleteFont(FontKey aFontKey);
   async DPBegin(IntSize aSize);
   async DPEnd(IntSize aSize, WebRenderParentCommand[] commands, OpDestroy[] toDestroy, uint64_t fwdTransactionId, uint64_t transactionId,
               LayoutSize aContentSize, ByteBuffer aDL, BuiltDisplayListDescriptor aDLDesc,
-              WebRenderScrollData aScrollData, uint32_t idNameSpace, TimeStamp fwdTime);
+              WebRenderScrollData aScrollData, IdNamespace aIdNamespace, TimeStamp fwdTime);
   sync DPSyncEnd(IntSize aSize, WebRenderParentCommand[] commands, OpDestroy[] toDestroy, uint64_t fwdTransactionId, uint64_t transactionId,
                  LayoutSize aContentSize, ByteBuffer aDL, BuiltDisplayListDescriptor aDLDesc,
-                 WebRenderScrollData aScrollData, uint32_t idNameSpace, TimeStamp fwdTime);
+                 WebRenderScrollData aScrollData, IdNamespace aIdNamespace, TimeStamp fwdTime);
   async ParentCommands(WebRenderParentCommand[] commands);
   sync DPGetSnapshot(PTexture texture);
   async AddPipelineIdForAsyncCompositable(PipelineId aImageId, CompositableHandle aHandle);
   async RemovePipelineIdForAsyncCompositable(PipelineId aPipelineId);
   async AddExternalImageIdForCompositable(ExternalImageId aImageId, CompositableHandle aHandle);
   async RemoveExternalImageId(ExternalImageId aImageId);
   async SetLayerObserverEpoch(uint64_t layerObserverEpoch);
   async ClearCachedResources();
@@ -83,14 +84,14 @@ parent:
   sync SetAsyncScrollOffset(ViewID scrollId, float x, float y);
   sync SetAsyncZoom(ViewID scrollId, float zoom);
   async FlushApzRepaints();
   sync GetAPZTestData() returns (APZTestData data);
 
   async Shutdown();
   sync ShutdownSync();
 child:
-  async WrUpdated(uint32_t newIdNameSpace);
+  async WrUpdated(IdNamespace aNewIdNamespace);
   async __delete__();
 };
 
 } // layers
 } // mozilla
--- a/gfx/layers/wr/AsyncImagePipelineManager.cpp
+++ b/gfx/layers/wr/AsyncImagePipelineManager.cpp
@@ -18,17 +18,17 @@ namespace layers {
 AsyncImagePipelineManager::AsyncImagePipeline::AsyncImagePipeline()
  : mInitialised(false)
  , mIsChanged(false)
  , mUseExternalImage(false)
  , mFilter(wr::ImageRendering::Auto)
  , mMixBlendMode(wr::MixBlendMode::Normal)
 {}
 
-AsyncImagePipelineManager::AsyncImagePipelineManager(uint32_t aIdNamespace)
+AsyncImagePipelineManager::AsyncImagePipelineManager(wr::IdNamespace aIdNamespace)
  : mIdNamespace(aIdNamespace)
  , mResourceId(0)
  , mAsyncImageEpoch(0)
  , mDestroyed(false)
 {
   MOZ_COUNT_CTOR(AsyncImagePipelineManager);
 }
 
--- a/gfx/layers/wr/AsyncImagePipelineManager.h
+++ b/gfx/layers/wr/AsyncImagePipelineManager.h
@@ -29,17 +29,17 @@ class CompositorVsyncScheduler;
 class WebRenderImageHost;
 class WebRenderTextureHost;
 
 class AsyncImagePipelineManager final
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AsyncImagePipelineManager)
 
-  explicit AsyncImagePipelineManager(uint32_t aIdNamespace);
+  explicit AsyncImagePipelineManager(wr::IdNamespace aIdNamespace);
 
 protected:
   ~AsyncImagePipelineManager();
 
 public:
   void Destroy(wr::WebRenderAPI* aApi);
   bool HasKeysToDelete();
 
@@ -89,21 +89,21 @@ public:
   {
     aNotifications->AppendElements(Move(mImageCompositeNotifications));
   }
 
 private:
   void DeleteOldAsyncImages(wr::WebRenderAPI* aApi);
 
   uint32_t GetNextResourceId() { return ++mResourceId; }
-  uint32_t GetNamespace() { return mIdNamespace; }
+  wr::IdNamespace GetNamespace() { return mIdNamespace; }
   wr::ImageKey GenerateImageKey()
   {
     wr::ImageKey key;
-    key.mNamespace.mHandle = GetNamespace();
+    key.mNamespace = GetNamespace();
     key.mHandle = GetNextResourceId();
     return key;
   }
   bool GenerateImageKeyForTextureHost(wr::WebRenderAPI* aApi, TextureHost* aTexture, nsTArray<wr::ImageKey>& aKeys);
 
   struct ForwardingTextureHost {
     ForwardingTextureHost(const wr::Epoch& aEpoch, TextureHost* aTexture)
       : mEpoch(aEpoch)
@@ -136,17 +136,17 @@ private:
   };
 
   bool UpdateImageKeys(wr::WebRenderAPI* aApi,
                        bool& aUseExternalImage,
                        AsyncImagePipeline* aImageMgr,
                        nsTArray<wr::ImageKey>& aKeys,
                        nsTArray<wr::ImageKey>& aKeysToDelete);
 
-  uint32_t mIdNamespace;
+  wr::IdNamespace mIdNamespace;
   uint32_t mResourceId;
 
   nsClassHashtable<nsUint64HashKey, PipelineTexturesHolder> mPipelineTexturesHolders;
   nsClassHashtable<nsUint64HashKey, AsyncImagePipeline> mAsyncImagePipelines;
   uint32_t mAsyncImageEpoch;
   nsTArray<wr::ImageKey> mKeysToDelete;
   bool mDestroyed;
 
--- a/gfx/layers/wr/WebRenderBridgeChild.cpp
+++ b/gfx/layers/wr/WebRenderBridgeChild.cpp
@@ -18,17 +18,17 @@ namespace mozilla {
 namespace layers {
 
 using namespace mozilla::gfx;
 
 WebRenderBridgeChild::WebRenderBridgeChild(const wr::PipelineId& aPipelineId)
   : mReadLockSequenceNumber(0)
   , mIsInTransaction(false)
   , mIsInClearCachedResources(false)
-  , mIdNamespace(0)
+  , mIdNamespace{0}
   , mResourceId(0)
   , mPipelineId(aPipelineId)
   , mIPCOpen(false)
   , mDestroyed(false)
   , mFontKeysDeleted(0)
 {
 }
 
@@ -114,17 +114,17 @@ WebRenderBridgeChild::DPEnd(wr::DisplayL
 
   TimeStamp fwdTime;
 #if defined(ENABLE_FRAME_LATENCY_LOG)
   fwdTime = TimeStamp::Now();
 #endif
 
   if (aIsSync) {
     this->SendDPSyncEnd(aSize, mParentCommands, mDestroyedActors, GetFwdTransactionId(), aTransactionId,
-                        contentSize, dlData, dl.dl_desc, aScrollData, mIdNamespace,fwdTime);
+                        contentSize, dlData, dl.dl_desc, aScrollData, mIdNamespace, fwdTime);
   } else {
     this->SendDPEnd(aSize, mParentCommands, mDestroyedActors, GetFwdTransactionId(), aTransactionId,
                     contentSize, dlData, dl.dl_desc, aScrollData, mIdNamespace, fwdTime);
   }
 
   mParentCommands.Clear();
   mDestroyedActors.Clear();
   mIsInTransaction = false;
@@ -255,17 +255,17 @@ WebRenderBridgeChild::GetFontKeyForScale
   }
 
   FontFileData data;
   if (!unscaled->GetFontFileData(WriteFontFileData, &data) ||
       !data.mFontBuffer.mData) {
     return key;
   }
 
-  key.mNamespace.mHandle = GetNamespace();
+  key.mNamespace = GetNamespace();
   key.mHandle = GetNextResourceId();
 
   SendAddRawFont(key, data.mFontBuffer, data.mFontIndex);
 
   mFontKeys.Put(unscaled, key);
 
   return key;
 }
@@ -447,21 +447,21 @@ WebRenderBridgeChild::GetFwdTransactionI
 
 bool
 WebRenderBridgeChild::InForwarderThread()
 {
   return NS_IsMainThread();
 }
 
 mozilla::ipc::IPCResult
-WebRenderBridgeChild::RecvWrUpdated(const uint32_t& aNewIdNameSpace)
+WebRenderBridgeChild::RecvWrUpdated(const wr::IdNamespace& aNewIdNamespace)
 {
   // Update mIdNamespace to identify obsolete keys and messages by WebRenderBridgeParent.
   // Since usage of invalid keys could cause crash in webrender.
-  mIdNamespace = aNewIdNameSpace;
+  mIdNamespace = aNewIdNamespace;
   // Remove all FontKeys since they are removed by WebRenderBridgeParent
   for (auto iter = mFontKeys.Iter(); !iter.Done(); iter.Next()) {
     SendDeleteFont(iter.Data());
   }
   mFontKeys.Clear();
   GetCompositorBridgeChild()->RecvInvalidateLayers(wr::AsUint64(mPipelineId));
   return IPC_OK();
 }
--- a/gfx/layers/wr/WebRenderBridgeChild.h
+++ b/gfx/layers/wr/WebRenderBridgeChild.h
@@ -85,25 +85,25 @@ public:
    * Clean this up, finishing with SendShutDown() which will cause __delete__
    * to be sent from the parent side.
    */
   void Destroy(bool aIsSync);
   bool IPCOpen() const { return mIPCOpen && !mDestroyed; }
   bool IsDestroyed() const { return mDestroyed; }
 
   uint32_t GetNextResourceId() { return ++mResourceId; }
-  uint32_t GetNamespace() { return mIdNamespace; }
-  void SetNamespace(uint32_t aIdNamespace)
+  wr::IdNamespace GetNamespace() { return mIdNamespace; }
+  void SetNamespace(wr::IdNamespace aIdNamespace)
   {
     mIdNamespace = aIdNamespace;
   }
 
   wr::WrImageKey GetNextImageKey()
   {
-    return wr::WrImageKey{ wr::WrIdNamespace { GetNamespace() }, GetNextResourceId() };
+    return wr::WrImageKey{ GetNamespace(), GetNextResourceId() };
   }
 
   void PushGlyphs(wr::DisplayListBuilder& aBuilder, const nsTArray<GlyphArray>& aGlyphs,
                   gfx::ScaledFont* aFont, const StackingContextHelper& aSc,
                   const LayerRect& aBounds, const LayerRect& aClip);
 
   wr::FontKey GetFontKeyForScaledFont(gfx::ScaledFont* aScaledFont);
 
@@ -139,17 +139,17 @@ private:
                                  TextureClient* aClientOnBlack,
                                  TextureClient* aClientOnWhite) override;
   void UpdateFwdTransactionId() override;
   uint64_t GetFwdTransactionId() override;
   bool InForwarderThread() override;
 
   void ActorDestroy(ActorDestroyReason why) override;
 
-  virtual mozilla::ipc::IPCResult RecvWrUpdated(const uint32_t& aNewIdNameSpace) override;
+  virtual mozilla::ipc::IPCResult RecvWrUpdated(const wr::IdNamespace& aNewIdNamespace) override;
 
   void AddIPDLReference() {
     MOZ_ASSERT(mIPCOpen == false);
     mIPCOpen = true;
     AddRef();
   }
   void ReleaseIPDLReference() {
     MOZ_ASSERT(mIPCOpen == true);
@@ -161,17 +161,17 @@ private:
 
   nsTArray<WebRenderParentCommand> mParentCommands;
   nsTArray<OpDestroy> mDestroyedActors;
   nsDataHashtable<nsUint64HashKey, CompositableClient*> mCompositables;
   nsTArray<nsTArray<ReadLockInit>> mReadLocks;
   uint64_t mReadLockSequenceNumber;
   bool mIsInTransaction;
   bool mIsInClearCachedResources;
-  uint32_t mIdNamespace;
+  wr::IdNamespace mIdNamespace;
   uint32_t mResourceId;
   wr::PipelineId mPipelineId;
 
   bool mIPCOpen;
   bool mDestroyed;
 
   uint32_t mFontKeysDeleted;
   nsDataHashtable<UnscaledFontHashKey, wr::FontKey> mFontKeys;
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -126,17 +126,17 @@ WebRenderBridgeParent::WebRenderBridgePa
   , mWidget(aWidget)
   , mApi(aApi)
   , mAsyncImageManager(aImageMgr)
   , mCompositorScheduler(aScheduler)
   , mAnimStorage(aAnimStorage)
   , mChildLayerObserverEpoch(0)
   , mParentLayerObserverEpoch(0)
   , mWrEpoch(0)
-  , mIdNameSpace(AllocIdNameSpace())
+  , mIdNamespace(AllocIdNameSpace())
   , mPaused(false)
   , mDestroyed(false)
   , mForceRendering(false)
 {
   MOZ_ASSERT(mAsyncImageManager);
   MOZ_ASSERT(mAnimStorage);
   mAsyncImageManager->AddPipeline(mPipelineId);
   if (mWidget) {
@@ -145,17 +145,17 @@ WebRenderBridgeParent::WebRenderBridgePa
   }
 }
 
 WebRenderBridgeParent::WebRenderBridgeParent()
   : mCompositorBridge(nullptr)
   , mChildLayerObserverEpoch(0)
   , mParentLayerObserverEpoch(0)
   , mWrEpoch(0)
-  , mIdNameSpace(AllocIdNameSpace())
+  , mIdNamespace(AllocIdNameSpace())
   , mPaused(false)
   , mDestroyed(true)
   , mForceRendering(false)
 {
 }
 
 /* static */ WebRenderBridgeParent*
 WebRenderBridgeParent::CreateDestroyed()
@@ -229,17 +229,17 @@ WebRenderBridgeParent::RecvAddImage(cons
                                     const gfx::SurfaceFormat& aFormat,
                                     const ByteBuffer& aBuffer)
 {
   if (mDestroyed) {
     return IPC_OK();
   }
 
   // Check if key is obsoleted.
-  if (aImageKey.mNamespace.mHandle != mIdNameSpace) {
+  if (aImageKey.mNamespace != mIdNamespace) {
     return IPC_OK();
   }
 
   MOZ_ASSERT(mApi);
   MOZ_ASSERT(mActiveImageKeys.find(wr::AsUint64(aImageKey)) == mActiveImageKeys.end());
 
   wr::ImageDescriptor descriptor(aSize, aStride, aFormat);
   mActiveImageKeys.insert(wr::AsUint64(aImageKey));
@@ -256,17 +256,17 @@ WebRenderBridgeParent::RecvAddBlobImage(
                                         const gfx::SurfaceFormat& aFormat,
                                         const ByteBuffer& aBuffer)
 {
   if (mDestroyed) {
     return IPC_OK();
   }
 
   // Check if key is obsoleted.
-  if (aImageKey.mNamespace.mHandle != mIdNameSpace) {
+  if (aImageKey.mNamespace != mIdNamespace) {
     return IPC_OK();
   }
 
   MOZ_ASSERT(mApi);
   MOZ_ASSERT(mActiveImageKeys.find(wr::AsUint64(aImageKey)) == mActiveImageKeys.end());
 
   wr::ImageDescriptor descriptor(aSize, aStride, aFormat);
   mActiveImageKeys.insert(wr::AsUint64(aImageKey));
@@ -281,17 +281,17 @@ WebRenderBridgeParent::RecvAddRawFont(co
                                       const ByteBuffer& aBuffer,
                                       const uint32_t& aFontIndex)
 {
   if (mDestroyed) {
     return IPC_OK();
   }
 
   // Check if key is obsoleted.
-  if (aFontKey.mNamespace.mHandle != mIdNameSpace) {
+  if (aFontKey.mNamespace != mIdNamespace) {
     return IPC_OK();
   }
 
   MOZ_ASSERT(mApi);
   MOZ_ASSERT(mFontKeys.find(wr::AsUint64(aFontKey)) == mFontKeys.end());
 
   auto slice = aBuffer.AsSlice();
   mFontKeys.insert(wr::AsUint64(aFontKey));
@@ -304,17 +304,17 @@ mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvDeleteFont(const wr::FontKey& aFontKey)
 {
   if (mDestroyed) {
     return IPC_OK();
   }
   MOZ_ASSERT(mApi);
 
   // Check if key is obsoleted.
-  if (aFontKey.mNamespace.mHandle != mIdNameSpace) {
+  if (aFontKey.mNamespace != mIdNamespace) {
     return IPC_OK();
   }
 
   if (mFontKeys.find(wr::AsUint64(aFontKey)) != mFontKeys.end()) {
     mFontKeys.erase(wr::AsUint64(aFontKey));
     mApi->DeleteFont(aFontKey);
   } else {
     MOZ_ASSERT_UNREACHABLE("invalid FontKey");
@@ -330,17 +330,17 @@ WebRenderBridgeParent::RecvUpdateImage(c
                                        const ByteBuffer& aBuffer)
 {
   if (mDestroyed) {
     return IPC_OK();
   }
   MOZ_ASSERT(mApi);
 
   // Check if key is obsoleted.
-  if (aImageKey.mNamespace.mHandle != mIdNameSpace) {
+  if (aImageKey.mNamespace != mIdNamespace) {
     return IPC_OK();
   }
 
   wr::ImageDescriptor descriptor(aSize, aFormat);
   mApi->UpdateImageBuffer(aImageKey, descriptor, aBuffer.AsSlice());
 
   return IPC_OK();
 }
@@ -349,17 +349,17 @@ mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvDeleteImage(const wr::ImageKey& aImageKey)
 {
   if (mDestroyed) {
     return IPC_OK();
   }
   MOZ_ASSERT(mApi);
 
   // Check if key is obsoleted.
-  if (aImageKey.mNamespace.mHandle != mIdNameSpace) {
+  if (aImageKey.mNamespace != mIdNamespace) {
     return IPC_OK();
   }
 
   if (mActiveImageKeys.find(wr::AsUint64(aImageKey)) != mActiveImageKeys.end()) {
     mActiveImageKeys.erase(wr::AsUint64(aImageKey));
     mKeysToDelete.push_back(aImageKey);
   } else {
     MOZ_ASSERT_UNREACHABLE("invalid ImageKey");
@@ -401,17 +401,17 @@ WebRenderBridgeParent::HandleDPEnd(const
                                  InfallibleTArray<WebRenderParentCommand>&& aCommands,
                                  InfallibleTArray<OpDestroy>&& aToDestroy,
                                  const uint64_t& aFwdTransactionId,
                                  const uint64_t& aTransactionId,
                                  const wr::LayoutSize& aContentSize,
                                  const wr::ByteBuffer& dl,
                                  const wr::BuiltDisplayListDescriptor& dlDesc,
                                  const WebRenderScrollData& aScrollData,
-                                 const uint32_t& aIdNameSpace,
+                                 const wr::IdNamespace& aIdNamespace,
                                  const TimeStamp& aFwdTime)
 {
   AutoProfilerTracing tracing("Paint", "DPTransaction");
   UpdateFwdTransactionId(aFwdTransactionId);
   AutoClearReadLocks clearLocks(mReadLocks);
 
   if (mDestroyed) {
     for (const auto& op : aToDestroy) {
@@ -420,23 +420,23 @@ WebRenderBridgeParent::HandleDPEnd(const
     return;
   }
   // This ensures that destroy operations are always processed. It is not safe
   // to early-return from RecvDPEnd without doing so.
   AutoWebRenderBridgeParentAsyncMessageSender autoAsyncMessageSender(this, &aToDestroy);
 
   uint32_t wrEpoch = GetNextWrEpoch();
   ProcessWebRenderCommands(aSize, aCommands, wr::NewEpoch(wrEpoch),
-                           aContentSize, dl, dlDesc, aIdNameSpace);
+                           aContentSize, dl, dlDesc, aIdNamespace);
   HoldPendingTransactionId(wrEpoch, aTransactionId, aFwdTime);
 
   mScrollData = aScrollData;
   UpdateAPZ();
 
-  if (mIdNameSpace != aIdNameSpace) {
+  if (mIdNamespace != aIdNamespace) {
     // Pretend we composited since someone is wating for this event,
     // though DisplayList was not pushed to webrender.
     TimeStamp now = TimeStamp::Now();
     mCompositorBridge->DidComposite(wr::AsUint64(mPipelineId), now, now);
   }
 }
 
 CompositorBridgeParent*
@@ -515,45 +515,45 @@ WebRenderBridgeParent::RecvDPEnd(const g
                                  InfallibleTArray<WebRenderParentCommand>&& aCommands,
                                  InfallibleTArray<OpDestroy>&& aToDestroy,
                                  const uint64_t& aFwdTransactionId,
                                  const uint64_t& aTransactionId,
                                  const wr::LayoutSize& aContentSize,
                                  const wr::ByteBuffer& dl,
                                  const wr::BuiltDisplayListDescriptor& dlDesc,
                                  const WebRenderScrollData& aScrollData,
-                                 const uint32_t& aIdNameSpace,
+                                 const wr::IdNamespace& aIdNamespace,
                                  const TimeStamp& aFwdTime)
 {
   if (mDestroyed) {
     return IPC_OK();
   }
   HandleDPEnd(aSize, Move(aCommands), Move(aToDestroy), aFwdTransactionId, aTransactionId,
-              aContentSize, dl, dlDesc, aScrollData, aIdNameSpace, aFwdTime);
+              aContentSize, dl, dlDesc, aScrollData, aIdNamespace, aFwdTime);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvDPSyncEnd(const gfx::IntSize &aSize,
                                      InfallibleTArray<WebRenderParentCommand>&& aCommands,
                                      InfallibleTArray<OpDestroy>&& aToDestroy,
                                      const uint64_t& aFwdTransactionId,
                                      const uint64_t& aTransactionId,
                                      const wr::LayoutSize& aContentSize,
                                      const wr::ByteBuffer& dl,
                                      const wr::BuiltDisplayListDescriptor& dlDesc,
                                      const WebRenderScrollData& aScrollData,
-                                     const uint32_t& aIdNameSpace,
+                                     const wr::IdNamespace& aIdNamespace,
                                      const TimeStamp& aFwdTime)
 {
   if (mDestroyed) {
     return IPC_OK();
   }
   HandleDPEnd(aSize, Move(aCommands), Move(aToDestroy), aFwdTransactionId, aTransactionId,
-              aContentSize, dl, dlDesc, aScrollData, aIdNameSpace, aFwdTime);
+              aContentSize, dl, dlDesc, aScrollData, aIdNamespace, aFwdTime);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvParentCommands(nsTArray<WebRenderParentCommand>&& aCommands)
 {
   if (mDestroyed) {
     return IPC_OK();
@@ -567,17 +567,17 @@ WebRenderBridgeParent::ProcessWebRenderP
 {
   for (InfallibleTArray<WebRenderParentCommand>::index_type i = 0; i < aCommands.Length(); ++i) {
     const WebRenderParentCommand& cmd = aCommands[i];
     switch (cmd.type()) {
       case WebRenderParentCommand::TOpAddExternalImage: {
         const OpAddExternalImage& op = cmd.get_OpAddExternalImage();
         Range<const wr::ImageKey> keys(&op.key(), 1);
         // Check if key is obsoleted.
-        if (keys[0].mNamespace.mHandle != mIdNameSpace) {
+        if (keys[0].mNamespace != mIdNamespace) {
           break;
         }
         MOZ_ASSERT(mExternalImageIds.Get(wr::AsUint64(op.externalImageId())).get());
         MOZ_ASSERT(mActiveImageKeys.find(wr::AsUint64(keys[0])) == mActiveImageKeys.end());
         mActiveImageKeys.insert(wr::AsUint64(keys[0]));
 
         RefPtr<WebRenderImageHost> host = mExternalImageIds.Get(wr::AsUint64(op.externalImageId()));
         if (!host) {
@@ -658,24 +658,24 @@ WebRenderBridgeParent::ProcessWebRenderP
   }
 }
 
 void
 WebRenderBridgeParent::ProcessWebRenderCommands(const gfx::IntSize &aSize,
                                                 InfallibleTArray<WebRenderParentCommand>& aCommands, const wr::Epoch& aEpoch,
                                                 const wr::LayoutSize& aContentSize, const wr::ByteBuffer& dl,
                                                 const wr::BuiltDisplayListDescriptor& dlDesc,
-                                                const uint32_t& aIdNameSpace)
+                                                const wr::IdNamespace& aIdNamespace)
 {
   mAsyncImageManager->SetCompositionTime(TimeStamp::Now());
   ProcessWebRenderParentCommands(aCommands);
 
   // The command is obsoleted.
   // Do not set the command to webrender since it causes crash in webrender.
-  if (mIdNameSpace != aIdNameSpace) {
+  if (mIdNamespace != aIdNamespace) {
     return;
   }
 
   if (mWidget) {
     LayoutDeviceIntSize size = mWidget->GetClientSize();
     mApi->SetWindowParameters(size);
   }
   gfx::Color color = mWidget ? gfx::Color(0.3f, 0.f, 0.f, 1.f) : gfx::Color(0.f, 0.f, 0.f, 0.f);
@@ -876,26 +876,26 @@ WebRenderBridgeParent::UpdateWebRender(C
   MOZ_ASSERT(aAnimStorage);
 
   if (mDestroyed) {
     return;
   }
 
   // Update id name space to identify obsoleted keys.
   // Since usage of invalid keys could cause crash in webrender.
-  mIdNameSpace = AllocIdNameSpace();
+  mIdNamespace = AllocIdNameSpace();
   // XXX Remove it when webrender supports sharing/moving Keys between different webrender instances.
   // XXX It requests client to update/reallocate webrender related resources,
   // but parent side does not wait end of the update.
   // The code could become simpler if we could serialise old keys deallocation and new keys allocation.
   // But we do not do it, it is because client side deallocate old layers/webrender keys
   // after new layers/webrender keys allocation.
   // Without client side's layout refactoring, we could not finish all old layers/webrender keys removals
   // before new layer/webrender keys allocation. In future, we could address the problem.
-  Unused << SendWrUpdated(mIdNameSpace);
+  Unused << SendWrUpdated(mIdNamespace);
   CompositorBridgeParentBase* cBridge = mCompositorBridge;
   // XXX Stop to clear resources if webreder supports resources sharing between different webrender instances.
   ClearResources();
   mCompositorBridge = cBridge;
   mCompositorScheduler = aScheduler;
   mApi = aApi;
   mAsyncImageManager = aImageMgr;
   mAnimStorage = aAnimStorage;
--- a/gfx/layers/wr/WebRenderBridgeParent.h
+++ b/gfx/layers/wr/WebRenderBridgeParent.h
@@ -97,28 +97,28 @@ public:
                                     InfallibleTArray<WebRenderParentCommand>&& aCommands,
                                     InfallibleTArray<OpDestroy>&& aToDestroy,
                                     const uint64_t& aFwdTransactionId,
                                     const uint64_t& aTransactionId,
                                     const wr::LayoutSize& aContentSize,
                                     const wr::ByteBuffer& dl,
                                     const wr::BuiltDisplayListDescriptor& dlDesc,
                                     const WebRenderScrollData& aScrollData,
-                                    const uint32_t& aIdNameSpace,
+                                    const wr::IdNamespace& aIdNamespace,
                                     const TimeStamp& aFwdTime) override;
   mozilla::ipc::IPCResult RecvDPSyncEnd(const gfx::IntSize& aSize,
                                         InfallibleTArray<WebRenderParentCommand>&& aCommands,
                                         InfallibleTArray<OpDestroy>&& aToDestroy,
                                         const uint64_t& aFwdTransactionId,
                                         const uint64_t& aTransactionId,
                                         const wr::LayoutSize& aContentSize,
                                         const wr::ByteBuffer& dl,
                                         const wr::BuiltDisplayListDescriptor& dlDesc,
                                         const WebRenderScrollData& aScrollData,
-                                        const uint32_t& aIdNameSpace,
+                                        const wr::IdNamespace& aIdNamespace,
                                         const TimeStamp& aFwdTime) override;
   mozilla::ipc::IPCResult RecvParentCommands(nsTArray<WebRenderParentCommand>&& commands) override;
   mozilla::ipc::IPCResult RecvDPGetSnapshot(PTextureParent* aTexture) override;
 
   mozilla::ipc::IPCResult RecvAddPipelineIdForAsyncCompositable(const wr::PipelineId& aPipelineIds,
                                                                 const CompositableHandle& aHandle) override;
   mozilla::ipc::IPCResult RecvRemovePipelineIdForAsyncCompositable(const wr::PipelineId& aPipelineId) override;
 
@@ -173,26 +173,26 @@ public:
   uint64_t LastPendingTransactionId();
   uint64_t FlushPendingTransactionIds();
   uint64_t FlushTransactionIdsForEpoch(const wr::Epoch& aEpoch, const TimeStamp& aEndTime);
 
   TextureFactoryIdentifier GetTextureFactoryIdentifier();
 
   void ExtractImageCompositeNotifications(nsTArray<ImageCompositeNotificationInfo>* aNotifications);
 
-  uint32_t GetIdNameSpace()
+  wr::IdNamespace GetIdNamespace()
   {
-    return mIdNameSpace;
+    return mIdNamespace;
   }
 
   void UpdateAPZ();
   const WebRenderScrollData& GetScrollData() const;
 
-  static uint32_t AllocIdNameSpace() {
-    return ++sIdNameSpace;
+  static wr::IdNamespace AllocIdNameSpace() {
+    return wr::IdNamespace { ++sIdNameSpace };
   }
 
   void FlushRendering(bool aIsSync);
 
   void ScheduleComposition();
 
   void UpdateWebRender(CompositorVsyncScheduler* aScheduler,
                        wr::WebRenderAPI* aApi,
@@ -207,30 +207,30 @@ private:
   void DeleteOldImages();
   void ProcessWebRenderParentCommands(InfallibleTArray<WebRenderParentCommand>& aCommands);
   void ProcessWebRenderCommands(const gfx::IntSize &aSize,
                                 InfallibleTArray<WebRenderParentCommand>& commands,
                                 const wr::Epoch& aEpoch,
                                 const wr::LayoutSize& aContentSize,
                                 const wr::ByteBuffer& dl,
                                 const wr::BuiltDisplayListDescriptor& dlDesc,
-                                const uint32_t& aIdNameSpace);
+                                const wr::IdNamespace& aIdNamespace);
   void ClearResources();
   uint64_t GetChildLayerObserverEpoch() const { return mChildLayerObserverEpoch; }
   bool ShouldParentObserveEpoch();
   void HandleDPEnd(const gfx::IntSize& aSize,
                    InfallibleTArray<WebRenderParentCommand>&& aCommands,
                    InfallibleTArray<OpDestroy>&& aToDestroy,
                    const uint64_t& aFwdTransactionId,
                    const uint64_t& aTransactionId,
                    const wr::LayoutSize& aContentSize,
                    const wr::ByteBuffer& dl,
                    const wr::BuiltDisplayListDescriptor& dlDesc,
                    const WebRenderScrollData& aScrollData,
-                   const uint32_t& aIdNameSpace,
+                   const wr::IdNamespace& aIdNamespace,
                    const TimeStamp& aFwdTime);
   mozilla::ipc::IPCResult HandleShutdown();
 
   void AdvanceAnimations();
   void SampleAnimations(nsTArray<wr::WrOpacityProperty>& aOpacityArray,
                         nsTArray<wr::WrTransformProperty>& aTransformArray);
 
   CompositorBridgeParent* GetRootCompositorBridgeParent() const;
@@ -282,17 +282,17 @@ private:
   // parent. mChildLayerObserverEpoch is the latest epoch value received from the child.
   // mParentLayerObserverEpoch is the latest epoch value that we have told TabParent about
   // (via ObserveLayerUpdate).
   uint64_t mChildLayerObserverEpoch;
   uint64_t mParentLayerObserverEpoch;
 
   std::queue<PendingTransactionId> mPendingTransactionIds;
   uint32_t mWrEpoch;
-  uint32_t mIdNameSpace;
+  wr::IdNamespace mIdNamespace;
 
   bool mPaused;
   bool mDestroyed;
   bool mForceRendering;
 
   // Can only be accessed on the compositor thread.
   WebRenderScrollData mScrollData;
 
--- a/gfx/layers/wr/WebRenderLayer.cpp
+++ b/gfx/layers/wr/WebRenderLayer.cpp
@@ -30,17 +30,17 @@ WebRenderLayer::WrBridge()
 {
   return WrManager()->WrBridge();
 }
 
 wr::WrImageKey
 WebRenderLayer::GenerateImageKey()
 {
   wr::WrImageKey key;
-  key.mNamespace.mHandle = WrBridge()->GetNamespace();
+  key.mNamespace = WrBridge()->GetNamespace();
   key.mHandle = WrBridge()->GetNextResourceId();
   return key;
 }
 
 Maybe<wr::WrImageMask>
 WebRenderLayer::BuildWrMaskLayer(const StackingContextHelper& aRelativeTo)
 {
   if (GetLayer()->GetMaskLayer()) {
--- a/gfx/layers/wr/WebRenderLayerManager.cpp
+++ b/gfx/layers/wr/WebRenderLayerManager.cpp
@@ -52,17 +52,17 @@ WebRenderLayerManager::Initialize(PCompo
                                   wr::PipelineId aLayersId,
                                   TextureFactoryIdentifier* aTextureFactoryIdentifier)
 {
   MOZ_ASSERT(mWrChild == nullptr);
   MOZ_ASSERT(aTextureFactoryIdentifier);
 
   LayoutDeviceIntSize size = mWidget->GetClientSize();
   TextureFactoryIdentifier textureFactoryIdentifier;
-  uint32_t id_namespace;
+  wr::IdNamespace id_namespace;
   PWebRenderBridgeChild* bridge = aCBChild->SendPWebRenderBridgeConstructor(aLayersId,
                                                                             size,
                                                                             &textureFactoryIdentifier,
                                                                             &id_namespace);
   if (!bridge) {
     // This should only fail if we attempt to access a layer we don't have
     // permission for, or more likely, the GPU process crashed again during
     // reinitialization. We can expect to be notified again to reinitialize
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -23,16 +23,17 @@ type WrExternalImageBufferType = Externa
 
 /// cbindgen:field-names=[mHandle]
 /// cbindgen:derive-lt=true
 /// cbindgen:derive-lte=true
 type WrEpoch = Epoch;
 /// cbindgen:field-names=[mHandle]
 /// cbindgen:derive-lt=true
 /// cbindgen:derive-lte=true
+/// cbindgen:derive-neq=true
 type WrIdNamespace = IdNamespace;
 
 /// cbindgen:field-names=[mNamespace, mHandle]
 type WrPipelineId = PipelineId;
 /// cbindgen:field-names=[mNamespace, mHandle]
 type WrImageKey = ImageKey;
 /// cbindgen:field-names=[mNamespace, mHandle]
 type WrFontKey = FontKey;
--- a/gfx/webrender_bindings/webrender_ffi_generated.h
+++ b/gfx/webrender_bindings/webrender_ffi_generated.h
@@ -163,16 +163,19 @@ typedef Vec_u8 VecU8;
 typedef Arc_VecU8 ArcVecU8;
 
 struct IdNamespace {
   uint32_t mHandle;
 
   bool operator==(const IdNamespace& aOther) const {
     return mHandle == aOther.mHandle;
   }
+  bool operator!=(const IdNamespace& aOther) const {
+    return mHandle != aOther.mHandle;
+  }
   bool operator<(const IdNamespace& aOther) const {
     return mHandle < aOther.mHandle;
   }
   bool operator<=(const IdNamespace& aOther) const {
     return mHandle <= aOther.mHandle;
   }
 };
 
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -653,21 +653,23 @@ XPCJSRuntime::TraverseAdditionalNativeRo
     for (XPCRootSetElem* e = mVariantRoots; e ; e = e->GetNextRoot()) {
         XPCTraceableVariant* v = static_cast<XPCTraceableVariant*>(e);
         if (nsCCUncollectableMarker::InGeneration(cb,
                                                   v->CCGeneration())) {
            JS::Value val = v->GetJSValPreserveColor();
            if (val.isObject() && !JS::ObjectIsMarkedGray(&val.toObject()))
                continue;
         }
-        cb.NoteXPCOMRoot(v);
+        cb.NoteXPCOMRoot(v,
+                         XPCTraceableVariant::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant());
     }
 
     for (XPCRootSetElem* e = mWrappedJSRoots; e ; e = e->GetNextRoot()) {
-        cb.NoteXPCOMRoot(ToSupports(static_cast<nsXPCWrappedJS*>(e)));
+        cb.NoteXPCOMRoot(ToSupports(static_cast<nsXPCWrappedJS*>(e)),
+                         nsXPCWrappedJS::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant());
     }
 }
 
 void
 XPCJSRuntime::UnmarkSkippableJSHolders()
 {
     CycleCollectedJSRuntime::UnmarkSkippableJSHolders();
 }
--- a/layout/reftests/css-parsing/invalid-url-handling.xhtml
+++ b/layout/reftests/css-parsing/invalid-url-handling.xhtml
@@ -17,27 +17,26 @@
   </style>
   <style type="text/css">
   /* not a URI token, but handled according to rules for parsing errors */
   #foo { background: url(foo"bar
   ) }
   #two { background-color: green; }
   </style>
   <style type="text/css">
-  /* not a URI token; the unterminated string ends at end of line, so
-     the brace never matches */
+  /* not a URI token; bad-url token is consumed until the first closing ) */
+  #foo { background: url(foo"bar) }
   #three { background-color: green; }
-  #foo { background: url(foo"bar) }
-  #three { background-color: red; }
   </style>
   <style type="text/css">
-  /* not a URI token; the unterminated string ends at end of line */
+  /* not a URI token; bad-url token is consumed until the first closing ) */
+  #four { background-color: green; }
   #foo { background: url(foo"bar) }
   ) }
-  #four { background-color: green; }
+  #four { background-color: red; }
   </style>
   <style type="text/css">
   /* not a URI token; the unterminated string ends at end of line, so
      the brace never matches */
   #five { background-color: green; }
   #foo { background: url("bar) }
   #five { background-color: red; }
   </style>
@@ -63,28 +62,29 @@
   /* perfectly good URI token (image is a 404, though) */
   #ten { background: url({) green; }
   </style>
   <style type="text/css">
   /* perfectly good URI token (image is a 404, though) */
   #eleven { background: url([) green; }
   </style>
   <style type="text/css">
-  /* not a URI token; brace matching should work only after invalid URI token */
-  #twelve { background: url(}{""{)}); background-color: green; }
+  /* not a URI token; bad-url token is consumed until the first closing )
+     so the brace immediately after it closes the declaration block */
+  #twelve { background-color: green; }
+  #twelve { background: url(}{""{)}); background-color: red; }
   </style>
   <style type="text/css">
   /* invalid URI token absorbs the [ */
   #thirteen { background: url([""); background-color: green; }
   </style>
   <style type="text/css">
-  /* not a URI token; the opening ( is never matched */
+  /* not a URI token; bad-url token is consumed until the first closing ) */
+  #foo { background: url(() }
   #fourteen { background-color: green; }
-  #foo { background: url(() }
-  #fourteen { background-color: red; }
   </style>
   <!-- The next three tests test that invalid URI tokens absorb [ and { -->
   <style type="text/css">
   #foo { background: url(a()); }
   #fifteen { background-color: green }
   </style>
   <style type="text/css">
   #foo { background: url([()); }
--- a/layout/reftests/css-parsing/reftest.list
+++ b/layout/reftests/css-parsing/reftest.list
@@ -1,9 +1,9 @@
 == at-rule-013.html at-rule-013-ref.html
-fails-if(styloVsGecko||stylo) == invalid-url-handling.xhtml invalid-url-handling-ref.xhtml # bug 1383075
+== invalid-url-handling.xhtml invalid-url-handling-ref.xhtml
 == pseudo-elements-1.html pseudo-elements-1-ref.html
 == invalid-attr-1.html invalid-attr-1-ref.html
 == at-rule-error-handling-import-1.html at-rule-error-handling-ref.html
 == at-rule-error-handling-media-1.html at-rule-error-handling-ref.html
 == invalid-font-face-descriptor-1.html invalid-font-face-descriptor-1-ref.html
 == two-dash-identifiers.html two-dash-identifiers-ref.html
 == supports-moz-bool-pref.html supports-moz-bool-pref-ref.html
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -2725,26 +2725,38 @@ Gecko_DestroyCSSErrorReporter(ErrorRepor
   delete reporter;
 }
 
 void
 Gecko_ReportUnexpectedCSSError(ErrorReporter* reporter,
                                const char* message,
                                const char* param,
                                uint32_t paramLen,
+                               const char* prefix,
+                               const char* prefixParam,
+                               uint32_t prefixParamLen,
+                               const char* suffix,
                                const char* source,
                                uint32_t sourceLen,
                                uint32_t lineNumber,
-                               uint32_t colNumber,
-                               nsIURI* uri,
-                               const char* followup)
+                               uint32_t colNumber)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  if (prefix) {
+    if (prefixParam) {
+      nsDependentCSubstring paramValue(prefixParam, prefixParamLen);
+      nsAutoString wideParam = NS_ConvertUTF8toUTF16(paramValue);
+      reporter->ReportUnexpectedUnescaped(prefix, wideParam);
+    } else {
+      reporter->ReportUnexpected(prefix);
+    }
+  }
+
   nsDependentCSubstring paramValue(param, paramLen);
   nsAutoString wideParam = NS_ConvertUTF8toUTF16(paramValue);
   reporter->ReportUnexpectedUnescaped(message, wideParam);
-  if (followup) {
-    reporter->ReportUnexpected(followup);
+  if (suffix) {
+    reporter->ReportUnexpected(suffix);
   }
   nsDependentCSubstring sourceValue(source, sourceLen);
   reporter->OutputError(lineNumber, colNumber, sourceValue);
 }
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -675,18 +675,20 @@ void Gecko_SetJemallocThreadLocalArena(b
 mozilla::css::ErrorReporter* Gecko_CreateCSSErrorReporter(mozilla::ServoStyleSheet* sheet,
                                                           mozilla::css::Loader* loader,
                                                           nsIURI* uri);
 void Gecko_DestroyCSSErrorReporter(mozilla::css::ErrorReporter* reporter);
 void Gecko_ReportUnexpectedCSSError(mozilla::css::ErrorReporter* reporter,
                                     const char* message,
                                     const char* param,
                                     uint32_t paramLen,
+                                    const char* prefix,
+                                    const char* prefixParam,
+                                    uint32_t prefixParamLen,
+                                    const char* suffix,
                                     const char* source,
                                     uint32_t sourceLen,
                                     uint32_t lineNumber,
-                                    uint32_t colNumber,
-                                    nsIURI* aURI,
-                                    const char* followup);
+                                    uint32_t colNumber);
 
 } // extern "C"
 
 #endif // mozilla_ServoBindings_h
--- a/layout/style/nsCSSScanner.cpp
+++ b/layout/style/nsCSSScanner.cpp
@@ -1160,26 +1160,27 @@ nsCSSScanner::AppendImpliedEOFCharacters
 void
 nsCSSScanner::NextURL(nsCSSToken& aToken)
 {
   SkipWhitespace();
 
   // aToken.mIdent may be "url" at this point; clear that out
   aToken.mIdent.Truncate();
 
+  bool hasString = false;
   int32_t ch = Peek();
   // Do we have a string?
   if (ch == '"' || ch == '\'') {
     ScanString(aToken);
     if (MOZ_UNLIKELY(aToken.mType == eCSSToken_Bad_String)) {
       aToken.mType = eCSSToken_Bad_URL;
       return;
     }
     MOZ_ASSERT(aToken.mType == eCSSToken_String, "unexpected token type");
-
+    hasString = true;
   } else {
     // Otherwise, this is the start of a non-quoted url (which may be empty).
     aToken.mSymbol = char16_t(0);
     GatherText(IS_URL_CHAR, aToken.mIdent);
   }
 
   // Consume trailing whitespace and then look for a close parenthesis.
   SkipWhitespace();
@@ -1189,16 +1190,35 @@ nsCSSScanner::NextURL(nsCSSToken& aToken
     Advance();
     aToken.mType = eCSSToken_URL;
     if (ch < 0) {
       AddEOFCharacters(eEOFCharacters_CloseParen);
     }
   } else {
     mSeenBadToken = true;
     aToken.mType = eCSSToken_Bad_URL;
+    if (!hasString) {
+      // Consume until before the next right parenthesis, which follows
+      // how <bad-url-token> is consumed in CSS Syntax 3 spec.
+      // Note that, we only do this when "url(" is not followed by a
+      // string, because in the spec, "url(" followed by a string is
+      // handled as a url function rather than a <url-token>, so the
+      // rest of content before ")" should be consumed in balance,
+      // which will be done by the parser.
+      // The closing ")" is not consumed here. It is left to the parser
+      // so that the parser can handle both cases.
+      do {
+        if (IsVertSpace(ch)) {
+          AdvanceLine();
+        } else {
+          Advance();
+        }
+        ch = Peek();
+      } while (ch >= 0 && ch != ')');
+    }
   }
 }
 
 /**
  * Primary scanner entry point.  Consume one token and fill in
  * |aToken| accordingly.  Will skip over any number of comments first,
  * and will also skip over rather than return whitespace and comment
  * tokens, depending on the value of |aSkip|.
--- a/layout/style/test/test_csslexer.js
+++ b/layout/style/test/test_csslexer.js
@@ -50,18 +50,17 @@ var LEX_TESTS = [
   ["23px", ["dimension:px"]],
   ["23%", ["percentage"]],
   ["url(http://example.com)", ["url:http://example.com"]],
   ["url('http://example.com')", ["url:http://example.com"]],
   ["url(  'http://example.com'  )",
              ["url:http://example.com"]],
   // In CSS Level 3, this is an ordinary URL, not a BAD_URL.
   ["url(http://example.com", ["url:http://example.com"]],
-  // See bug 1153981 to understand why this gets a SYMBOL token.
-  ["url(http://example.com @", ["bad_url:http://example.com", "symbol:@"]],
+  ["url(http://example.com @", ["bad_url:http://example.com"]],
   ["quo\\ting", ["ident:quoting"]],
   ["'bad string\n", ["bad_string:bad string", "whitespace"]],
   ["~=", ["includes"]],
   ["|=", ["dashmatch"]],
   ["^=", ["beginsmatch"]],
   ["$=", ["endsmatch"]],
   ["*=", ["containsmatch"]],
 
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5409,17 +5409,17 @@ pref("browser.safebrowsing.provider.mozi
 pref("browser.safebrowsing.provider.mozilla.gethashURL", "https://shavar.services.mozilla.com/gethash?client=SAFEBROWSING_ID&appver=%MAJOR_VERSION%&pver=2.2");
 // Set to a date in the past to force immediate download in new profiles.
 pref("browser.safebrowsing.provider.mozilla.nextupdatetime", "1");
 // Block lists for tracking protection. The name values will be used as the keys
 // to lookup the localized name in preferences.properties.
 pref("browser.safebrowsing.provider.mozilla.lists.base.name", "mozstdName");
 pref("browser.safebrowsing.provider.mozilla.lists.base.description", "mozstdDesc");
 pref("browser.safebrowsing.provider.mozilla.lists.content.name", "mozfullName");
-pref("browser.safebrowsing.provider.mozilla.lists.content.description", "mozfullDesc");
+pref("browser.safebrowsing.provider.mozilla.lists.content.description", "mozfullDesc2");
 
 pref("urlclassifier.flashAllowTable", "allow-flashallow-digest256");
 pref("urlclassifier.flashAllowExceptTable", "except-flashallow-digest256");
 pref("urlclassifier.flashTable", "block-flash-digest256");
 pref("urlclassifier.flashExceptTable", "except-flash-digest256");
 pref("urlclassifier.flashSubDocTable", "block-flashsubdoc-digest256");
 pref("urlclassifier.flashSubDocExceptTable", "except-flashsubdoc-digest256");
 pref("urlclassifier.flashInfobarTable", "except-flashinfobar-digest256");
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -49,16 +49,17 @@
 #include "nsIDocument.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMWindowUtils.h"
 #include "nsIEventTarget.h"
 #include "nsRedirectHistoryEntry.h"
 #include "nsSocketTransportService2.h"
 #include "nsStreamUtils.h"
 #include "nsThreadUtils.h"
+#include "nsCORSListenerProxy.h"
 
 #ifdef MOZ_TASK_TRACER
 #include "GeckoTaskTracer.h"
 #endif
 
 using namespace mozilla::dom;
 using namespace mozilla::ipc;
 
@@ -3593,10 +3594,27 @@ HttpChannelChild::ActorDestroy(ActorDest
   // OnStartRequest might be dropped if IPDL is destroyed abnormally
   // and BackgroundChild might have pending IPC messages.
   // Clean up BackgroundChild at this time to prevent memleak.
   if (aWhy != Deletion) {
     CleanupBackgroundChannel();
   }
 }
 
+mozilla::ipc::IPCResult
+HttpChannelChild::RecvLogBlockedCORSRequest(const nsString& aMessage)
+{
+  Unused << LogBlockedCORSRequest(aMessage);
+  return IPC_OK();
+}
+
+NS_IMETHODIMP
+HttpChannelChild::LogBlockedCORSRequest(const nsAString & aMessage)
+{
+  if (mLoadInfo) {
+    uint64_t innerWindowID = mLoadInfo->GetInnerWindowID();
+    nsCORSListenerProxy::LogBlockedCORSRequest(innerWindowID, aMessage);
+  }
+  return NS_OK;
+}
+
 } // namespace net
 } // namespace mozilla
--- a/netwerk/protocol/http/HttpChannelChild.h
+++ b/netwerk/protocol/http/HttpChannelChild.h
@@ -174,16 +174,19 @@ protected:
 
   nsresult
   AsyncCall(void (HttpChannelChild::*funcPtr)(),
             nsRunnableMethod<HttpChannelChild> **retval = nullptr) override;
 
   // Get event target for processing network events.
   already_AddRefed<nsIEventTarget> GetNeckoTarget() override;
 
+  virtual mozilla::ipc::IPCResult RecvLogBlockedCORSRequest(const nsString& aMessage) override;
+  NS_IMETHOD LogBlockedCORSRequest(const nsAString & aMessage) override;
+
 private:
   // this section is for main-thread-only object
   // all the references need to be proxy released on main thread.
   nsCOMPtr<nsISupports> mCacheKey;
 
   // Proxy release all members above on main thread.
   void ReleaseMainThreadOnlyReferences();
 
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -227,16 +227,22 @@ HttpChannelParent::CleanupBackgroundChan
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mBgParent) {
     RefPtr<HttpBackgroundChannelParent> bgParent = mBgParent.forget();
     bgParent->OnChannelClosed();
     return;
   }
 
+  // The nsHttpChannel may have a reference to this parent, release it
+  // to avoid circular references.
+  if (mChannel) {
+    mChannel->SetWarningReporter(nullptr);
+  }
+
   if (!mPromise.IsEmpty()) {
     mRequest.DisconnectIfExists();
     mPromise.Reject(NS_ERROR_FAILURE, __func__);
 
     if (!mChannel) {
       return;
     }
 
@@ -800,16 +806,18 @@ HttpChannelParent::ConnectChannel(const 
   LOG(("  found channel %p, rv=%08" PRIx32, channel.get(), static_cast<uint32_t>(rv)));
   mChannel = do_QueryObject(channel);
   if (!mChannel) {
     LOG(("  but it's not nsHttpChannel"));
     Delete();
     return true;
   }
 
+  mChannel->SetWarningReporter(this);
+
   nsCOMPtr<nsINetworkInterceptController> controller;
   NS_QueryNotificationCallbacks(channel, controller);
   RefPtr<HttpChannelParentListener> parentListener = do_QueryObject(controller);
   MOZ_ASSERT(parentListener);
   parentListener->SetupInterceptionAfterRedirect(shouldIntercept);
 
   if (mPBOverride != kPBOverride_Unset) {
     // redirected-to channel may not support PB
@@ -1539,16 +1547,18 @@ HttpChannelParent::OnStopRequest(nsIRequ
   mChannel->GetEncodedBodySize(&timing.encodedBodySize);
   // decodedBodySize can be computed in the child process so it doesn't need
   // to be passed down.
   mChannel->GetProtocolVersion(timing.protocolVersion);
 
   mChannel->GetCacheReadStart(&timing.cacheReadStart);
   mChannel->GetCacheReadEnd(&timing.cacheReadEnd);
 
+  mChannel->SetWarningReporter(nullptr);
+
   // Either IPC channel is closed or background channel
   // is ready to send OnStopRequest.
   MOZ_ASSERT(mIPCClosed || mBgParent);
 
   if (mIPCClosed ||
       !mBgParent || !mBgParent->OnStopRequest(aStatusCode, timing)) {
     return NS_ERROR_UNEXPECTED;
   }
@@ -2222,10 +2232,20 @@ HttpChannelParent::ReadyToVerify(nsresul
 void
 HttpChannelParent::DoSendSetPriority(int16_t aValue)
 {
   if (!mIPCClosed) {
     Unused << SendSetPriority(aValue);
   }
 }
 
+nsresult
+HttpChannelParent::LogBlockedCORSRequest(const nsAString& aMessage)
+{
+  if (mIPCClosed ||
+      NS_WARN_IF(!SendLogBlockedCORSRequest(nsString(aMessage)))) {
+    return NS_ERROR_UNEXPECTED;
+  }
+  return NS_OK;
+}
+
 } // namespace net
 } // namespace mozilla
--- a/netwerk/protocol/http/HttpChannelParent.h
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -209,16 +209,17 @@ protected:
   void FailDiversion(nsresult aErrorCode);
 
   friend class HttpChannelParentListener;
   RefPtr<mozilla::dom::TabParent> mTabParent;
 
   MOZ_MUST_USE nsresult
   ReportSecurityMessage(const nsAString& aMessageTag,
                         const nsAString& aMessageCategory) override;
+  nsresult LogBlockedCORSRequest(const nsAString& aMessage) override;
 
   // Calls SendDeleteSelf and sets mIPCClosed to true because we should not
   // send any more messages after that. Bug 1274886
   MOZ_MUST_USE bool DoSendDeleteSelf();
   // Called to notify the parent channel to not send any more IPC messages.
   virtual mozilla::ipc::IPCResult RecvDeletingChannel() override;
   virtual mozilla::ipc::IPCResult RecvFinishInterceptedRedirect() override;
 
--- a/netwerk/protocol/http/NullHttpChannel.cpp
+++ b/netwerk/protocol/http/NullHttpChannel.cpp
@@ -849,16 +849,22 @@ NullHttpChannel::GetIsMainDocumentChanne
 }
 
 NS_IMETHODIMP
 NullHttpChannel::SetIsMainDocumentChannel(bool aValue)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
+NS_IMETHODIMP
+NullHttpChannel::LogBlockedCORSRequest(const nsAString& aMessage)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
 #define IMPL_TIMING_ATTR(name)                                 \
 NS_IMETHODIMP                                                  \
 NullHttpChannel::Get##name##Time(PRTime* _retval) {            \
     TimeStamp stamp;                                           \
     Get##name(&stamp);                                         \
     if (stamp.IsNull()) {                                      \
         *_retval = 0;                                          \
         return NS_OK;                                          \
--- a/netwerk/protocol/http/PHttpChannel.ipdl
+++ b/netwerk/protocol/http/PHttpChannel.ipdl
@@ -136,16 +136,21 @@ child:
 
   // Tell child to delete channel (all IPDL deletes must be done from child to
   // avoid races: see bug 591708).
   async DeleteSelf();
 
   // Tell the child to issue a deprecation warning.
   async IssueDeprecationWarning(uint32_t warning, bool asError);
 
+  // When CORS blocks the request in the parent process, it doesn't have the
+  // correct window ID, so send the message to the child for logging to the web
+  // console.
+  async LogBlockedCORSRequest(nsString message);
+
 both:
   // After receiving this message, the parent also calls
   // SendFinishInterceptedRedirect, and makes sure not to send any more messages
   // after that. When receiving this message, the child will call
   // Send__delete__() and complete the steps required to finish the redirect.
   async FinishInterceptedRedirect();
 
   async SetPriority(int16_t priority);
--- a/netwerk/protocol/http/nsCORSListenerProxy.cpp
+++ b/netwerk/protocol/http/nsCORSListenerProxy.cpp
@@ -37,48 +37,37 @@
 #include "nsIConsoleService.h"
 #include "nsIDOMNode.h"
 #include "nsIDOMWindowUtils.h"
 #include "nsIDOMWindow.h"
 #include "nsINetworkInterceptController.h"
 #include "NullPrincipal.h"
 #include "nsICorsPreflightCallback.h"
 #include "nsISupportsImpl.h"
+#include "nsHttpChannel.h"
 #include "mozilla/LoadInfo.h"
 #include "nsIHttpHeaderVisitor.h"
+#include "nsQueryObject.h"
 #include <algorithm>
 
 using namespace mozilla;
 
 #define PREFLIGHT_CACHE_SIZE 100
 
 static bool gDisableCORS = false;
 static bool gDisableCORSPrivateData = false;
 
 static void
 LogBlockedRequest(nsIRequest* aRequest,
                   const char* aProperty,
-                  const char16_t* aParam)
+                  const char16_t* aParam,
+                  nsIHttpChannel* aCreatingChannel)
 {
   nsresult rv = NS_OK;
 
-  // Build the error object and log it to the console
-  nsCOMPtr<nsIConsoleService> console(do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv));
-  if (NS_FAILED(rv)) {
-    NS_WARNING("Failed to log blocked cross-site request (no console)");
-    return;
-  }
-
-  nsCOMPtr<nsIScriptError> scriptError =
-    do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
-  if (NS_FAILED(rv)) {
-    NS_WARNING("Failed to log blocked cross-site request (no scriptError)");
-    return;
-  }
-
   nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
   nsCOMPtr<nsIURI> aUri;
   channel->GetURI(getter_AddRefs(aUri));
   nsAutoCString spec;
   if (aUri) {
     spec = aUri->GetSpecOrDefault();
   }
 
@@ -93,44 +82,29 @@ LogBlockedRequest(nsIRequest* aRequest,
 
   if (NS_FAILED(rv)) {
     NS_WARNING("Failed to log blocked cross-site request (no formalizedStr");
     return;
   }
 
   nsAutoString msg(blockedMessage.get());
 
-  // query innerWindowID and log to web console, otherwise log to
-  // the error to the browser console.
-  uint64_t innerWindowID = nsContentUtils::GetInnerWindowID(aRequest);
-
-  if (innerWindowID > 0) {
-    rv = scriptError->InitWithWindowID(msg,
-                                       EmptyString(), // sourceName
-                                       EmptyString(), // sourceLine
-                                       0,             // lineNumber
-                                       0,             // columnNumber
-                                       nsIScriptError::warningFlag,
-                                       "CORS",
-                                       innerWindowID);
+  if (XRE_IsParentProcess()) {
+    if (aCreatingChannel) {
+      rv = aCreatingChannel->LogBlockedCORSRequest(msg);
+      if (NS_SUCCEEDED(rv)) {
+        return;
+      }
+    }
+    NS_WARNING("Failed to log blocked cross-site request to web console from parent->child, falling back to browser console");
   }
-  else {
-    rv = scriptError->Init(msg,
-                           EmptyString(), // sourceName
-                           EmptyString(), // sourceLine
-                           0,             // lineNumber
-                           0,             // columnNumber
-                           nsIScriptError::warningFlag,
-                           "CORS");
-  }
-  if (NS_FAILED(rv)) {
-    NS_WARNING("Failed to log blocked cross-site request (scriptError init failed)");
-    return;
-  }
-  console->LogMessage(scriptError);
+
+  // log message ourselves
+  uint64_t innerWindowID = nsContentUtils::GetInnerWindowID(aRequest);
+  nsCORSListenerProxy::LogBlockedCORSRequest(innerWindowID, msg);
 }
 
 //////////////////////////////////////////////////////////////////////////
 // Preflight cache
 
 class nsPreflightCache
 {
 public:
@@ -446,16 +420,17 @@ nsCORSListenerProxy::Init(nsIChannel* aC
   aChannel->SetNotificationCallbacks(this);
 
   nsresult rv = UpdateChannel(aChannel, aAllowDataURI, UpdateType::Default);
   if (NS_FAILED(rv)) {
     mOuterListener = nullptr;
     mRequestingPrincipal = nullptr;
     mOriginHeaderPrincipal = nullptr;
     mOuterNotificationCallbacks = nullptr;
+    mHttpChannel = nullptr;
   }
 #ifdef DEBUG
   mInited = true;
 #endif
   return rv;
 }
 
 NS_IMETHODIMP
@@ -535,19 +510,21 @@ NS_IMPL_ISUPPORTS(CheckOriginHeader, nsI
 
 nsresult
 nsCORSListenerProxy::CheckRequestApproved(nsIRequest* aRequest)
 {
   // Check if this was actually a cross domain request
   if (!mHasBeenCrossSite) {
     return NS_OK;
   }
+  nsCOMPtr<nsIHttpChannel> topChannel;
+  topChannel.swap(mHttpChannel);
 
   if (gDisableCORS) {
-    LogBlockedRequest(aRequest, "CORSDisabled", nullptr);
+    LogBlockedRequest(aRequest, "CORSDisabled", nullptr, topChannel);
     return NS_ERROR_DOM_BAD_URI;
   }
 
   // Check if the request failed
   nsresult status;
   nsresult rv = aRequest->GetStatus(&status);
   if (NS_FAILED(rv)) {
    return rv;
@@ -555,17 +532,17 @@ nsCORSListenerProxy::CheckRequestApprove
 
   if (NS_FAILED(status)) {
     return status;
   }
 
   // Test that things worked on a HTTP level
   nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest);
   if (!http) {
-    LogBlockedRequest(aRequest, "CORSRequestNotHttp", nullptr);
+    LogBlockedRequest(aRequest, "CORSRequestNotHttp", nullptr, topChannel);
     return NS_ERROR_DOM_BAD_URI;
   }
 
   nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(aRequest);
   NS_ENSURE_STATE(internal);
   bool responseSynthesized = false;
   if (NS_SUCCEEDED(internal->GetResponseSynthesized(&responseSynthesized)) &&
       responseSynthesized) {
@@ -577,76 +554,77 @@ nsCORSListenerProxy::CheckRequestApprove
 
   // Check the Access-Control-Allow-Origin header
   RefPtr<CheckOriginHeader> visitor = new CheckOriginHeader();
   nsAutoCString allowedOriginHeader;
 
   // check for duplicate headers
   rv = http->VisitOriginalResponseHeaders(visitor);
   if (NS_FAILED(rv)) {
-    LogBlockedRequest(aRequest, "CORSAllowOriginNotMatchingOrigin", nullptr);
+    LogBlockedRequest(aRequest, "CORSAllowOriginNotMatchingOrigin", nullptr, topChannel);
     return rv;
   }
 
   rv = http->GetResponseHeader(
     NS_LITERAL_CSTRING("Access-Control-Allow-Origin"), allowedOriginHeader);
   if (NS_FAILED(rv)) {
-    LogBlockedRequest(aRequest, "CORSMissingAllowOrigin", nullptr);
+    LogBlockedRequest(aRequest, "CORSMissingAllowOrigin", nullptr, topChannel);
     return rv;
   }
 
   // Bug 1210985 - Explicitly point out the error that the credential is
   // not supported if the allowing origin is '*'. Note that this check
   // has to be done before the condition
   //
   // >> if (mWithCredentials || !allowedOriginHeader.EqualsLiteral("*"))
   //
   // below since "if (A && B)" is included in "if (A || !B)".
   //
   if (mWithCredentials && allowedOriginHeader.EqualsLiteral("*")) {
-    LogBlockedRequest(aRequest, "CORSNotSupportingCredentials", nullptr);
+    LogBlockedRequest(aRequest, "CORSNotSupportingCredentials", nullptr, topChannel);
     return NS_ERROR_DOM_BAD_URI;
   }
 
   if (mWithCredentials || !allowedOriginHeader.EqualsLiteral("*")) {
     MOZ_ASSERT(!nsContentUtils::IsExpandedPrincipal(mOriginHeaderPrincipal));
     nsAutoCString origin;
     nsContentUtils::GetASCIIOrigin(mOriginHeaderPrincipal, origin);
 
     if (!allowedOriginHeader.Equals(origin)) {
       LogBlockedRequest(aRequest, "CORSAllowOriginNotMatchingOrigin",
-                        NS_ConvertUTF8toUTF16(allowedOriginHeader).get());
+                        NS_ConvertUTF8toUTF16(allowedOriginHeader).get(), topChannel);
       return NS_ERROR_DOM_BAD_URI;
     }
   }
 
   // Check Access-Control-Allow-Credentials header
   if (mWithCredentials) {
     nsAutoCString allowCredentialsHeader;
     rv = http->GetResponseHeader(
       NS_LITERAL_CSTRING("Access-Control-Allow-Credentials"), allowCredentialsHeader);
 
     if (!allowCredentialsHeader.EqualsLiteral("true")) {
-      LogBlockedRequest(aRequest, "CORSMissingAllowCredentials", nullptr);
+      LogBlockedRequest(aRequest, "CORSMissingAllowCredentials", nullptr, topChannel);
       return NS_ERROR_DOM_BAD_URI;
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsCORSListenerProxy::OnStopRequest(nsIRequest* aRequest,
                                    nsISupports* aContext,
                                    nsresult aStatusCode)
 {
   MOZ_ASSERT(mInited, "nsCORSListenerProxy has not been initialized properly");
   nsresult rv = mOuterListener->OnStopRequest(aRequest, aContext, aStatusCode);
   mOuterListener = nullptr;
   mOuterNotificationCallbacks = nullptr;
+  mHttpChannel = nullptr;
   return rv;
 }
 
 NS_IMETHODIMP
 nsCORSListenerProxy::OnDataAvailable(nsIRequest* aRequest,
                                      nsISupports* aContext,
                                      nsIInputStream* aInputStream,
                                      uint64_t aOffset,
@@ -989,16 +967,18 @@ nsCORSListenerProxy::UpdateChannel(nsICh
     rv = http->GetLoadFlags(&flags);
     NS_ENSURE_SUCCESS(rv, rv);
 
     flags |= nsIRequest::LOAD_ANONYMOUS;
     rv = http->SetLoadFlags(flags);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
+  mHttpChannel = http;
+
   return NS_OK;
 }
 
 nsresult
 nsCORSListenerProxy::CheckPreflightNeeded(nsIChannel* aChannel, UpdateType aUpdateType)
 {
   // If this caller isn't using AsyncOpen2, or if this *is* a preflight channel,
   // then we shouldn't initiate preflight for this channel.
@@ -1305,21 +1285,22 @@ nsCORSPreflightListener::CheckPreflightR
   nsresult rv = aRequest->GetStatus(&status);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_SUCCESS(status, status);
 
   // Test that things worked on a HTTP level
   nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest);
   nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(aRequest);
   NS_ENSURE_STATE(internal);
+  nsCOMPtr<nsIHttpChannel> parentHttpChannel = do_QueryInterface(mCallback);
 
   bool succeedded;
   rv = http->GetRequestSucceeded(&succeedded);
   if (NS_FAILED(rv) || !succeedded) {
-    LogBlockedRequest(aRequest, "CORSPreflightDidNotSucceed", nullptr);
+    LogBlockedRequest(aRequest, "CORSPreflightDidNotSucceed", nullptr, parentHttpChannel);
     return NS_ERROR_DOM_BAD_URI;
   }
 
   nsAutoCString headerVal;
   // The "Access-Control-Allow-Methods" header contains a comma separated
   // list of method names.
   Unused << http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Methods"),
                                     headerVal);
@@ -1329,49 +1310,49 @@ nsCORSPreflightListener::CheckPreflightR
   nsCCharSeparatedTokenizer methodTokens(headerVal, ',');
   while(methodTokens.hasMoreTokens()) {
     const nsDependentCSubstring& method = methodTokens.nextToken();
     if (method.IsEmpty()) {
       continue;
     }
     if (!NS_IsValidHTTPToken(method)) {
       LogBlockedRequest(aRequest, "CORSInvalidAllowMethod",
-                        NS_ConvertUTF8toUTF16(method).get());
+                        NS_ConvertUTF8toUTF16(method).get(), parentHttpChannel);
       return NS_ERROR_DOM_BAD_URI;
     }
     foundMethod |= mPreflightMethod.Equals(method);
   }
   if (!foundMethod) {
-    LogBlockedRequest(aRequest, "CORSMethodNotFound", nullptr);
+    LogBlockedRequest(aRequest, "CORSMethodNotFound", nullptr, parentHttpChannel);
     return NS_ERROR_DOM_BAD_URI;
   }
 
   // The "Access-Control-Allow-Headers" header contains a comma separated
   // list of header names.
   Unused << http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Headers"),
                                     headerVal);
   nsTArray<nsCString> headers;
   nsCCharSeparatedTokenizer headerTokens(headerVal, ',');
   while(headerTokens.hasMoreTokens()) {
     const nsDependentCSubstring& header = headerTokens.nextToken();
     if (header.IsEmpty()) {
       continue;
     }
     if (!NS_IsValidHTTPToken(header)) {
       LogBlockedRequest(aRequest, "CORSInvalidAllowHeader",
-                        NS_ConvertUTF8toUTF16(header).get());
+                        NS_ConvertUTF8toUTF16(header).get(), parentHttpChannel);
       return NS_ERROR_DOM_BAD_URI;
     }
     headers.AppendElement(header);
   }
   for (uint32_t i = 0; i < mPreflightHeaders.Length(); ++i) {
     if (!headers.Contains(mPreflightHeaders[i],
                           nsCaseInsensitiveCStringArrayComparator())) {
       LogBlockedRequest(aRequest, "CORSMissingAllowHeaderFromPreflight",
-                        NS_ConvertUTF8toUTF16(mPreflightHeaders[i]).get());
+                        NS_ConvertUTF8toUTF16(mPreflightHeaders[i]).get(), parentHttpChannel);
       return NS_ERROR_DOM_BAD_URI;
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -1391,26 +1372,28 @@ nsCORSListenerProxy::RemoveFromCorsPrefl
                                                   nsIPrincipal* aRequestingPrincipal)
 {
   MOZ_ASSERT(XRE_IsParentProcess());
   if (sPreflightCache) {
     sPreflightCache->RemoveEntries(aURI, aRequestingPrincipal);
   }
 }
 
+// static
 nsresult
 nsCORSListenerProxy::StartCORSPreflight(nsIChannel* aRequestChannel,
                                         nsICorsPreflightCallback* aCallback,
                                         nsTArray<nsCString>& aUnsafeHeaders,
                                         nsIChannel** aPreflightChannel)
 {
   *aPreflightChannel = nullptr;
 
   if (gDisableCORS) {
-    LogBlockedRequest(aRequestChannel, "CORSDisabled", nullptr);
+    nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequestChannel);
+    LogBlockedRequest(aRequestChannel, "CORSDisabled", nullptr, http);
     return NS_ERROR_DOM_BAD_URI;
   }
 
   nsAutoCString method;
   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequestChannel));
   NS_ENSURE_TRUE(httpChannel, NS_ERROR_UNEXPECTED);
   Unused << httpChannel->GetRequestMethod(method);
 
@@ -1496,16 +1479,23 @@ nsCORSListenerProxy::StartCORSPreflight(
   rv = preHttp->SetRequestMethod(NS_LITERAL_CSTRING("OPTIONS"));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = preHttp->
     SetRequestHeader(NS_LITERAL_CSTRING("Access-Control-Request-Method"),
                      method, false);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  // Set the CORS preflight channel's warning reporter to be the same as the
+  // requesting channel so that all log messages are able to be reported through
+  // the warning reporter.
+  RefPtr<nsHttpChannel> reqCh = do_QueryObject(aRequestChannel);
+  RefPtr<nsHttpChannel> preCh = do_QueryObject(preHttp);
+  preCh->SetWarningReporter(reqCh->GetWarningReporter());
+
   nsTArray<nsCString> preflightHeaders;
   if (!aUnsafeHeaders.IsEmpty()) {
     for (uint32_t i = 0; i < aUnsafeHeaders.Length(); ++i) {
       preflightHeaders.AppendElement();
       ToLowerCase(aUnsafeHeaders[i], preflightHeaders[i]);
     }
     preflightHeaders.Sort();
     nsAutoCString headers;
@@ -1533,8 +1523,57 @@ nsCORSListenerProxy::StartCORSPreflight(
   rv = preflightChannel->AsyncOpen2(preflightListener);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Return newly created preflight channel
   preflightChannel.forget(aPreflightChannel);
 
   return NS_OK;
 }
+
+// static
+void
+nsCORSListenerProxy::LogBlockedCORSRequest(uint64_t aInnerWindowID,
+                                           const nsAString& aMessage)
+{
+  nsresult rv = NS_OK;
+
+  // Build the error object and log it to the console
+  nsCOMPtr<nsIConsoleService> console(do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv));
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Failed to log blocked cross-site request (no console)");
+    return;
+  }
+
+  nsCOMPtr<nsIScriptError> scriptError =
+    do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Failed to log blocked cross-site request (no scriptError)");
+    return;
+  }
+
+  // query innerWindowID and log to web console, otherwise log to
+  // the error to the browser console.
+  if (aInnerWindowID > 0) {
+    rv = scriptError->InitWithWindowID(aMessage,
+                                       EmptyString(), // sourceName
+                                       EmptyString(), // sourceLine
+                                       0,             // lineNumber
+                                       0,             // columnNumber
+                                       nsIScriptError::warningFlag,
+                                       "CORS",
+                                       aInnerWindowID);
+  }
+  else {
+    rv = scriptError->Init(aMessage,
+                           EmptyString(), // sourceName
+                           EmptyString(), // sourceLine
+                           0,             // lineNumber
+                           0,             // columnNumber
+                           nsIScriptError::warningFlag,
+                           "CORS");
+  }
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Failed to log blocked cross-site request (scriptError init failed)");
+    return;
+  }
+  console->LogMessage(scriptError);
+}
--- a/netwerk/protocol/http/nsCORSListenerProxy.h
+++ b/netwerk/protocol/http/nsCORSListenerProxy.h
@@ -65,16 +65,20 @@ public:
 
   static void Shutdown();
 
   MOZ_MUST_USE nsresult Init(nsIChannel* aChannel,
                              DataURIHandling aAllowDataURI);
 
   void SetInterceptController(nsINetworkInterceptController* aInterceptController);
 
+  // When CORS blocks a request, log the message to the web console, or the
+  // browser console if no valid inner window ID is found.
+  static void LogBlockedCORSRequest(uint64_t aInnerWindowID,
+                                    const nsAString& aMessage);
 private:
   // Only HttpChannelParent can call RemoveFromCorsPreflightCache
   friend class mozilla::net::HttpChannelParent;
   // Only nsHttpChannel can invoke CORS preflights
   friend class mozilla::net::nsHttpChannel;
 
   static void RemoveFromCorsPreflightCache(nsIURI* aURI,
                                            nsIPrincipal* aRequestingPrincipal);
@@ -103,14 +107,18 @@ private:
   nsCOMPtr<nsINetworkInterceptController> mInterceptController;
   bool mWithCredentials;
   bool mRequestApproved;
   // Please note that the member variable mHasBeenCrossSite may rely on the
   // promise that the CSP directive 'upgrade-insecure-requests' upgrades
   // an http: request to https: in nsHttpChannel::Connect() and hence
   // a request might not be marked as cross site request based on that promise.
   bool mHasBeenCrossSite;
+  // Under e10s, logging happens in the child process. Keep a reference to the
+  // creator nsIHttpChannel in order to find the way back to the child. Released
+  // in OnStopRequest().
+  nsCOMPtr<nsIHttpChannel> mHttpChannel;
 #ifdef DEBUG
   bool mInited;
 #endif
 };
 
 #endif
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -411,16 +411,25 @@ nsHttpChannel::AddSecurityMessage(const 
     if (mWarningReporter) {
         return mWarningReporter->ReportSecurityMessage(aMessageTag,
                                                        aMessageCategory);
     }
     return HttpBaseChannel::AddSecurityMessage(aMessageTag,
                                                aMessageCategory);
 }
 
+NS_IMETHODIMP
+nsHttpChannel::LogBlockedCORSRequest(const nsAString& aMessage)
+{
+    if (mWarningReporter) {
+        return mWarningReporter->LogBlockedCORSRequest(aMessage);
+    }
+    return NS_ERROR_UNEXPECTED;
+}
+
 //-----------------------------------------------------------------------------
 // nsHttpChannel <private>
 //-----------------------------------------------------------------------------
 
 nsresult
 nsHttpChannel::Connect()
 {
     nsresult rv;
@@ -853,16 +862,17 @@ nsHttpChannel::DoNotifyListenerCleanup()
     CleanRedirectCacheChainIfNecessary();
 }
 
 void
 nsHttpChannel::ReleaseListeners()
 {
     HttpBaseChannel::ReleaseListeners();
     mChannelClassifier = nullptr;
+    mWarningReporter = nullptr;
 }
 
 void
 nsHttpChannel::HandleAsyncRedirect()
 {
     NS_PRECONDITION(!mCallOnResume, "How did that happen?");
 
     if (mSuspendCount) {
@@ -5917,16 +5927,17 @@ nsHttpChannel::Cancel(nsresult status)
     MOZ_ASSERT_IF(mPreflightChannel, !mCachePump);
 
     LOG(("nsHttpChannel::Cancel [this=%p status=%" PRIx32 "]\n",
          this, static_cast<uint32_t>(status)));
     if (mCanceled) {
         LOG(("  ignoring; already canceled\n"));
         return NS_OK;
     }
+
     if (mWaitingForRedirectCallback) {
         LOG(("channel canceled during wait for redirect callback"));
     }
     mCanceled = true;
     mStatus = status;
     if (mProxyRequest)
         mProxyRequest->Cancel(status);
     CancelNetworkRequest(status);
@@ -9245,10 +9256,24 @@ nsHttpChannel::Notify(nsITimer *aTimer)
         return TriggerNetwork(0);
     } else {
         MOZ_CRASH("Unknown timer");
     }
 
     return NS_OK;
 }
 
+void
+nsHttpChannel::SetWarningReporter(HttpChannelSecurityWarningReporter *aReporter)
+{
+    LOG(("nsHttpChannel [this=%p] SetWarningReporter [%p]", this, aReporter));
+    mWarningReporter = aReporter;
+}
+
+HttpChannelSecurityWarningReporter*
+nsHttpChannel::GetWarningReporter()
+{
+    LOG(("nsHttpChannel [this=%p] GetWarningReporter [%p]", this, mWarningReporter.get()));
+    return mWarningReporter.get();
+}
+
 } // namespace net
 } // namespace mozilla
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -38,22 +38,23 @@ class nsIHttpChannelAuthProvider;
 class nsInputStreamPump;
 class nsISSLStatus;
 
 namespace mozilla { namespace net {
 
 class nsChannelClassifier;
 class Http2PushedStream;
 
-class HttpChannelSecurityWarningReporter
+class HttpChannelSecurityWarningReporter : public nsISupports
 {
 public:
   virtual MOZ_MUST_USE nsresult
   ReportSecurityMessage(const nsAString& aMessageTag,
                         const nsAString& aMessageCategory) = 0;
+  virtual nsresult LogBlockedCORSRequest(const nsAString& aMessage) = 0;
 };
 
 //-----------------------------------------------------------------------------
 // nsHttpChannel
 //-----------------------------------------------------------------------------
 
 // Use to support QI nsIChannel to nsHttpChannel
 #define NS_HTTPCHANNEL_IID                         \
@@ -185,20 +186,20 @@ public:
     NS_IMETHOD GetResponseEnd(mozilla::TimeStamp *aResponseEnd) override;
     // nsICorsPreflightCallback
     NS_IMETHOD OnPreflightSucceeded() override;
     NS_IMETHOD OnPreflightFailed(nsresult aError) override;
 
     MOZ_MUST_USE nsresult
     AddSecurityMessage(const nsAString& aMessageTag,
                        const nsAString& aMessageCategory) override;
+    NS_IMETHOD LogBlockedCORSRequest(const nsAString& aMessage) override;
 
-    void SetWarningReporter(HttpChannelSecurityWarningReporter* aReporter)
-      { mWarningReporter = aReporter; }
-
+    void SetWarningReporter(HttpChannelSecurityWarningReporter *aReporter);
+    HttpChannelSecurityWarningReporter* GetWarningReporter();
 public: /* internal necko use only */
 
     using InitLocalBlockListCallback = std::function<void(bool)>;
 
     void InternalSetUploadStream(nsIInputStream *uploadStream)
       { mUploadStream = uploadStream; }
     void SetUploadStreamHasHeaders(bool hasHeaders)
       { mUploadStreamHasHeaders = hasHeaders; }
@@ -667,17 +668,17 @@ private:
 
     MOZ_MUST_USE nsresult WaitForRedirectCallback();
     void PushRedirectAsyncFunc(nsContinueRedirectionFunc func);
     void PopRedirectAsyncFunc(nsContinueRedirectionFunc func);
 
     nsCString mUsername;
 
     // If non-null, warnings should be reported to this object.
-    HttpChannelSecurityWarningReporter* mWarningReporter;
+    RefPtr<HttpChannelSecurityWarningReporter> mWarningReporter;
 
     RefPtr<ADivertableParentChannel> mParentChannel;
 
     // True if the channel is reading from cache.
     Atomic<bool> mIsReadingFromCache;
 
     // These next members are only used in unit tests to delay the call to
     // cache->AsyncOpenURI in order to race the cache with the network.
--- a/netwerk/protocol/http/nsIHttpChannel.idl
+++ b/netwerk/protocol/http/nsIHttpChannel.idl
@@ -473,9 +473,19 @@ interface nsIHttpChannel : nsIChannel
     /**
      * ID of the top-level outer content window. Identifies this channel's
      * top-level window it comes from.
      *
      * NOTE: The setter of this attribute is currently for xpcshell test only.
      *       Don't alter it otherwise.
      */
     [must_use] attribute uint64_t topLevelOuterContentWindowId;
+
+    /**
+     * In e10s, the information that the CORS response blocks the load is in the
+     * parent, which doesn't know the true window id of the request, so we may
+     * need to proxy the request to the child.
+     *
+     * @param aMessage
+     *        The message to print in the console.
+     */
+    void logBlockedCORSRequest(in AString aMessage);
 };
--- a/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
+++ b/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
@@ -1039,8 +1039,18 @@ nsViewSourceChannel::SetIsMainDocumentCh
 }
 
 // Have to manually forward SetCorsPreflightParameters since it's [notxpcom]
 void
 nsViewSourceChannel::SetCorsPreflightParameters(const nsTArray<nsCString>& aUnsafeHeaders)
 {
   mHttpChannelInternal->SetCorsPreflightParameters(aUnsafeHeaders);
 }
+
+NS_IMETHODIMP
+nsViewSourceChannel::LogBlockedCORSRequest(const nsAString& aMessage)
+{
+  if (!mHttpChannel) {
+    NS_WARNING("nsViewSourceChannel::LogBlockedCORSRequest mHttpChannel is null");
+    return NS_ERROR_UNEXPECTED;
+  }
+  return mHttpChannel->LogBlockedCORSRequest(aMessage);
+}
--- a/services/sync/modules/bookmark_repair.js
+++ b/services/sync/modules/bookmark_repair.js
@@ -106,17 +106,20 @@ class BookmarkRepairRequestor extends Co
     // report duplicates -- if the server is missing a record, it is unlikely
     // to cause only a single problem.
     let ids = new Set();
 
     // Note that we allow any of the validation problem fields to be missing so
     // that tests have a slightly easier time, hence the `|| []` in each loop.
 
     // Missing children records when the parent exists but a child doesn't.
-    for (let { child } of validationInfo.problems.missingChildren || []) {
+    for (let { parent, child } of validationInfo.problems.missingChildren || []) {
+      // We can't be sure if the child is missing or our copy of the parent is
+      // wrong, so request both
+      ids.add(parent);
       ids.add(child);
     }
     if (ids.size > MAX_REQUESTED_IDS) {
       return ids; // might as well give up here - we aren't going to repair.
     }
 
     // Orphans are when the child exists but the parent doesn't.
     // This could either be a problem in the child (it's wrong about the node
@@ -125,28 +128,32 @@ class BookmarkRepairRequestor extends Co
       // Request both, to handle both cases
       ids.add(id);
       ids.add(parent);
     }
     if (ids.size > MAX_REQUESTED_IDS) {
       return ids; // might as well give up here - we aren't going to repair.
     }
 
-    // Entries where we have the parent but know for certain that the child was
-    // deleted.
-    for (let { parent } of validationInfo.problems.deletedChildren || []) {
+    // Entries where we have the parent but we have a record from the server that
+    // claims the child was deleted.
+    for (let { parent, child } of validationInfo.problems.deletedChildren || []) {
+      // Request both, since we don't know if it's a botched deletion or revival
       ids.add(parent);
+      ids.add(child);
     }
     if (ids.size > MAX_REQUESTED_IDS) {
       return ids; // might as well give up here - we aren't going to repair.
     }
 
     // Entries where the child references a parent that we don't have, but we
-    // know why: the parent was deleted.
-    for (let { child } of validationInfo.problems.deletedParents || []) {
+    // have a record from the server that claims the parent was deleted.
+    for (let { parent, child } of validationInfo.problems.deletedParents || []) {
+      // Request both, since we don't know if it's a botched deletion or revival
+      ids.add(parent);
       ids.add(child);
     }
     if (ids.size > MAX_REQUESTED_IDS) {
       return ids; // might as well give up here - we aren't going to repair.
     }
 
     // Cases where the parent and child disagree about who the parent is.
     for (let { parent, child } of validationInfo.problems.parentChildMismatches || []) {
@@ -164,17 +171,16 @@ class BookmarkRepairRequestor extends Co
     // child is right.
     for (let { parents, child } of validationInfo.problems.multipleParents || []) {
       for (let parent of parents) {
         ids.add(parent);
       }
       ids.add(child);
     }
 
-    // XXX - any others we should consider?
     return ids;
   }
 
   _countServerOnlyFixableProblems(validationInfo) {
     const fixableProblems = ["clientMissing", "serverMissing", "serverDeleted"];
     return fixableProblems.reduce((numProblems, problemLabel) => {
       return numProblems + validationInfo.problems[problemLabel].length;
     }, 0);
--- a/services/sync/tests/unit/test_bookmark_repair.js
+++ b/services/sync/tests/unit/test_bookmark_repair.js
@@ -158,34 +158,34 @@ add_task(async function test_bookmark_re
     await validationPromise;
     let flowID = Svc.Prefs.get("repairs.bookmarks.flowID");
     checkRecordedEvents([{
       object: "repair",
       method: "started",
       value: undefined,
       extra: {
         flowID,
-        numIDs: "1",
+        numIDs: "2",
       },
     }, {
       object: "sendcommand",
       method: "repairRequest",
       value: undefined,
       extra: {
         flowID,
         deviceID: Service.identity.hashedDeviceID(remoteID),
       },
     }, {
       object: "repair",
       method: "request",
       value: "upload",
       extra: {
         deviceID: Service.identity.hashedDeviceID(remoteID),
         flowID,
-        numIDs: "1",
+        numIDs: "2",
       },
     }], "Should record telemetry events for repair request");
 
     // We should have started a repair with our second client.
     equal((await clientsEngine.getClientCommands(remoteID)).length, 1,
       "Should queue repair request for remote client after repair");
     _("Sync to send outgoing repair request");
     await Service.sync();
@@ -220,33 +220,33 @@ add_task(async function test_bookmark_re
         flowID,
       },
     }, {
       object: "repairResponse",
       method: "uploading",
       value: undefined,
       extra: {
         flowID,
-        numIDs: "1",
+        numIDs: "2",
       },
     }, {
       object: "sendcommand",
       method: "repairResponse",
       value: undefined,
       extra: {
         flowID,
         deviceID: Service.identity.hashedDeviceID(initialID),
       },
     }, {
       object: "repairResponse",
       method: "finished",
       value: undefined,
       extra: {
         flowID,
-        numIDs: "1",
+        numIDs: "2",
       }
     }], "Should record telemetry events for repair response");
 
     // We should queue the repair response for the initial client.
     equal((await remoteClientsEngine.getClientCommands(initialID)).length, 1,
       "Should queue repair response for initial client after repair");
     ok(user.collection("bookmarks").wbo(bookmarkInfo.guid),
       "Should upload missing bookmark");
@@ -285,17 +285,17 @@ add_task(async function test_bookmark_re
       },
     }, {
       object: "repair",
       method: "response",
       value: "upload",
       extra: {
         flowID,
         deviceID: Service.identity.hashedDeviceID(remoteID),
-        numIDs: "1",
+        numIDs: "2",
       },
     }, {
       object: "repair",
       method: "finished",
       value: undefined,
       extra: {
         flowID,
         numIDs: "0",
--- a/services/sync/tests/unit/test_bookmark_repair_requestor.js
+++ b/services/sync/tests/unit/test_bookmark_repair_requestor.js
@@ -100,22 +100,22 @@ add_task(async function test_requestor_n
 
   await requestor.startRepairs(validationInfo, flowID);
   // there are no clients, so we should end up in "finished" (which we need to
   // check via telemetry)
   deepEqual(mockService._recordedEvents, [
     { object: "repair",
       method: "started",
       value: undefined,
-      extra: { flowID, numIDs: 3 },
+      extra: { flowID, numIDs: 4 },
     },
     { object: "repair",
       method: "finished",
       value: undefined,
-      extra: { flowID, numIDs: 3 },
+      extra: { flowID, numIDs: 4 },
     }
   ]);
 });
 
 add_task(async function test_requestor_one_client_no_response() {
   let mockService = new MockService({ "client-a": makeClientRecord("client-a") });
   let requestor = NewBookmarkRepairRequestor(mockService);
   let validationInfo = {
@@ -151,32 +151,32 @@ add_task(async function test_requestor_o
   await requestor.continueRepairs();
   // There are no more clients, so we've given up.
 
   checkRepairFinished();
   deepEqual(mockService._recordedEvents, [
     { object: "repair",
       method: "started",
       value: undefined,
-      extra: { flowID, numIDs: 3 },
+      extra: { flowID, numIDs: 4 },
     },
     { object: "repair",
       method: "request",
       value: "upload",
-      extra: { flowID, numIDs: 3, deviceID: "client-a" },
+      extra: { flowID, numIDs: 4, deviceID: "client-a" },
     },
     { object: "repair",
       method: "request",
       value: "upload",
-      extra: { flowID, numIDs: 3, deviceID: "client-a" },
+      extra: { flowID, numIDs: 4, deviceID: "client-a" },
     },
     { object: "repair",
       method: "finished",
       value: undefined,
-      extra: { flowID, numIDs: 3 },
+      extra: { flowID, numIDs: 4 },
     }
   ]);
 });
 
 add_task(async function test_requestor_one_client_no_sync() {
   let mockService = new MockService({ "client-a": makeClientRecord("client-a") });
   let requestor = NewBookmarkRepairRequestor(mockService);
   let validationInfo = {
@@ -203,32 +203,32 @@ add_task(async function test_requestor_o
   await requestor.continueRepairs();
 
   // We should be finished as we gave up in disgust.
   checkRepairFinished();
   deepEqual(mockService._recordedEvents, [
     { object: "repair",
       method: "started",
       value: undefined,
-      extra: { flowID, numIDs: 3 },
+      extra: { flowID, numIDs: 4 },
     },
     { object: "repair",
       method: "request",
       value: "upload",
-      extra: { flowID, numIDs: 3, deviceID: "client-a" },
+      extra: { flowID, numIDs: 4, deviceID: "client-a" },
     },
     { object: "repair",
       method: "abandon",
       value: "silent",
       extra: { flowID, deviceID: "client-a" },
     },
     { object: "repair",
       method: "finished",
       value: undefined,
-      extra: { flowID, numIDs: 3 },
+      extra: { flowID, numIDs: 4 },
     }
   ]);
 });
 
 add_task(async function test_requestor_latest_client_used() {
   let mockService = new MockService({
     "client-early": makeClientRecord("client-early", { serverLastModified: Date.now() - 10 }),
     "client-late": makeClientRecord("client-late", { serverLastModified: Date.now() }),
@@ -283,47 +283,47 @@ add_task(async function test_requestor_c
   checkOutgoingCommand(mockService, "client-b");
 
   // Now let's pretend client B wrote all missing IDs.
   let response = {
     collection: "bookmarks",
     request: "upload",
     flowID: requestor._flowID,
     clientID: "client-b",
-    ids: ["a", "b", "c"],
+    ids: ["a", "b", "c", "x"],
   }
   await requestor.continueRepairs(response);
 
   // We should be finished as we got all our IDs.
   checkRepairFinished();
   deepEqual(mockService._recordedEvents, [
     { object: "repair",
       method: "started",
       value: undefined,
-      extra: { flowID, numIDs: 3 },
+      extra: { flowID, numIDs: 4 },
     },
     { object: "repair",
       method: "request",
       value: "upload",
-      extra: { flowID, numIDs: 3, deviceID: "client-a" },
+      extra: { flowID, numIDs: 4, deviceID: "client-a" },
     },
     { object: "repair",
       method: "abandon",
       value: "missing",
       extra: { flowID, deviceID: "client-a" },
     },
     { object: "repair",
       method: "request",
       value: "upload",
-      extra: { flowID, numIDs: 3, deviceID: "client-b" },
+      extra: { flowID, numIDs: 4, deviceID: "client-b" },
     },
     { object: "repair",
       method: "response",
       value: "upload",
-      extra: { flowID, deviceID: "client-b", numIDs: 3 },
+      extra: { flowID, deviceID: "client-b", numIDs: 4 },
     },
     { object: "repair",
       method: "finished",
       value: undefined,
       extra: { flowID, numIDs: 0 },
     }
   ]);
 });
@@ -366,47 +366,47 @@ add_task(async function test_requestor_s
   checkOutgoingCommand(mockService, "client-b");
 
   // Now let's pretend client B write the missing ID.
   response = {
     collection: "bookmarks",
     request: "upload",
     clientID: "client-b",
     flowID: requestor._flowID,
-    ids: ["c"],
+    ids: ["c", "x"],
   }
   await requestor.continueRepairs(response);
 
   // We should be finished as we got all our IDs.
   checkRepairFinished();
   deepEqual(mockService._recordedEvents, [
     { object: "repair",
       method: "started",
       value: undefined,
-      extra: { flowID, numIDs: 3 },
+      extra: { flowID, numIDs: 4 },
     },
     { object: "repair",
       method: "request",
       value: "upload",
-      extra: { flowID, numIDs: 3, deviceID: "client-a" },
+      extra: { flowID, numIDs: 4, deviceID: "client-a" },
     },
     { object: "repair",
       method: "response",
       value: "upload",
       extra: { flowID, deviceID: "client-a", numIDs: 2 },
     },
     { object: "repair",
       method: "request",
       value: "upload",
-      extra: { flowID, numIDs: 1, deviceID: "client-b" },
+      extra: { flowID, numIDs: 2, deviceID: "client-b" },
     },
     { object: "repair",
       method: "response",
       value: "upload",
-      extra: { flowID, deviceID: "client-b", numIDs: 1 },
+      extra: { flowID, deviceID: "client-b", numIDs: 2 },
     },
     { object: "repair",
       method: "finished",
       value: undefined,
       extra: { flowID, numIDs: 0 },
     }
   ]);
 });
@@ -491,24 +491,24 @@ add_task(async function test_requestor_a
   await requestor.continueRepairs(response);
 
   // We should have aborted now
   checkRepairFinished();
   const expected = [
     { method: "started",
       object: "repair",
       value: undefined,
-      extra: { flowID, numIDs: "3" },
+      extra: { flowID, numIDs: "4" },
     },
     { method: "request",
       object: "repair",
       value: "upload",
-      extra: { flowID, numIDs: "3", deviceID: "client-a" },
+      extra: { flowID, numIDs: "4", deviceID: "client-a" },
     },
     { method: "aborted",
       object: "repair",
       value: undefined,
-      extra: { flowID, numIDs: "3", reason: "other clients repairing" },
+      extra: { flowID, numIDs: "4", reason: "other clients repairing" },
     }
   ];
 
   deepEqual(mockService._recordedEvents, expected);
 });
--- a/servo/components/style/gecko/generated/bindings.rs
+++ b/servo/components/style/gecko/generated/bindings.rs
@@ -2876,15 +2876,20 @@ extern "C" {
 }
 extern "C" {
     pub fn Gecko_ReportUnexpectedCSSError(reporter: *mut ErrorReporter,
                                           message:
                                               *const ::std::os::raw::c_char,
                                           param:
                                               *const ::std::os::raw::c_char,
                                           paramLen: u32,
+                                          prefix:
+                                              *const ::std::os::raw::c_char,
+                                          prefixParam:
+                                              *const ::std::os::raw::c_char,
+                                          prefixParamLen: u32,
+                                          suffix:
+                                              *const ::std::os::raw::c_char,
                                           source:
                                               *const ::std::os::raw::c_char,
                                           sourceLen: u32, lineNumber: u32,
-                                          colNumber: u32, aURI: *mut nsIURI,
-                                          followup:
-                                              *const ::std::os::raw::c_char);
-}
+                                          colNumber: u32);
+}
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -33,17 +33,17 @@ use media_queries::Device;
 use parser::{Parse, ParserContext};
 use properties::animated_properties::AnimatableLonghand;
 #[cfg(feature = "gecko")] use properties::longhands::system_font::SystemFont;
 use selector_parser::PseudoElement;
 use selectors::parser::SelectorParseError;
 #[cfg(feature = "servo")] use servo_config::prefs::PREFS;
 use shared_lock::StylesheetGuards;
 use style_traits::{PARSING_MODE_DEFAULT, HasViewportPercentage, ToCss, ParseError};
-use style_traits::{PropertyDeclarationParseError, StyleParseError};
+use style_traits::{PropertyDeclarationParseError, StyleParseError, ValueParseError};
 use stylesheets::{CssRuleType, MallocSizeOf, MallocSizeOfFn, Origin, UrlExtraData};
 #[cfg(feature = "servo")] use values::Either;
 use values::generics::text::LineHeight;
 use values::computed;
 use cascade_info::CascadeInfo;
 use rule_tree::{CascadeLevel, StrongRuleNode};
 use self::computed_value_flags::ComputedValueFlags;
 use style_adjuster::StyleAdjuster;
@@ -1484,45 +1484,48 @@ impl PropertyDeclaration {
                       "Declarations are only expected inside a keyframe, page, or style rule.");
         id.check_allowed_in(rule_type, context.stylesheet_origin)?;
         match id {
             PropertyId::Custom(name) => {
                 let value = match input.try(|i| CSSWideKeyword::parse(context, i)) {
                     Ok(keyword) => DeclaredValueOwned::CSSWideKeyword(keyword),
                     Err(_) => match ::custom_properties::SpecifiedValue::parse(context, input) {
                         Ok(value) => DeclaredValueOwned::Value(value),
-                        Err(_) => return Err(PropertyDeclarationParseError::InvalidValue(name.to_string().into())),
+                        Err(e) => return Err(PropertyDeclarationParseError::InvalidValue(name.to_string().into(),
+                        ValueParseError::from_parse_error(e))),
                     }
                 };
                 declarations.push(PropertyDeclaration::Custom(name, value));
                 Ok(())
             }
             PropertyId::Longhand(id) => {
                 input.try(|i| CSSWideKeyword::parse(context, i)).map(|keyword| {
                     PropertyDeclaration::CSSWideKeyword(id, keyword)
                 }).or_else(|_| {
                     input.look_for_var_functions();
                     let start = input.position();
                     input.parse_entirely(|input| id.parse_value(context, input))
-                    .or_else(|_| {
+                    .or_else(|err| {
                         while let Ok(_) = input.next() {}  // Look for var() after the error.
                         if input.seen_var_functions() {
                             input.reset(start);
                             let (first_token_type, css) =
-                                ::custom_properties::parse_non_custom_with_var(input).map_err(|_| {
-                                    PropertyDeclarationParseError::InvalidValue(id.name().into())
+                                ::custom_properties::parse_non_custom_with_var(input).map_err(|e| {
+                                    PropertyDeclarationParseError::InvalidValue(id.name().into(),
+                                        ValueParseError::from_parse_error(e))
                                 })?;
                             Ok(PropertyDeclaration::WithVariables(id, Arc::new(UnparsedValue {
                                 css: css.into_owned(),
                                 first_token_type: first_token_type,
                                 url_data: context.url_data.clone(),
                                 from_shorthand: None,
                             })))
                         } else {
-                            Err(PropertyDeclarationParseError::InvalidValue(id.name().into()))
+                            Err(PropertyDeclarationParseError::InvalidValue(id.name().into(),
+                                ValueParseError::from_parse_error(err)))
                         }
                     })
                 }).map(|declaration| {
                     declarations.push(declaration)
                 })
             }
             PropertyId::Shorthand(id) => {
                 if let Ok(keyword) = input.try(|i| CSSWideKeyword::parse(context, i)) {
@@ -1534,23 +1537,24 @@ impl PropertyDeclaration {
                         }
                     }
                     Ok(())
                 } else {
                     input.look_for_var_functions();
                     let start = input.position();
                     // Not using parse_entirely here: each ${shorthand.ident}::parse_into function
                     // needs to do so *before* pushing to `declarations`.
-                    id.parse_into(declarations, context, input).or_else(|_| {
+                    id.parse_into(declarations, context, input).or_else(|err| {
                         while let Ok(_) = input.next() {}  // Look for var() after the error.
                         if input.seen_var_functions() {
                             input.reset(start);
                             let (first_token_type, css) =
-                                ::custom_properties::parse_non_custom_with_var(input).map_err(|_| {
-                                    PropertyDeclarationParseError::InvalidValue(id.name().into())
+                                ::custom_properties::parse_non_custom_with_var(input).map_err(|e| {
+                                    PropertyDeclarationParseError::InvalidValue(id.name().into(),
+                                        ValueParseError::from_parse_error(e))
                                 })?;
                             let unparsed = Arc::new(UnparsedValue {
                                 css: css.into_owned(),
                                 first_token_type: first_token_type,
                                 url_data: context.url_data.clone(),
                                 from_shorthand: Some(id),
                             });
                             if id == ShorthandId::All {
@@ -1559,17 +1563,18 @@ impl PropertyDeclaration {
                                 for &longhand in id.longhands() {
                                     declarations.push(
                                         PropertyDeclaration::WithVariables(longhand, unparsed.clone())
                                     )
                                 }
                             }
                             Ok(())
                         } else {
-                            Err(PropertyDeclarationParseError::InvalidValue(id.name().into()))
+                            Err(PropertyDeclarationParseError::InvalidValue(id.name().into(),
+                                ValueParseError::from_parse_error(err)))
                         }
                     })
                 }
             }
         }
     }
 }
 
--- a/servo/components/style/values/specified/color.rs
+++ b/servo/components/style/values/specified/color.rs
@@ -8,17 +8,17 @@ use cssparser::{Color as CSSParserColor,
 #[cfg(feature = "gecko")]
 use gecko_bindings::structs::nscolor;
 use itoa;
 use parser::{ParserContext, Parse};
 #[cfg(feature = "gecko")]
 use properties::longhands::color::SystemColor;
 use std::fmt;
 use std::io::Write;
-use style_traits::{ToCss, ParseError, StyleParseError};
+use style_traits::{ToCss, ParseError, StyleParseError, ValueParseError};
 use super::AllowQuirks;
 use values::computed::{Color as ComputedColor, Context, ToComputedValue};
 
 /// Specified color value
 #[derive(Clone, PartialEq, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum Color {
     /// The 'currentColor' keyword
@@ -71,34 +71,38 @@ impl Parse for Color {
         // because all browsers serialize those values as keywords for
         // specified value.
         let start_position = input.position();
         let authored = match input.next() {
             Ok(&Token::Ident(ref s)) => Some(s.to_lowercase().into_boxed_str()),
             _ => None,
         };
         input.reset(start_position);
-        if let Ok(value) = input.try(CSSParserColor::parse) {
-            Ok(match value {
-                CSSParserColor::CurrentColor => Color::CurrentColor,
-                CSSParserColor::RGBA(rgba) => Color::Numeric {
-                    parsed: rgba,
-                    authored: authored,
-                },
-            })
-        } else {
-            #[cfg(feature = "gecko")] {
-                if let Ok(system) = input.try(SystemColor::parse) {
-                    Ok(Color::System(system))
-                } else {
-                    gecko::SpecialColorKeyword::parse(input).map(Color::Special)
+        match input.try(CSSParserColor::parse) {
+            Ok(value) =>
+                Ok(match value {
+                    CSSParserColor::CurrentColor => Color::CurrentColor,
+                    CSSParserColor::RGBA(rgba) => Color::Numeric {
+                        parsed: rgba,
+                        authored: authored,
+                    },
+                }),
+            Err(e) => {
+                #[cfg(feature = "gecko")] {
+                    if let Ok(system) = input.try(SystemColor::parse) {
+                        return Ok(Color::System(system));
+                    } else if let Ok(c) = gecko::SpecialColorKeyword::parse(input) {
+                        return Ok(Color::Special(c));
+                    }
                 }
-            }
-            #[cfg(not(feature = "gecko"))] {
-                Err(StyleParseError::UnspecifiedError.into())
+                match e {
+                    BasicParseError::UnexpectedToken(t) =>
+                        Err(StyleParseError::ValueError(ValueParseError::InvalidColor(t)).into()),
+                    e => Err(e.into())
+                }
             }
         }
     }
 }
 
 impl ToCss for Color {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         match *self {
@@ -156,21 +160,23 @@ impl Color {
 
     /// Parse a color, with quirks.
     ///
     /// https://quirks.spec.whatwg.org/#the-hashless-hex-color-quirk
     pub fn parse_quirky<'i, 't>(context: &ParserContext,
                                 input: &mut Parser<'i, 't>,
                                 allow_quirks: AllowQuirks)
                                 -> Result<Self, ParseError<'i>> {
-        input.try(|i| Self::parse(context, i)).or_else(|_| {
+        input.try(|i| Self::parse(context, i)).or_else(|e| {
             if !allow_quirks.allowed(context.quirks_mode) {
-                return Err(StyleParseError::UnspecifiedError.into());
+                return Err(e);
             }
-            Color::parse_quirky_color(input).map(|rgba| Color::rgba(rgba))
+            Color::parse_quirky_color(input)
+                .map(|rgba| Color::rgba(rgba))
+                .map_err(|_| e)
         })
     }
 
     /// Parse a <quirky-color> value.
     ///
     /// https://quirks.spec.whatwg.org/#the-hashless-hex-color-quirk
     fn parse_quirky_color<'i, 't>(input: &mut Parser<'i, 't>) -> Result<RGBA, ParseError<'i>> {
         let (value, unit) = match *input.next()? {
--- a/servo/components/style_traits/lib.rs
+++ b/servo/components/style_traits/lib.rs
@@ -119,29 +119,50 @@ pub enum StyleParseError<'i> {
     /// Unexpected @charset rule encountered.
     UnexpectedCharsetRule,
     /// Unsupported @ rule
     UnsupportedAtRule(CowRcStr<'i>),
     /// A placeholder for many sources of errors that require more specific variants.
     UnspecifiedError,
     /// An unexpected token was found within a namespace rule.
     UnexpectedTokenWithinNamespace(Token<'i>),
+    /// An error was encountered while parsing a property value.
+    ValueError(ValueParseError<'i>),
+}
+
+/// Specific errors that can be encountered while parsing property values.
+#[derive(Clone, Debug, PartialEq)]
+pub enum ValueParseError<'i> {
+    /// An invalid token was encountered while parsing a color value.
+    InvalidColor(Token<'i>),
+}
+
+impl<'i> ValueParseError<'i> {
+    /// Attempt to extract a ValueParseError value from a ParseError.
+    pub fn from_parse_error(this: ParseError<'i>) -> Option<ValueParseError<'i>> {
+        match this {
+            cssparser::ParseError::Custom(
+                SelectorParseError::Custom(
+                    StyleParseError::ValueError(e))) => Some(e),
+            _ => None,
+        }
+    }
 }
 
 /// The result of parsing a property declaration.
-#[derive(Eq, PartialEq, Clone, Debug)]
+#[derive(PartialEq, Clone, Debug)]
 pub enum PropertyDeclarationParseError<'i> {
     /// The property declaration was for an unknown property.
     UnknownProperty(CowRcStr<'i>),
     /// An unknown vendor-specific identifier was encountered.
     UnknownVendorProperty,
     /// The property declaration was for a disabled experimental property.
     ExperimentalProperty,
     /// The property declaration contained an invalid value.
-    InvalidValue(CowRcStr<'i>),
+    InvalidValue(CowRcStr<'i>, Option<ValueParseError<'i>>),
     /// The declaration contained an animation property, and we were parsing
     /// this as a keyframe block (so that property should be ignored).
     ///
     /// See: https://drafts.csswg.org/css-animations/#keyframes
     AnimationPropertyInKeyframeBlock,
     /// The property is not allowed within a page rule.
     NotAllowedInPageRule,
 }
--- a/servo/ports/geckolib/error_reporter.rs
+++ b/servo/ports/geckolib/error_reporter.rs
@@ -13,17 +13,17 @@ use std::ptr;
 use style::error_reporting::{ParseErrorReporter, ContextualParseError};
 use style::gecko_bindings::bindings::{Gecko_CreateCSSErrorReporter, Gecko_DestroyCSSErrorReporter};
 use style::gecko_bindings::bindings::Gecko_ReportUnexpectedCSSError;
 use style::gecko_bindings::structs::{Loader, ServoStyleSheet, nsIURI};
 use style::gecko_bindings::structs::ErrorReporter as GeckoErrorReporter;
 use style::gecko_bindings::structs::URLExtraData as RawUrlExtraData;
 use style::gecko_bindings::sugar::refptr::RefPtr;
 use style::stylesheets::UrlExtraData;
-use style_traits::{ParseError, StyleParseError, PropertyDeclarationParseError};
+use style_traits::{ParseError, StyleParseError, PropertyDeclarationParseError, ValueParseError};
 
 /// Wrapper around an instance of Gecko's CSS error reporter.
 pub struct ErrorReporter(*mut GeckoErrorReporter);
 
 impl ErrorReporter {
     /// Create a new instance of the Gecko error reporter.
     pub fn new(sheet: *mut ServoStyleSheet,
                loader: *mut Loader,
@@ -189,18 +189,69 @@ fn token_to_str<'a>(t: Token<'a>) -> Str
 enum Action {
     Nothing,
     Skip,
     Drop,
 }
 
 trait ErrorHelpers<'a> {
     fn error_data(self) -> (CowRcStr<'a>, ParseError<'a>);
-    fn error_param(self) -> ErrorString<'a>;
-    fn to_gecko_message(&self) -> (&'static [u8], Action);
+    fn error_params(self) -> (ErrorString<'a>, Option<ErrorString<'a>>);
+    fn to_gecko_message(&self) -> (Option<&'static [u8]>, &'static [u8], Action);
+}
+
+fn extract_error_param<'a>(err: ParseError<'a>) -> Option<ErrorString<'a>> {
+    Some(match err {
+        CssParseError::Basic(BasicParseError::UnexpectedToken(t)) =>
+            ErrorString::UnexpectedToken(t),
+
+        CssParseError::Basic(BasicParseError::AtRuleInvalid(i)) =>
+            ErrorString::Snippet(format!("@{}", escape_css_ident(&i)).into()),
+
+        CssParseError::Custom(SelectorParseError::Custom(
+            StyleParseError::PropertyDeclaration(
+                PropertyDeclarationParseError::InvalidValue(property, None)))) =>
+            ErrorString::Snippet(property),
+
+        CssParseError::Custom(SelectorParseError::UnexpectedIdent(ident)) =>
+            ErrorString::Ident(ident),
+
+        CssParseError::Custom(SelectorParseError::ExpectedNamespace(namespace)) =>
+            ErrorString::Ident(namespace),
+
+        CssParseError::Custom(SelectorParseError::Custom(
+            StyleParseError::PropertyDeclaration(
+                PropertyDeclarationParseError::UnknownProperty(property)))) =>
+            ErrorString::Ident(property),
+
+        CssParseError::Custom(SelectorParseError::Custom(
+            StyleParseError::UnexpectedTokenWithinNamespace(token))) =>
+            ErrorString::UnexpectedToken(token),
+
+        _ => return None,
+    })
+}
+
+fn extract_value_error_param<'a>(err: ValueParseError<'a>) -> ErrorString<'a> {
+    match err {
+        ValueParseError::InvalidColor(t) => ErrorString::UnexpectedToken(t),
+    }
+}
+
+/// If an error parameter is present in the given error, return it. Additionally return
+/// a second parameter if it exists, for use in the prefix for the eventual error message.
+fn extract_error_params<'a>(err: ParseError<'a>) -> Option<(ErrorString<'a>, Option<ErrorString<'a>>)> {
+    match err {
+        CssParseError::Custom(SelectorParseError::Custom(
+            StyleParseError::PropertyDeclaration(
+                PropertyDeclarationParseError::InvalidValue(property, Some(e))))) =>
+            Some((ErrorString::Snippet(property.into()), Some(extract_value_error_param(e)))),
+
+        err => extract_error_param(err).map(|e| (e, None)),
+    }
 }
 
 impl<'a> ErrorHelpers<'a> for ContextualParseError<'a> {
     fn error_data(self) -> (CowRcStr<'a>, ParseError<'a>) {
         match self {
             ContextualParseError::UnsupportedPropertyDeclaration(s, err) |
             ContextualParseError::UnsupportedFontFaceDescriptor(s, err) |
             ContextualParseError::UnsupportedFontFeatureValuesDescriptor(s, err) |
@@ -217,60 +268,38 @@ impl<'a> ErrorHelpers<'a> for Contextual
                 (s.into(), StyleParseError::UnspecifiedError.into()),
             ContextualParseError::InvalidCounterStyleWithoutAdditiveSymbols |
             ContextualParseError::InvalidCounterStyleExtendsWithSymbols |
             ContextualParseError::InvalidCounterStyleExtendsWithAdditiveSymbols =>
                 ("".into(), StyleParseError::UnspecifiedError.into())
         }
     }
 
-    fn error_param(self) -> ErrorString<'a> {
-        match self.error_data() {
-            (_, CssParseError::Basic(BasicParseError::UnexpectedToken(t))) =>
-                ErrorString::UnexpectedToken(t),
-
-            (_, CssParseError::Basic(BasicParseError::AtRuleInvalid(i))) =>
-                ErrorString::Snippet(format!("@{}", escape_css_ident(&i)).into()),
-
-            (_, CssParseError::Custom(SelectorParseError::Custom(
-                StyleParseError::PropertyDeclaration(
-                    PropertyDeclarationParseError::InvalidValue(property))))) =>
-                ErrorString::Snippet(property),
-
-            (_, CssParseError::Custom(SelectorParseError::UnexpectedIdent(ident))) =>
-                ErrorString::Ident(ident),
-
-            (_, CssParseError::Custom(SelectorParseError::ExpectedNamespace(namespace))) =>
-                ErrorString::Ident(namespace),
-
-            (_, CssParseError::Custom(SelectorParseError::Custom(
-                StyleParseError::PropertyDeclaration(
-                    PropertyDeclarationParseError::UnknownProperty(property))))) =>
-                ErrorString::Ident(property),
-
-            (_, CssParseError::Custom(SelectorParseError::Custom(
-                StyleParseError::UnexpectedTokenWithinNamespace(token)))) =>
-                ErrorString::UnexpectedToken(token),
-
-            (s, _)  => ErrorString::Snippet(s)
-        }
+    fn error_params(self) -> (ErrorString<'a>, Option<ErrorString<'a>>) {
+        let (s, error) = self.error_data();
+        extract_error_params(error).unwrap_or((ErrorString::Snippet(s), None))
     }
 
-    fn to_gecko_message(&self) -> (&'static [u8], Action) {
-        match *self {
+    fn to_gecko_message(&self) -> (Option<&'static [u8]>, &'static [u8], Action) {
+        let (msg, action): (&[u8], Action) = match *self {
             ContextualParseError::UnsupportedPropertyDeclaration(
                 _, CssParseError::Basic(BasicParseError::UnexpectedToken(_))) |
             ContextualParseError::UnsupportedPropertyDeclaration(
                 _, CssParseError::Basic(BasicParseError::AtRuleInvalid(_))) =>
                 (b"PEParseDeclarationDeclExpected\0", Action::Skip),
             ContextualParseError::UnsupportedPropertyDeclaration(
                 _, CssParseError::Custom(SelectorParseError::Custom(
                     StyleParseError::PropertyDeclaration(
-                        PropertyDeclarationParseError::InvalidValue(_))))) =>
-                (b"PEValueParsingError\0", Action::Drop),
+                        PropertyDeclarationParseError::InvalidValue(_, ref err))))) => {
+                let prefix = match *err {
+                    Some(ValueParseError::InvalidColor(_)) => Some(&b"PEColorNotColor\0"[..]),
+                    _ => None,
+                };
+                return (prefix, b"PEValueParsingError\0", Action::Drop);
+            }
             ContextualParseError::UnsupportedPropertyDeclaration(..) =>
                 (b"PEUnknownProperty\0", Action::Drop),
             ContextualParseError::UnsupportedFontFaceDescriptor(..) =>
                 (b"PEUnknwnFontDesc\0", Action::Skip),
             ContextualParseError::InvalidKeyframeRule(..) =>
                 (b"PEKeyframeBadName\0", Action::Nothing),
             ContextualParseError::UnsupportedKeyframePropertyDeclaration(..) =>
                 (b"PEBadSelectorKeyframeRuleIgnored\0", Action::Nothing),
@@ -290,45 +319,51 @@ impl<'a> ErrorHelpers<'a> for Contextual
             ContextualParseError::InvalidCounterStyleWithoutSymbols(..) |
             ContextualParseError::InvalidCounterStyleNotEnoughSymbols(..) |
             ContextualParseError::InvalidCounterStyleWithoutAdditiveSymbols |
             ContextualParseError::InvalidCounterStyleExtendsWithSymbols |
             ContextualParseError::InvalidCounterStyleExtendsWithAdditiveSymbols |
             ContextualParseError::UnsupportedFontFeatureValuesDescriptor(..) |
             ContextualParseError::InvalidFontFeatureValuesRule(..) =>
                 (b"PEUnknownAtRule\0", Action::Skip),
-        }
+        };
+        (None, msg, action)
     }
 }
 
 impl ParseErrorReporter for ErrorReporter {
     fn report_error<'a>(&self,
                         input: &mut Parser,
                         position: SourcePosition,
                         error: ContextualParseError<'a>,
-                        url: &UrlExtraData,
+                        _url: &UrlExtraData,
                         line_number_offset: u64) {
         let location = input.source_location(position);
         let line_number = location.line + line_number_offset as u32;
 
-        let (name, action) = error.to_gecko_message();
-        let followup = match action {
+        let (pre, name, action) = error.to_gecko_message();
+        let suffix = match action {
             Action::Nothing => ptr::null(),
             Action::Skip => b"PEDeclSkipped\0".as_ptr(),
             Action::Drop => b"PEDeclDropped\0".as_ptr(),
         };
-        let param = error.error_param().into_str();
+        let (param, pre_param) = error.error_params();
+        let param = param.into_str();
+        let pre_param = pre_param.map(|p| p.into_str());
+        let pre_param_ptr = pre_param.as_ref().map_or(ptr::null(), |p| p.as_ptr());
         // The CSS source text is unused and will be removed in bug 1381188.
         let source = "";
         unsafe {
             Gecko_ReportUnexpectedCSSError(self.0,
                                            name.as_ptr() as *const _,
                                            param.as_ptr() as *const _,
                                            param.len() as u32,
+                                           pre.map_or(ptr::null(), |p| p.as_ptr()) as *const _,
+                                           pre_param_ptr as *const _,
+                                           pre_param.as_ref().map_or(0, |p| p.len()) as u32,
+                                           suffix as *const _,
                                            source.as_ptr() as *const _,
                                            source.len() as u32,
                                            line_number as u32,
-                                           location.column as u32,
-                                           url.mBaseURI.raw::<nsIURI>(),
-                                           followup as *const _);
+                                           location.column as u32);
         }
     }
 }
--- a/servo/tests/unit/style/stylesheets.rs
+++ b/servo/tests/unit/style/stylesheets.rs
@@ -373,17 +373,17 @@ fn test_report_error_stylesheet() {
     let error = errors.pop().unwrap();
     assert_eq!("Unsupported property declaration: 'invalid: true;', \
                 Custom(PropertyDeclaration(UnknownProperty(\"invalid\")))", error.message);
     assert_eq!(9, error.line);
     assert_eq!(8, error.column);
 
     let error = errors.pop().unwrap();
     assert_eq!("Unsupported property declaration: 'display: invalid;', \
-                Custom(PropertyDeclaration(InvalidValue(\"display\")))", error.message);
+                Custom(PropertyDeclaration(InvalidValue(\"display\", None)))", error.message);
     assert_eq!(8, error.line);
     assert_eq!(8, error.column);
 
     // testing for the url
     assert_eq!(url, error.url);
 }
 
 #[test]
--- a/taskcluster/ci/build/windows.yml
+++ b/taskcluster/ci/build/windows.yml
@@ -179,16 +179,19 @@ win32-devedition/opt:
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/win32/releng.manifest"
     run:
         using: mozharness
         script: mozharness/scripts/fx_desktop_build.py
         config:
             - builds/taskcluster_firefox_windows_32_opt.py
     run-on-projects: ['mozilla-beta', ]
+    toolchains:
+        - win32-clang-cl
+        - win64-sccache
 
 win64-devedition/opt:
     description: "Win64 Dev Edition Opt"
     index:
         product: devedition
         job-name: win64-devedition-opt
     treeherder:
         platform: windows2012-64-devedition/opt
@@ -200,16 +203,19 @@ win64-devedition/opt:
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/win64/releng.manifest"
     run:
         using: mozharness
         script: mozharness/scripts/fx_desktop_build.py
         config:
             - builds/taskcluster_firefox_windows_64_opt.py
     run-on-projects: ['mozilla-beta', ]
+    toolchains:
+        - win64-clang-cl
+        - win64-sccache
 
 win32-nightly/opt:
     description: "Win32 Nightly"
     index:
         product: firefox
         job-name: win32-opt
         type: nightly
     attributes:
@@ -425,16 +431,19 @@ win64-asan/opt:
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/win64/clang.manifest"
     run:
         using: mozharness
         script: mozharness/scripts/fx_desktop_build.py
         config:
             - builds/taskcluster_firefox_win64_asan_opt.py
     run-on-projects: []
+    toolchains:
+        - win64-clang-cl
+        - win64-sccache
 
 win32-devedition-nightly/opt:
     description: "Win32 Dev Edition Nightly"
     index:
         product: devedition
         job-name: win32-opt
         type: nightly
     attributes:
@@ -452,16 +461,19 @@ win32-devedition-nightly/opt:
         using: mozharness
         script: mozharness/scripts/fx_desktop_build.py
         config:
             - builds/taskcluster_firefox_windows_32_opt.py
             - disable_signing.py
             - taskcluster_nightly.py
         custom-build-variant-cfg: devedition
     run-on-projects: [ 'mozilla-beta', ]
+    toolchains:
+        - win32-clang-cl
+        - win64-sccache
 
 win64-devedition-nightly/opt:
     description: "Win64 Dev Edition Nightly"
     index:
         product: devedition
         job-name: win64-opt
         type: nightly
     attributes:
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_select.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_select.py
@@ -36,17 +36,17 @@ class TestSelect(SelectTestCase):
             </select>"""))
         select = self.marionette.find_element(By.TAG_NAME, "select")
         options = self.marionette.find_elements(By.TAG_NAME, "option")
 
         self.assertSelected(options[0])
         options[1].click()
         self.assertSelected(options[1])
 
-    def test_deselect(self):
+    def test_deselect_others(self):
         self.marionette.navigate(inline("""
           <select>
             <option>first
             <option>second
             <option>third
           </select>"""))
         select = self.marionette.find_element(By.TAG_NAME, "select")
         options = self.marionette.find_elements(By.TAG_NAME, "option")
@@ -55,16 +55,32 @@ class TestSelect(SelectTestCase):
         self.assertSelected(options[0])
         options[1].click()
         self.assertSelected(options[1])
         options[2].click()
         self.assertSelected(options[2])
         options[0].click()
         self.assertSelected(options[0])
 
+    def test_select_self(self):
+        self.marionette.navigate(inline("""
+          <select>
+            <option>first
+            <option>second
+          </select>"""))
+        select = self.marionette.find_element(By.TAG_NAME, "select")
+        options = self.marionette.find_elements(By.TAG_NAME, "option")
+        self.assertSelected(options[0])
+        self.assertNotSelected(options[1])
+
+        options[1].click()
+        self.assertSelected(options[1])
+        options[1].click()
+        self.assertSelected(options[1])
+
     def test_out_of_view(self):
         self.marionette.navigate(inline("""
           <select>
             <option>1
             <option>2
             <option>3
             <option>4
             <option>5
--- a/testing/marionette/interaction.js
+++ b/testing/marionette/interaction.js
@@ -4,22 +4,23 @@
 
 "use strict";
 
 const {utils: Cu} = Components;
 
 Cu.import("chrome://marionette/content/accessibility.js");
 Cu.import("chrome://marionette/content/atom.js");
 const {
+  ElementClickInterceptedError,
+  ElementNotInteractableError,
   error,
   InvalidArgument,
-  ElementNotInteractableError,
-  ElementClickInterceptedError,
+  InvalidArgumentError,
   InvalidElementStateError,
-  InvalidArgumentError,
+  pprint,
 } = Cu.import("chrome://marionette/content/error.js", {});
 Cu.import("chrome://marionette/content/element.js");
 Cu.import("chrome://marionette/content/event.js");
 
 Cu.importGlobalProperties(["File"]);
 
 this.EXPORTED_SYMBOLS = ["interaction"];
 
@@ -276,32 +277,38 @@ function* seleniumClickElement(el, a11y)
  *     If <var>el</var> is a XUL element or not an <tt>&lt;option&gt;</tt>
  *     element.
  * @throws {Error}
  *     If unable to find <var>el</var>'s parent <tt>&lt;select&gt;</tt>
  *     element.
  */
 interaction.selectOption = function(el) {
   if (element.isXULElement(el)) {
-    throw new Error("XUL dropdowns not supported");
+    throw new TypeError("XUL dropdowns not supported");
   }
   if (el.localName != "option") {
-    throw new TypeError("Invalid elements");
+    throw new TypeError(pprint`Expected <option> element, got ${el}`);
   }
 
   let containerEl = element.getContainer(el);
 
   event.mouseover(containerEl);
   event.mousemove(containerEl);
   event.mousedown(containerEl);
   event.focus(containerEl);
   event.input(containerEl);
 
-  // toggle selectedness the way holding down control works
-  el.selected = !el.selected;
+  // Clicking <option> in <select> should not be deselected if selected.
+  // However, clicking one in a <select multiple> should toggle
+  // selectedness the way holding down Control works.
+  if (containerEl.multiple) {
+    el.selected = !el.selected;
+  } else if (!el.selected) {
+    el.selected = true;
+  }
 
   event.change(containerEl);
   event.mouseup(containerEl);
   event.click(containerEl);
 };
 
 /**
  * Flushes the event loop by requesting an animation frame.
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -305530,16 +305530,21 @@
      {}
     ]
    ],
    "webdriver/tests/conftest.py": [
     [
      {}
     ]
    ],
+   "webdriver/tests/element_click/__init__.py": [
+    [
+     {}
+    ]
+   ],
    "webdriver/tests/support/__init__.py": [
     [
      {}
     ]
    ],
    "webdriver/tests/support/asserts.py": [
     [
      {}
@@ -402866,16 +402871,22 @@
     ]
    ],
    "webdriver/tests/cookies.py": [
     [
      "/webdriver/tests/cookies.py",
      {}
     ]
    ],
+   "webdriver/tests/element_click/select.py": [
+    [
+     "/webdriver/tests/element_click/select.py",
+     {}
+    ]
+   ],
    "webdriver/tests/navigation.py": [
     [
      "/webdriver/tests/navigation.py",
      {}
     ]
    ],
    "webdriver/tests/window_maximizing.py": [
     [
@@ -621661,16 +621672,24 @@
   "webdriver/tests/contexts.py": [
    "9c4be1b08b99945621b149d1aa2aa64167caad50",
    "wdspec"
   ],
   "webdriver/tests/cookies.py": [
    "e31177e638269864031e44808945fa1e7c46031c",
    "wdspec"
   ],
+  "webdriver/tests/element_click/__init__.py": [
+   "da39a3ee5e6b4b0d3255bfef95601890afd80709",
+   "support"
+  ],
+  "webdriver/tests/element_click/select.py": [
+   "5ba51b660c7203bba3ada597c2f56fe094358e1f",
+   "wdspec"
+  ],
   "webdriver/tests/navigation.py": [
    "cec2987258d9c807a247da9e0216b3af1f171484",
    "wdspec"
   ],
   "webdriver/tests/support/__init__.py": [
    "5a31a3917a5157516c10951a3b3d5ffb43b992d9",
    "support"
   ],
--- a/testing/web-platform/meta/css/CSS2/syntax/uri-013.xht.ini
+++ b/testing/web-platform/meta/css/CSS2/syntax/uri-013.xht.ini
@@ -1,4 +1,3 @@
 [uri-013.xht]
   type: reftest
-  expected:
-    if stylo: FAIL
+  expected: FAIL
--- a/testing/web-platform/tests/tools/webdriver/webdriver/client.py
+++ b/testing/web-platform/tests/tools/webdriver/webdriver/client.py
@@ -632,15 +632,22 @@ class Element(object):
     def style(self, property_name):
         return self.send_element_command("GET", "css/%s" % property_name)
 
     @property
     @command
     def rect(self):
         return self.send_element_command("GET", "rect")
 
+    @property
     @command
-    def property(self, name):
-        return self.send_element_command("GET", "property/%s" % name)
+    def selected(self):
+        return self.send_element_command("GET", "selected")
 
     @command
     def attribute(self, name):
         return self.send_element_command("GET", "attribute/%s" % name)
+
+    # This MUST come last because otherwise @property decorators above
+    # will be overridden by this.
+    @command
+    def property(self, name):
+        return self.send_element_command("GET", "property/%s" % name)
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/webdriver/tests/element_click/select.py
@@ -0,0 +1,213 @@
+from tests.support.inline import inline
+
+
+def test_click_option(session):
+    session.url = inline("""
+      <select>
+        <option>first
+        <option>second
+      </select>""")
+    options = session.find.css("option")
+
+    assert options[0].selected
+    assert not options[1].selected
+
+    options[1].click()
+    assert options[1].selected
+    assert not options[0].selected
+
+
+def test_click_multiple_option(session):
+    session.url = inline("""
+      <select multiple>
+        <option>first
+        <option>second
+      </select>""")
+    options = session.find.css("option")
+
+    assert not options[0].selected
+    assert not options[1].selected
+
+    options[0].click()
+    assert options[0].selected
+    assert not options[1].selected
+
+
+def test_click_preselected_option(session):
+    session.url = inline("""
+      <select>
+        <option>first
+        <option selected>second
+      </select>""")
+    options = session.find.css("option")
+
+    assert not options[0].selected
+    assert options[1].selected
+
+    options[1].click()
+    assert options[1].selected
+    assert not options[0].selected
+
+    options[0].click()
+    assert options[0].selected
+    assert not options[1].selected
+
+
+def test_click_preselected_multiple_option(session):
+    session.url = inline("""
+      <select multiple>
+        <option>first
+        <option selected>second
+      </select>""")
+    options = session.find.css("option")
+
+    assert not options[0].selected
+    assert options[1].selected
+
+    options[1].click()
+    assert not options[1].selected
+    assert not options[0].selected
+
+    options[0].click()
+    assert options[0].selected
+    assert not options[1].selected
+
+
+def test_click_deselects_others(session):
+    session.url = inline("""
+      <select>
+        <option>first
+        <option>second
+        <option>third
+      </select>""")
+    options = session.find.css("option")
+
+    options[0].click()
+    assert options[0].selected
+    options[1].click()
+    assert options[1].selected
+    options[2].click()
+    assert options[2].selected
+    options[0].click()
+    assert options[0].selected
+
+
+def test_click_multiple_does_not_deselect_others(session):
+    session.url = inline("""
+      <select multiple>
+        <option>first
+        <option>second
+        <option>third
+      </select>""")
+    options = session.find.css("option")
+
+    options[0].click()
+    assert options[0].selected
+    options[1].click()
+    assert options[0].selected
+    assert options[1].selected
+    options[2].click()
+    assert options[0].selected
+    assert options[1].selected
+    assert options[2].selected
+
+
+def test_click_selected_option(session):
+    session.url = inline("""
+      <select>
+        <option>first
+        <option>second
+      </select>""")
+    options = session.find.css("option")
+
+    # First <option> is selected in dropdown
+    assert options[0].selected
+    assert not options[1].selected
+
+    options[1].click()
+    assert options[1].selected
+    options[1].click()
+    assert options[1].selected
+
+
+def test_click_selected_multiple_option(session):
+    session.url = inline("""
+      <select multiple>
+        <option>first
+        <option>second
+      </select>""")
+    options = session.find.css("option")
+
+    # No implicitly selected <option> in <select multiple>
+    assert not options[0].selected
+    assert not options[1].selected
+
+    options[0].click()
+    assert options[0].selected
+    assert not options[1].selected
+
+    # Second click in <select multiple> deselects
+    options[0].click()
+    assert not options[0].selected
+    assert not options[1].selected
+
+
+def test_out_of_view_dropdown(session):
+    session.url = inline("""
+      <select>
+        <option>1
+        <option>2
+        <option>3
+        <option>4
+        <option>5
+        <option>6
+        <option>7
+        <option>8
+        <option>9
+        <option>10
+        <option>11
+        <option>12
+        <option>13
+        <option>14
+        <option>15
+        <option>16
+        <option>17
+        <option>18
+        <option>19
+        <option>20
+      </select>""")
+    options = session.find.css("option")
+
+    options[14].click()
+    assert options[14].selected
+
+
+def test_out_of_view_multiple(session):
+    session.url = inline("""
+      <select multiple>
+        <option>1
+        <option>2
+        <option>3
+        <option>4
+        <option>5
+        <option>6
+        <option>7
+        <option>8
+        <option>9
+        <option>10
+        <option>11
+        <option>12
+        <option>13
+        <option>14
+        <option>15
+        <option>16
+        <option>17
+        <option>18
+        <option>19
+        <option>20
+      </select>""")
+    options = session.find.css("option")
+
+    last_option = options[-1]
+    last_option.click()
+    assert last_option.selected
--- a/toolkit/components/extensions/ExtensionChild.jsm
+++ b/toolkit/components/extensions/ExtensionChild.jsm
@@ -635,17 +635,25 @@ class ProxyAPIImplementation extends Sch
     this.path = null;
     this.childApiManager = null;
   }
 
   callFunctionNoReturn(args) {
     this.childApiManager.callParentFunctionNoReturn(this.path, args);
   }
 
-  callAsyncFunction(args, callback) {
+  callAsyncFunction(args, callback, requireUserInput) {
+    if (requireUserInput) {
+      let context = this.childApiManager.context;
+      let winUtils = context.contentWindow.getInterface(Ci.nsIDOMWindowUtils);
+      if (!winUtils.isHandlingUserInput) {
+        let err = new context.cloneScope.Error(`${this.path} may only be called from a user input handler`);
+        return context.wrapPromise(Promise.reject(err), callback);
+      }
+    }
     return this.childApiManager.callParentAsyncFunction(this.path, args, callback);
   }
 
   addListener(listener, args) {
     let map = this.childApiManager.listeners.get(this.path);
 
     if (map.listeners.has(listener)) {
       // TODO: Called with different args?
--- a/toolkit/components/extensions/ExtensionCommon.jsm
+++ b/toolkit/components/extensions/ExtensionCommon.jsm
@@ -445,20 +445,22 @@ class SchemaAPIInterface {
 
   /**
    * Calls this as a function that completes asynchronously.
    *
    * @abstract
    * @param {Array} args The parameters for the function.
    * @param {function(*)} [callback] The callback to be called when the function
    *     completes.
+   * @param {boolean} [requireUserInput=false] If true, the function should
+   *                  fail if the browser is not currently handling user input.
    * @returns {Promise|undefined} Must be void if `callback` is set, and a
    *     promise otherwise. The promise is resolved when the function completes.
    */
-  callAsyncFunction(args, callback) {
+  callAsyncFunction(args, callback, requireUserInput = false) {
     throw new Error("Not implemented");
   }
 
   /**
    * Retrieves the value of this as a property.
    *
    * @abstract
    * @returns {*} The value of the property.
@@ -554,19 +556,26 @@ class LocalAPIImplementation extends Sch
   callFunction(args) {
     return this.pathObj[this.name](...args);
   }
 
   callFunctionNoReturn(args) {
     this.pathObj[this.name](...args);
   }
 
-  callAsyncFunction(args, callback) {
+  callAsyncFunction(args, callback, requireUserInput) {
     let promise;
     try {
+      if (requireUserInput) {
+        let winUtils = this.context.contentWindow
+                           .getInterface(Ci.nsIDOMWindowUtils);
+        if (!winUtils.isHandlingUserInput) {
+          throw new ExtensionError(`${this.name} may only be called from a user input handler`);
+        }
+      }
       promise = this.pathObj[this.name](...args) || Promise.resolve();
     } catch (e) {
       promise = Promise.reject(e);
     }
     return this.context.wrapPromise(promise, callback);
   }
 
   getProperty() {
--- a/toolkit/components/extensions/Schemas.jsm
+++ b/toolkit/components/extensions/Schemas.jsm
@@ -1831,17 +1831,18 @@ class ArrayType extends Type {
 
   checkBaseType(baseType) {
     return baseType == "array";
   }
 }
 
 class FunctionType extends Type {
   static get EXTRA_PROPERTIES() {
-    return ["parameters", "async", "returns", ...super.EXTRA_PROPERTIES];
+    return ["parameters", "async", "returns", "requireUserInput",
+            ...super.EXTRA_PROPERTIES];
   }
 
   static parseSchema(schema, path, extraProperties = []) {
     this.checkSchemaProperties(schema, path, extraProperties);
 
     let isAsync = !!schema.async;
     let isExpectingCallback = typeof schema.async === "string";
     let parameters = null;
@@ -1882,24 +1883,26 @@ class FunctionType extends Type {
       }
       if (isAsync && schema.allowAmbiguousOptionalArguments && !hasAsyncCallback) {
         throw new Error("Internal error: Async functions with ambiguous " +
                         "arguments must declare the callback as the last parameter");
       }
     }
 
 
-    return new this(schema, parameters, isAsync, hasAsyncCallback);
+    return new this(schema, parameters, isAsync, hasAsyncCallback,
+                    !!schema.requireUserInput);
   }
 
-  constructor(schema, parameters, isAsync, hasAsyncCallback) {
+  constructor(schema, parameters, isAsync, hasAsyncCallback, requireUserInput) {
     super(schema);
     this.parameters = parameters;
     this.isAsync = isAsync;
     this.hasAsyncCallback = hasAsyncCallback;
+    this.requireUserInput = requireUserInput;
   }
 
   normalize(value, context) {
     return this.normalizeBase("function", value, context);
   }
 
   checkBaseType(baseType) {
     return baseType == "function";
@@ -2162,16 +2165,17 @@ FunctionEntry = class FunctionEntry exte
   constructor(schema, path, name, type, unsupported, allowAmbiguousOptionalArguments, returns, permissions) {
     super(schema, path, name, type.parameters, allowAmbiguousOptionalArguments);
     this.unsupported = unsupported;
     this.returns = returns;
     this.permissions = permissions;
 
     this.isAsync = type.isAsync;
     this.hasAsyncCallback = type.hasAsyncCallback;
+    this.requireUserInput = type.requireUserInput;
   }
 
   checkValue({type, optional, name}, value, context) {
     if (optional && value == null) {
       return;
     }
     if (type.reference === "ExtensionPanel" || type.reference === "Port") {
       // TODO: We currently treat objects with functions as SubModuleType,
@@ -2211,17 +2215,17 @@ FunctionEntry = class FunctionEntry exte
         }
         if (DEBUG && this.hasAsyncCallback && callback) {
           let original = callback;
           callback = (...args) => {
             this.checkCallback(args, context);
             original(...args);
           };
         }
-        let result = apiImpl.callAsyncFunction(actuals, callback);
+        let result = apiImpl.callAsyncFunction(actuals, callback, this.requireUserInput);
         if (DEBUG && this.hasAsyncCallback && !callback) {
           return result.then(result => {
             this.checkCallback([result], context);
             return result;
           });
         }
         return result;
       };
deleted file mode 100644
--- a/toolkit/components/extensions/ext-c-downloads.js
+++ /dev/null
@@ -1,22 +0,0 @@
-"use strict";
-
-var {
-  ExtensionError,
-} = ExtensionUtils;
-
-this.downloads = class extends ExtensionAPI {
-  getAPI(context) {
-    return {
-      downloads: {
-        open(downloadId) {
-          let winUtils = context.contentWindow.getInterface(Ci.nsIDOMWindowUtils);
-          if (!winUtils.isHandlingUserInput) {
-            throw new ExtensionError("May only open downloads from a user input handler");
-          }
-
-          return context.childManager.callParentAsyncFunction("downloads.open_parent", [downloadId]);
-        },
-      },
-    };
-  }
-};
deleted file mode 100644
--- a/toolkit/components/extensions/ext-c-permissions.js
+++ /dev/null
@@ -1,22 +0,0 @@
-"use strict";
-
-var {
-  ExtensionError,
-} = ExtensionUtils;
-
-this.permissions = class extends ExtensionAPI {
-  getAPI(context) {
-    return {
-      permissions: {
-        async request(perms) {
-          let winUtils = context.contentWindow.getInterface(Ci.nsIDOMWindowUtils);
-          if (!winUtils.isHandlingUserInput) {
-            throw new ExtensionError("May only request permissions from a user input handler");
-          }
-
-          return context.childManager.callParentAsyncFunction("permissions.request_parent", [perms]);
-        },
-      },
-    };
-  }
-};
--- a/toolkit/components/extensions/ext-c-toolkit.js
+++ b/toolkit/components/extensions/ext-c-toolkit.js
@@ -36,44 +36,30 @@ extensions.registerModules({
     url: "chrome://extensions/content/ext-c-backgroundPage.js",
     scopes: ["addon_child"],
     manifest: ["background"],
     paths: [
       ["extension", "getBackgroundPage"],
       ["runtime", "getBackgroundPage"],
     ],
   },
-  downloads: {
-    url: "chrome://extensions/content/ext-c-downloads.js",
-    scopes: ["addon_child"],
-    paths: [
-      ["downloads"],
-    ],
-  },
   extension: {
     url: "chrome://extensions/content/ext-c-extension.js",
     scopes: ["addon_child", "content_child", "devtools_child", "proxy_script"],
     paths: [
       ["extension"],
     ],
   },
   i18n: {
     url: "chrome://extensions/content/ext-i18n.js",
     scopes: ["addon_child", "content_child", "devtools_child", "proxy_script"],
     paths: [
       ["i18n"],
     ],
   },
-  permissions: {
-    url: "chrome://extensions/content/ext-c-permissions.js",
-    scopes: ["addon_child", "content_child", "devtools_child", "proxy_script"],
-    paths: [
-      ["permissions"],
-    ],
-  },
   runtime: {
     url: "chrome://extensions/content/ext-c-runtime.js",
     scopes: ["addon_child", "content_child", "devtools_child", "proxy_script"],
     paths: [
       ["runtime"],
     ],
   },
   storage: {
--- a/toolkit/components/extensions/ext-downloads.js
+++ b/toolkit/components/extensions/ext-downloads.js
@@ -640,17 +640,17 @@ this.downloads = class extends Extension
             for (let item of items) {
               promises.push(DownloadMap.erase(item));
               results.push(item.id);
             }
             return Promise.all(promises).then(() => results);
           });
         },
 
-        open_parent(downloadId) {
+        open(downloadId) {
           return DownloadMap.lazyInit().then(() => {
             let download = DownloadMap.fromId(downloadId).download;
             if (download.succeeded) {
               return download.launch();
             }
             return Promise.reject({message: "Download has not completed."});
           }).catch((error) => {
             return Promise.reject({message: error.message});
--- a/toolkit/components/extensions/ext-permissions.js
+++ b/toolkit/components/extensions/ext-permissions.js
@@ -13,17 +13,17 @@ var {
 
 XPCOMUtils.defineLazyPreferenceGetter(this, "promptsEnabled",
                                       "extensions.webextOptionalPermissionPrompts");
 
 this.permissions = class extends ExtensionAPI {
   getAPI(context) {
     return {
       permissions: {
-        async request_parent(perms) {
+        async request(perms) {
           let {permissions, origins} = perms;
 
           let manifestPermissions = context.extension.manifest.optional_permissions;
           for (let perm of permissions) {
             if (!manifestPermissions.includes(perm)) {
               throw new ExtensionError(`Cannot request permission ${perm} since it was not declared in optional_permissions`);
             }
           }
--- a/toolkit/components/extensions/jar.mn
+++ b/toolkit/components/extensions/jar.mn
@@ -27,18 +27,16 @@ toolkit.jar:
     content/extensions/ext-theme.js
     content/extensions/ext-toolkit.js
     content/extensions/ext-topSites.js
     content/extensions/ext-webRequest.js
     content/extensions/ext-webNavigation.js
     # Below is a separate group using the naming convention ext-c-*.js that run
     # in the child process.
     content/extensions/ext-c-backgroundPage.js
-    content/extensions/ext-c-downloads.js
     content/extensions/ext-c-extension.js
 #ifndef ANDROID
     content/extensions/ext-c-identity.js
 #endif
-    content/extensions/ext-c-permissions.js
     content/extensions/ext-c-runtime.js
     content/extensions/ext-c-storage.js
     content/extensions/ext-c-test.js
     content/extensions/ext-c-toolkit.js
--- a/toolkit/components/extensions/schemas/downloads.json
+++ b/toolkit/components/extensions/schemas/downloads.json
@@ -552,16 +552,17 @@
             "type": "function"
           }
         ]
       },
       {
         "name": "open",
         "type": "function",
         "async": "callback",
+        "requireUserInput": true,
         "description": "Open the downloaded file.",
         "permissions": ["downloads.open"],
         "parameters": [
           {
             "name": "downloadId",
             "type": "integer"
           },
           {
--- a/toolkit/components/extensions/schemas/permissions.json
+++ b/toolkit/components/extensions/schemas/permissions.json
@@ -81,16 +81,17 @@
           }
         ]
       },
       {
         "name": "request",
         "type": "function",
         "allowedContexts": ["content"],
         "async": "callback",
+        "requireUserInput": true,
         "description": "Request the given permissions.",
         "parameters": [
           {
             "name": "permissions",
             "$ref": "Permissions"
           },
           {
             "name": "callback",
--- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_open.html
+++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_open.html
@@ -31,17 +31,17 @@ add_task(async function test_downloads_o
   await extension.awaitFinish("downloads tests");
   await extension.unload();
 });
 
 add_task(async function test_downloads_open_requires_user_interaction() {
   async function backgroundScript() {
     await browser.test.assertRejects(
       browser.downloads.open(10),
-      "May only open downloads from a user input handler",
+      "downloads.open may only be called from a user input handler",
       "The error is informative.");
 
     browser.test.notifyPass("downloads tests");
   }
 
   let extensionData = {
     background: backgroundScript,
     manifest: {
--- a/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
@@ -125,17 +125,17 @@ add_task(async function test_permissions
   result = await call("contains", {
     permissions: [...REQUIRED_PERMISSIONS, ...OPTIONAL_PERMISSIONS],
   });
   equal(result, false, "contains() returns false for a mix of available and unavailable permissions");
 
   let perm = OPTIONAL_PERMISSIONS[0];
   result = await call("request", {permissions: [perm]});
   equal(result.status, "error", "request() fails if not called from an event handler");
-  ok(/May only request permissions from a user input handler/.test(result.message),
+  ok(/request may only be called from a user input handler/.test(result.message),
      "error message for calling request() outside an event handler is reasonable");
   result = await call("contains", {permissions: [perm]});
   equal(result, false, "Permission requested outside an event handler was not granted");
 
   let userInputHandle = winUtils.setHandlingUserInput(true);
 
   result = await call("request", {permissions: ["notifications"]});
   equal(result.status, "error", "request() for permission not in optional_permissions should fail");
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_schemas_interactive.js
@@ -0,0 +1,135 @@
+"use strict";
+
+/* global ExtensionAPIs */
+Components.utils.import("resource://gre/modules/ExtensionAPI.jsm");
+const {ExtensionManager} = Components.utils.import("resource://gre/modules/ExtensionChild.jsm", {});
+
+Components.utils.importGlobalProperties(["Blob", "URL"]);
+
+let schema = [
+  {
+    namespace: "userinputtest",
+    functions: [
+      {
+        name: "test",
+        type: "function",
+        async: true,
+        requireUserInput: true,
+        parameters: [],
+      },
+    ],
+  },
+];
+
+class API extends ExtensionAPI {
+  getAPI(context) {
+    return {
+      userinputtest: {
+        test() {},
+      },
+    };
+  }
+}
+
+let schemaUrl = `data:,${JSON.stringify(schema)}`;
+
+// Set the "handlingUserInput" flag for the given extension's background page.
+// Returns an RAIIHelper that should be destruct()ed eventually.
+function setHandlingUserInput(extension) {
+  let extensionChild = ExtensionManager.extensions.get(extension.extension.id);
+  let bgwin = null;
+  for (let view of extensionChild.views) {
+    if (view.viewType == "background") {
+      bgwin = view.contentWindow;
+      break;
+    }
+  }
+  notEqual(bgwin, null, "Found background window for the test extension");
+  let winutils = bgwin.QueryInterface(Ci.nsIInterfaceRequestor)
+                      .getInterface(Ci.nsIDOMWindowUtils);
+  return winutils.setHandlingUserInput(true);
+}
+
+// Test that the schema requireUserInput flag works correctly for
+// proxied api implementations.
+add_task(async function test_proxy() {
+  let apiUrl = URL.createObjectURL(new Blob([API.toString()]));
+  ExtensionAPIs.register("userinputtest", schemaUrl, apiUrl);
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background() {
+      browser.test.onMessage.addListener(async () => {
+        try {
+          await browser.userinputtest.test();
+          browser.test.sendMessage("result", null);
+        } catch (err) {
+          browser.test.sendMessage("result", err.message);
+        }
+      });
+    },
+    manifest: {
+      permissions: ["experiments.userinputtest"],
+    },
+  });
+
+  await extension.startup();
+
+  extension.sendMessage("test");
+  let result = await extension.awaitMessage("result");
+  ok(/test may only be called from a user input handler/.test(result),
+     "function failed when not called from a user input handler");
+
+  let handle = setHandlingUserInput(extension);
+  extension.sendMessage("test");
+  result = await extension.awaitMessage("result");
+  equal(result, null, "function succeeded when called from a user input handler");
+  handle.destruct();
+
+  await extension.unload();
+  ExtensionAPIs.unregister("userinputtest");
+});
+
+// Test that the schema requireUserInput flag works correctly for
+// non-proxied api implementations.
+add_task(async function test_local() {
+  let apiString = `this.userinputtest = ${API.toString()};`;
+  let apiUrl = URL.createObjectURL(new Blob([apiString]));
+  await Schemas.load(schemaUrl);
+  const {apiManager} = Components.utils.import("resource://gre/modules/ExtensionPageChild.jsm", {});
+  apiManager.registerModules({
+    userinputtest: {
+      url: apiUrl,
+      scopes: ["addon_child"],
+      paths: [["userinputtest"]],
+    },
+  });
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background() {
+      browser.test.onMessage.addListener(async () => {
+        try {
+          await browser.userinputtest.test();
+          browser.test.sendMessage("result", null);
+        } catch (err) {
+          browser.test.sendMessage("result", err.message);
+        }
+      });
+    },
+    manifest: {},
+  });
+
+  await extension.startup();
+
+  extension.sendMessage("test");
+  let result = await extension.awaitMessage("result");
+  ok(/test may only be called from a user input handler/.test(result),
+     "function failed when not called from a user input handler");
+
+  let handle = setHandlingUserInput(extension);
+  extension.sendMessage("test");
+  result = await extension.awaitMessage("result");
+  equal(result, null, "function succeeded when called from a user input handler");
+  handle.destruct();
+
+  await extension.unload();
+});
--- a/toolkit/components/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell.ini
@@ -36,16 +36,17 @@ tags = webextensions in-process-webexten
 [test_ext_json_parser.js]
 [test_ext_manifest_content_security_policy.js]
 [test_ext_manifest_incognito.js]
 [test_ext_manifest_minimum_chrome_version.js]
 [test_ext_manifest_themes.js]
 [test_ext_schemas.js]
 [test_ext_schemas_async.js]
 [test_ext_schemas_allowed_contexts.js]
+[test_ext_schemas_interactive.js]
 [test_ext_schemas_revoke.js]
 [test_ext_themes_supported_properties.js]
 [test_ext_unknown_permissions.js]
 [test_locale_converter.js]
 [test_locale_data.js]
 
 [test_ext_permissions.js]
 skip-if = os == "android" # Bug 1350559
--- a/toolkit/components/resistfingerprinting/nsRFPService.cpp
+++ b/toolkit/components/resistfingerprinting/nsRFPService.cpp
@@ -157,24 +157,24 @@ nsRFPService::UpdatePref()
       }
       // PR_SetEnv() needs the input string been leaked intentionally, so
       // we copy it here.
       tz = ToNewCString(tzValue);
       if (tz) {
         PR_SetEnv(tz);
       }
     } else {
-#if defined(XP_LINUX) || defined (XP_MACOSX)
+#if defined(XP_WIN)
+      // For Windows, we reset the TZ to an empty string. This will make Windows to use
+      // its system timezone.
+      PR_SetEnv("TZ=");
+#else
       // For POSIX like system, we reset the TZ to the /etc/localtime, which is the
       // system timezone.
       PR_SetEnv("TZ=:/etc/localtime");
-#else
-      // For Windows, we reset the TZ to an empty string. This will make Windows to use
-      // its system timezone.
-      PR_SetEnv("TZ=");
 #endif
     }
   }
 
   nsJSUtils::ResetTimeZone();
 }
 
 void
--- a/toolkit/components/url-classifier/nsUrlClassifierHashCompleter.js
+++ b/toolkit/components/url-classifier/nsUrlClassifierHashCompleter.js
@@ -411,17 +411,17 @@ HashCompleterRequest.prototype = {
     });
   },
 
   notify: function HCR_notify() {
     // If we haven't gotten onStopRequest, just cancel. This will call us
     // with onStopRequest since we implement nsIStreamListener on the
     // channel.
     if (this._channel && this._channel.isPending()) {
-      log("cancelling request to " + this.gethashUrl + "\n");
+      log("cancelling request to " + this.gethashUrl + " (timeout)\n");
       Services.telemetry.getKeyedHistogramById("URLCLASSIFIER_COMPLETE_TIMEOUT2").
         add(this.telemetryProvider, 1);
       this._channel.cancel(Cr.NS_BINDING_ABORTED);
     }
   },
 
   // Creates an nsIChannel for the request and fills the body.
   openChannel: function HCR_openChannel() {
--- a/toolkit/components/url-classifier/nsUrlClassifierStreamUpdater.cpp
+++ b/toolkit/components/url-classifier/nsUrlClassifierStreamUpdater.cpp
@@ -972,25 +972,29 @@ nsUrlClassifierStreamUpdater::Notify(nsI
   if (timer == mResponseTimeoutTimer) {
     mResponseTimeoutTimer = nullptr;
     if (mTimeoutTimer) {
       mTimeoutTimer->Cancel();
       mTimeoutTimer = nullptr;
     }
     mDownloadError = true; // Trigger backoff
     updateFailed = true;
+    MOZ_LOG(gUrlClassifierStreamUpdaterLog, mozilla::LogLevel::Error,
+            ("Safe Browsing timed out while waiting for the update server to respond."));
     mozilla::Telemetry::Accumulate(mozilla::Telemetry::URLCLASSIFIER_UPDATE_TIMEOUT,
                                    mTelemetryProvider,
                                    static_cast<uint8_t>(eResponseTimeout));
   }
 
   if (timer == mTimeoutTimer) {
     mTimeoutTimer = nullptr;
     // No backoff since the connection may just be temporarily slow.
     updateFailed = true;
+    MOZ_LOG(gUrlClassifierStreamUpdaterLog, mozilla::LogLevel::Error,
+            ("Safe Browsing timed out while waiting for the update server to finish."));
     mozilla::Telemetry::Accumulate(mozilla::Telemetry::URLCLASSIFIER_UPDATE_TIMEOUT,
                                    mTelemetryProvider,
                                    static_cast<uint8_t>(eDownloadTimeout));
   }
 
   if (updateFailed) {
     // Cancelling the channel will trigger OnStopRequest.
     mozilla::Unused << mChannel->Cancel(NS_ERROR_ABORT);
--- a/tools/profiler/core/ProfileBuffer.h
+++ b/tools/profiler/core/ProfileBuffer.h
@@ -59,16 +59,18 @@ public:
   void StreamSamplesToJSON(SpliceableJSONWriter& aWriter, int aThreadId,
                            double aSinceTime, double* aOutFirstSampleTime,
                            JSContext* cx,
                            UniqueStacks& aUniqueStacks) const;
   void StreamMarkersToJSON(SpliceableJSONWriter& aWriter, int aThreadId,
                            const mozilla::TimeStamp& aProcessStartTime,
                            double aSinceTime,
                            UniqueStacks& aUniqueStacks) const;
+  void StreamPausedRangesToJSON(SpliceableJSONWriter& aWriter,
+                                double aSinceTime) const;
 
   // Find (via |aLS|) the most recent sample for the thread denoted by
   // |aThreadId| and clone it, patching in |aProcessStartTime| as appropriate.
   bool DuplicateLastSample(int aThreadId,
                            const mozilla::TimeStamp& aProcessStartTime,
                            LastSample& aLS);
 
   void AddStoredMarker(ProfilerMarker* aStoredMarker);
--- a/tools/profiler/core/ProfileBufferEntry.cpp
+++ b/tools/profiler/core/ProfileBufferEntry.cpp
@@ -585,30 +585,36 @@ public:
 
 private:
   const ProfileBufferEntry* const mEntries;
   int mReadPos;
   const int mWritePos;
   const int mEntrySize;
 };
 
-// Each sample is made up of multiple ProfileBuffer entries. The following
-// grammar shows legal sequences.
+// The following grammar shows legal sequences of profile buffer entries.
+// The sequences beginning with a ThreadId entry are known as "samples".
 //
 // (
-//   ThreadId
-//   Time
-//   ( NativeLeafAddr
-//   | Label DynamicStringFragment* LineNumber? Category?
-//   | JitReturnAddr
-//   )+
-//   Marker*
-//   Responsiveness?
-//   ResidentMemory?
-//   UnsharedMemory?
+//   (
+//     ThreadId
+//     Time
+//     ( NativeLeafAddr
+//     | Label DynamicStringFragment* LineNumber? Category?
+//     | JitReturnAddr
+//     )+
+//     Marker*
+//     Responsiveness?
+//     ResidentMemory?
+//     UnsharedMemory?
+//   )
+//   | CollectionStart
+//   | CollectionEnd
+//   | Pause
+//   | Resume
 // )*
 //
 // The most complicated part is the stack entry sequence that begins with
 // Label. Here are some examples.
 //
 // - PseudoStack entries without a dynamic string:
 //
 //     Label("js::RunScript")
@@ -700,78 +706,83 @@ ProfileBuffer::StreamSamplesToJSON(Splic
                                    JSContext* aContext,
                                    UniqueStacks& aUniqueStacks) const
 {
   UniquePtr<char[]> strbuf = MakeUnique<char[]>(kMaxFrameKeyLength);
 
   // Because this is a format entirely internal to the Profiler, any parsing
   // error indicates a bug in the ProfileBuffer writing or the parser itself,
   // or possibly flaky hardware.
-  #define ERROR_AND_SKIP_TO_NEXT_SAMPLE(msg) \
-    do { \
+  #define ERROR_AND_CONTINUE(msg) \
+    { \
       fprintf(stderr, "ProfileBuffer parse error: %s", msg); \
       MOZ_ASSERT(false, msg); \
-      goto skip_to_next_sample; \
-    } while (0)
+      continue; \
+    }
 
   EntryGetter e(*this);
   bool seenFirstSample = false;
 
-  // This block skips entries until we find the start of the next sample. This
-  // is useful in two situations.
-  //
-  // - The circular buffer overwrites old entries, so when we start parsing we
-  //   might be in the middle of a sample, and we must skip forward to the
-  //   start of the next sample.
-  //
-  // - We skip samples that don't have an appropriate ThreadId or Time.
-  //
-skip_to_next_sample:
-  while (e.Has()) {
-    if (e.Get().IsThreadId()) {
+  for (;;) {
+    // This block skips entries until we find the start of the next sample.
+    // This is useful in three situations.
+    //
+    // - The circular buffer overwrites old entries, so when we start parsing
+    //   we might be in the middle of a sample, and we must skip forward to the
+    //   start of the next sample.
+    //
+    // - We skip samples that don't have an appropriate ThreadId or Time.
+    //
+    // - We skip range Pause, Resume, CollectionStart, and CollectionEnd
+    //   entries between samples.
+    while (e.Has()) {
+      if (e.Get().IsThreadId()) {
+        break;
+      } else {
+        e.Next();
+      }
+    }
+
+    if (!e.Has()) {
       break;
-    } else {
-      e.Next();
     }
-  }
 
-  while (e.Has()) {
     if (e.Get().IsThreadId()) {
       int threadId = e.Get().u.mInt;
       e.Next();
 
       // Ignore samples that are for the wrong thread.
       if (threadId != aThreadId) {
-        goto skip_to_next_sample;
+        continue;
       }
     } else {
       // Due to the skip_to_next_sample block above, if we have an entry here
       // it must be a ThreadId entry.
       MOZ_CRASH();
     }
 
     ProfileSample sample;
 
     if (e.Has() && e.Get().IsTime()) {
       sample.mTime = e.Get().u.mDouble;
       e.Next();
 
       // Ignore samples that are too old.
       if (sample.mTime < aSinceTime) {
-        goto skip_to_next_sample;
+        continue;
       }
 
       if (!seenFirstSample) {
         if (aOutFirstSampleTime) {
           *aOutFirstSampleTime = sample.mTime;
         }
         seenFirstSample = true;
       }
     } else {
-      ERROR_AND_SKIP_TO_NEXT_SAMPLE("expected a Time entry");
+      ERROR_AND_CONTINUE("expected a Time entry");
     }
 
     UniqueStacks::Stack stack =
       aUniqueStacks.BeginStack(UniqueStacks::OnStackFrameKey("(root)"));
 
     int numFrames = 0;
     while (e.Has()) {
       if (e.Get().IsNativeLeafAddr()) {
@@ -857,17 +868,17 @@ skip_to_next_sample:
         e.Next();
 
       } else {
         break;
       }
     }
 
     if (numFrames == 0) {
-      ERROR_AND_SKIP_TO_NEXT_SAMPLE("expected one or more frame entries");
+      ERROR_AND_CONTINUE("expected one or more frame entries");
     }
 
     sample.mStack = stack.GetOrAddIndex();
 
     // Skip over the markers. We process them in StreamMarkersToJSON().
     while (e.Has()) {
       if (e.Get().IsMarker()) {
         e.Next();
@@ -889,17 +900,17 @@ skip_to_next_sample:
     if (e.Has() && e.Get().IsUnsharedMemory()) {
       sample.mUSS = Some(e.Get().u.mDouble);
       e.Next();
     }
 
     WriteSample(aWriter, sample);
   }
 
-  #undef ERROR_AND_SKIP_TO_NEXT_SAMPLE
+  #undef ERROR_AND_CONTINUE
 }
 
 void
 ProfileBuffer::StreamMarkersToJSON(SpliceableJSONWriter& aWriter,
                                    int aThreadId,
                                    const TimeStamp& aProcessStartTime,
                                    double aSinceTime,
                                    UniqueStacks& aUniqueStacks) const
@@ -918,16 +929,71 @@ ProfileBuffer::StreamMarkersToJSON(Splic
       if (marker->GetTime() >= aSinceTime) {
         marker->StreamJSON(aWriter, aProcessStartTime, aUniqueStacks);
       }
     }
     e.Next();
   }
 }
 
+static void
+AddPausedRange(SpliceableJSONWriter& aWriter, const char* aReason,
+               const Maybe<double>& aStartTime, const Maybe<double>& aEndTime)
+{
+  aWriter.Start(SpliceableJSONWriter::SingleLineStyle);
+  if (aStartTime) {
+    aWriter.DoubleProperty("startTime", *aStartTime);
+  } else {
+    aWriter.NullProperty("startTime");
+  }
+  if (aEndTime) {
+    aWriter.DoubleProperty("endTime", *aEndTime);
+  } else {
+    aWriter.NullProperty("endTime");
+  }
+  aWriter.StringProperty("reason", aReason);
+  aWriter.End();
+}
+
+void
+ProfileBuffer::StreamPausedRangesToJSON(SpliceableJSONWriter& aWriter,
+                                        double aSinceTime) const
+{
+  EntryGetter e(*this);
+
+  Maybe<double> currentPauseStartTime;
+  Maybe<double> currentCollectionStartTime;
+
+  while (e.Has()) {
+    if (e.Get().IsPause()) {
+      currentPauseStartTime = Some(e.Get().u.mDouble);
+    } else if (e.Get().IsResume()) {
+      AddPausedRange(aWriter, "profiler-paused",
+                     currentPauseStartTime, Some(e.Get().u.mDouble));
+      currentPauseStartTime = Nothing();
+    } else if (e.Get().IsCollectionStart()) {
+      currentCollectionStartTime = Some(e.Get().u.mDouble);
+    } else if (e.Get().IsCollectionEnd()) {
+      AddPausedRange(aWriter, "collecting",
+                     currentCollectionStartTime, Some(e.Get().u.mDouble));
+      currentCollectionStartTime = Nothing();
+    }
+    e.Next();
+  }
+
+  if (currentPauseStartTime) {
+    AddPausedRange(aWriter, "profiler-paused",
+                   currentPauseStartTime, Nothing());
+  }
+  if (currentCollectionStartTime) {
+    AddPausedRange(aWriter, "collecting",
+                   currentCollectionStartTime, Nothing());
+  }
+}
+
 int
 ProfileBuffer::FindLastSampleOfThread(int aThreadId, const LastSample& aLS)
   const
 {
   // |aLS| has a valid generation number if either it matches the buffer's
   // generation, or is one behind the buffer's generation, since the buffer's
   // generation is incremented on wraparound.  There's no ambiguity relative to
   // ProfileBuffer::reset, since that increments mGeneration by two.
@@ -970,16 +1036,20 @@ ProfileBuffer::DuplicateLastSample(int a
 
   AddThreadIdEntry(aThreadId, &aLS);
 
   // Go through the whole entry and duplicate it, until we find the next one.
   for (int readPos = (lastSampleStartPos + 1) % mEntrySize;
        readPos != mWritePos;
        readPos = (readPos + 1) % mEntrySize) {
     switch (mEntries[readPos].GetKind()) {
+      case ProfileBufferEntry::Kind::Pause:
+      case ProfileBufferEntry::Kind::Resume:
+      case ProfileBufferEntry::Kind::CollectionStart:
+      case ProfileBufferEntry::Kind::CollectionEnd:
       case ProfileBufferEntry::Kind::ThreadId:
         // We're done.
         return true;
       case ProfileBufferEntry::Kind::Time:
         // Copy with new time
         AddEntry(ProfileBufferEntry::Time(
           (TimeStamp::Now() - aProcessStartTime).ToMilliseconds()));
         break;
--- a/tools/profiler/core/ProfileBufferEntry.h
+++ b/tools/profiler/core/ProfileBufferEntry.h
@@ -24,24 +24,28 @@
 #include "gtest/MozGtestFriend.h"
 #include "mozilla/HashFunctions.h"
 #include "mozilla/UniquePtr.h"
 
 class ProfilerMarker;
 
 #define FOR_EACH_PROFILE_BUFFER_ENTRY_KIND(macro) \
   macro(Category,              int) \
+  macro(CollectionStart,       double) \
+  macro(CollectionEnd,         double) \
   macro(Label,                 const char*) \
   macro(DynamicStringFragment, char*) /* char[kNumChars], really */ \
   macro(JitReturnAddr,         void*) \
   macro(LineNumber,            int) \
   macro(NativeLeafAddr,        void*) \
   macro(Marker,                ProfilerMarker*) \
+  macro(Pause,                 double) \
   macro(ResidentMemory,        double) \
   macro(Responsiveness,        double) \
+  macro(Resume,                double) \
   macro(ThreadId,              int) \
   macro(Time,                  double) \
   macro(UnsharedMemory,        double)
 
 // NB: Packing this structure has been shown to cause SIGBUS issues on ARM.
 #if !defined(GP_ARCH_arm)
 #pragma pack(push, 1)
 #endif
--- a/tools/profiler/core/ProfilerBacktrace.cpp
+++ b/tools/profiler/core/ProfilerBacktrace.cpp
@@ -30,15 +30,17 @@ ProfilerBacktrace::StreamJSON(Spliceable
 {
   // This call to StreamSamplesAndMarkers() can safely pass in a non-null
   // JSContext. That's because StreamSamplesAndMarkers() only accesses the
   // JSContext when streaming JitReturnAddress entries, and such entries
   // never appear in synchronous samples.
   double firstSampleTimeIgnored;
   StreamSamplesAndMarkers(mName.get(), mThreadId,
                           *mBuffer.get(), aWriter, aProcessStartTime,
+                          /* aRegisterTime */ TimeStamp(),
+                          /* aUnregisterTime */ TimeStamp(),
                           /* aSinceTime */ 0, &firstSampleTimeIgnored,
                           /* aContext */ nullptr,
                           /* aSavedStreamedSamples */ nullptr,
                           /* aFirstSavedStreamedSampleTime */ 0.0,
                           /* aSavedStreamedMarkers */ nullptr,
                           aUniqueStacks);
 }
--- a/tools/profiler/core/ThreadInfo.cpp
+++ b/tools/profiler/core/ThreadInfo.cpp
@@ -17,16 +17,17 @@
 #define getpid _getpid
 #else
 #include <unistd.h> // for getpid()
 #endif
 
 ThreadInfo::ThreadInfo(const char* aName, int aThreadId, bool aIsMainThread,
                        void* aStackTop)
   : mName(strdup(aName))
+  , mRegisterTime(TimeStamp::Now())
   , mThreadId(aThreadId)
   , mIsMainThread(aIsMainThread)
   , mRacyInfo(mozilla::WrapNotNull(new RacyThreadInfo()))
   , mPlatformData(AllocPlatformData(aThreadId))
   , mStackTop(aStackTop)
   , mIsBeingProfiled(false)
   , mContext(nullptr)
   , mJSSampling(INACTIVE)
@@ -79,17 +80,19 @@ ThreadInfo::StreamJSON(const ProfileBuff
     mUniqueStacks.emplace(mContext);
   }
 
   double firstSampleTime = 0.0;
 
   aWriter.Start(SpliceableJSONWriter::SingleLineStyle);
   {
     StreamSamplesAndMarkers(Name(), ThreadId(), aBuffer, aWriter,
-                            aProcessStartTime, aSinceTime, &firstSampleTime,
+                            aProcessStartTime,
+                            mRegisterTime, mUnregisterTime,
+                            aSinceTime, &firstSampleTime,
                             mContext,
                             mSavedStreamedSamples.get(),
                             mFirstSavedStreamedSampleTime,
                             mSavedStreamedMarkers.get(),
                             *mUniqueStacks);
     mSavedStreamedSamples.reset();
     mFirstSavedStreamedSampleTime = 0.0;
     mSavedStreamedMarkers.reset();
@@ -143,31 +146,47 @@ ThreadInfo::StreamJSON(const ProfileBuff
 }
 
 void
 StreamSamplesAndMarkers(const char* aName,
                         int aThreadId,
                         const ProfileBuffer& aBuffer,
                         SpliceableJSONWriter& aWriter,
                         const TimeStamp& aProcessStartTime,
+                        const TimeStamp& aRegisterTime,
+                        const TimeStamp& aUnregisterTime,
                         double aSinceTime,
                         double* aOutFirstSampleTime,
                         JSContext* aContext,
                         char* aSavedStreamedSamples,
                         double aFirstSavedStreamedSampleTime,
                         char* aSavedStreamedMarkers,
                         UniqueStacks& aUniqueStacks)
 {
   aWriter.StringProperty("processType",
                          XRE_ChildProcessTypeToString(XRE_GetProcessType()));
 
   aWriter.StringProperty("name", aName);
   aWriter.IntProperty("tid", static_cast<int64_t>(aThreadId));
   aWriter.IntProperty("pid", static_cast<int64_t>(getpid()));
 
+  if (aRegisterTime) {
+    aWriter.DoubleProperty("registerTime",
+      (aRegisterTime - aProcessStartTime).ToMilliseconds());
+  } else {
+    aWriter.NullProperty("registerTime");
+  }
+
+  if (aUnregisterTime) {
+    aWriter.DoubleProperty("unregisterTime",
+      (aUnregisterTime - aProcessStartTime).ToMilliseconds());
+  } else {
+    aWriter.NullProperty("unregisterTime");
+  }
+
   aWriter.StartObjectProperty("samples");
   {
     {
       JSONSchemaWriter schema(aWriter);
       schema.WriteField("stack");
       schema.WriteField("time");
       schema.WriteField("responsiveness");
       schema.WriteField("rss");
--- a/tools/profiler/core/ThreadInfo.h
+++ b/tools/profiler/core/ThreadInfo.h
@@ -3,16 +3,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/. */
 
 #ifndef ThreadInfo_h
 #define ThreadInfo_h
 
 #include "mozilla/NotNull.h"
+#include "mozilla/TimeStamp.h"
 #include "mozilla/UniquePtrExtensions.h"
 
 #include "platform.h"
 #include "ProfileBuffer.h"
 #include "js/ProfilingStack.h"
 
 // This class contains the info for a single thread that is accessible without
 // protection from gPSMutex in platform.cpp. Because there is no external
@@ -177,25 +178,29 @@ public:
   bool IsMainThread() const { return mIsMainThread; }
 
   mozilla::NotNull<RacyThreadInfo*> RacyInfo() const { return mRacyInfo; }
 
   void StartProfiling();
   void StopProfiling();
   bool IsBeingProfiled() { return mIsBeingProfiled; }
 
+  void NotifyUnregistered() { mUnregisterTime = TimeStamp::Now(); }
+
   PlatformData* GetPlatformData() const { return mPlatformData.get(); }
   void* StackTop() const { return mStackTop; }
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
   ProfileBuffer::LastSample& LastSample() { return mLastSample; }
 
 private:
   mozilla::UniqueFreePtr<char> mName;
+  mozilla::TimeStamp mRegisterTime;
+  mozilla::TimeStamp mUnregisterTime;
   int mThreadId;
   const bool mIsMainThread;
 
   // The thread's RacyThreadInfo. This is an owning pointer. It could be an
   // inline member, but we don't do that because RacyThreadInfo is quite large
   // (due to the PseudoStack within it), and we have ThreadInfo vectors and so
   // we'd end up wasting a lot of space in those vectors for excess elements.
   mozilla::NotNull<RacyThreadInfo*> mRacyInfo;
@@ -370,16 +375,18 @@ private:
   ProfileBuffer::LastSample mLastSample;
 };
 
 void
 StreamSamplesAndMarkers(const char* aName, int aThreadId,
                         const ProfileBuffer& aBuffer,
                         SpliceableJSONWriter& aWriter,
                         const mozilla::TimeStamp& aProcessStartTime,
+                        const TimeStamp& aRegisterTime,
+                        const TimeStamp& aUnregisterTime,
                         double aSinceTime,
                         double* aOutFirstSampleTime,
                         JSContext* aContext,
                         char* aSavedStreamedSamples,
                         double aFirstSavedStreamedSampleTime,
                         char* aSavedStreamedMarkers,
                         UniqueStacks& aUniqueStacks);
 
--- a/tools/profiler/core/platform.cpp
+++ b/tools/profiler/core/platform.cpp
@@ -1437,29 +1437,39 @@ StreamTaskTracer(PSLockRef aLock, Splice
   aWriter.EndArray();
 
   aWriter.DoubleProperty(
     "start", static_cast<double>(tasktracer::GetStartTime()));
 #endif
 }
 
 static void
-StreamMetaJSCustomObject(PSLockRef aLock, SpliceableJSONWriter& aWriter)
+StreamMetaJSCustomObject(PSLockRef aLock, SpliceableJSONWriter& aWriter,
+                         const TimeStamp& aShutdownTime)
 {
   MOZ_RELEASE_ASSERT(CorePS::Exists() && ActivePS::Exists(aLock));
 
-  aWriter.IntProperty("version", 7);
+  aWriter.IntProperty("version", 8);
 
   // The "startTime" field holds the number of milliseconds since midnight
   // January 1, 1970 GMT. This grotty code computes (Now - (Now -
   // ProcessStartTime)) to convert CorePS::ProcessStartTime() into that form.
   TimeDuration delta = TimeStamp::Now() - CorePS::ProcessStartTime();
   aWriter.DoubleProperty(
     "startTime", static_cast<double>(PR_Now()/1000.0 - delta.ToMilliseconds()));
 
+  // Write the shutdownTime field. Unlike startTime, shutdownTime is not an
+  // absolute time stamp: It's relative to startTime. This is consistent with
+  // all other (non-"startTime") times anywhere in the profile JSON.
+  if (aShutdownTime) {
+    aWriter.DoubleProperty("shutdownTime", profiler_time());
+  } else {
+    aWriter.NullProperty("shutdownTime");
+  }
+
   if (!NS_IsMainThread()) {
     // Leave the rest of the properties out if we're not on the main thread.
     // At the moment, the only case in which this function is called on a
     // background thread is if we're in a content process and are going to
     // send this profile to the parent process. In that case, the parent
     // process profile's "meta" object already has the rest of the properties,
     // and the parent process profile is dumped on that process's main thread.
     return;
@@ -1581,31 +1591,37 @@ BuildJavaThreadJSObject(SpliceableJSONWr
   }
   aWriter.EndArray();
 }
 #endif
 
 static TimeStamp
 locked_profiler_stream_json_for_this_process(PSLockRef aLock,
                                              SpliceableJSONWriter& aWriter,
-                                             double aSinceTime)
+                                             double aSinceTime,
+                                             bool aIsShuttingDown)
 {
   LOG("locked_profiler_stream_json_for_this_process");
 
   MOZ_RELEASE_ASSERT(CorePS::Exists() && ActivePS::Exists(aLock));
 
+  double collectionStart = profiler_time();
+
+  ProfileBuffer& buffer = ActivePS::Buffer(aLock);
+
   // Put shared library info
   aWriter.StartArrayProperty("libs");
   AppendSharedLibraries(aWriter);
   aWriter.EndArray();
 
   // Put meta data
   aWriter.StartObjectProperty("meta");
   {
-    StreamMetaJSCustomObject(aLock, aWriter);
+    StreamMetaJSCustomObject(aLock, aWriter,
+                             aIsShuttingDown ? TimeStamp::Now() : TimeStamp());
   }
   aWriter.EndObject();
 
   // Data of TaskTracer doesn't belong in the circular buffer.
   if (ActivePS::FeatureTaskTracer(aLock)) {
     aWriter.StartObjectProperty("tasktracer");
     StreamTaskTracer(aLock, aWriter);
     aWriter.EndObject();
@@ -1618,28 +1634,28 @@ locked_profiler_stream_json_for_this_pro
   {
     const CorePS::ThreadVector& liveThreads = CorePS::LiveThreads(aLock);
     for (size_t i = 0; i < liveThreads.size(); i++) {
       ThreadInfo* info = liveThreads.at(i);
       if (!info->IsBeingProfiled()) {
         continue;
       }
       double thisThreadFirstSampleTime =
-        info->StreamJSON(ActivePS::Buffer(aLock), aWriter,
+        info->StreamJSON(buffer, aWriter,
                          CorePS::ProcessStartTime(), aSinceTime);
       firstSampleTime = std::min(thisThreadFirstSampleTime, firstSampleTime);
     }
 
     const CorePS::ThreadVector& deadThreads = CorePS::DeadThreads(aLock);
     for (size_t i = 0; i < deadThreads.size(); i++) {
       ThreadInfo* info = deadThreads.at(i);
       MOZ_ASSERT(info->IsBeingProfiled());
       double thisThreadFirstSampleTime =
-        info->StreamJSON(ActivePS::Buffer(aLock), aWriter,
-                        CorePS::ProcessStartTime(), aSinceTime);
+        info->StreamJSON(buffer, aWriter,
+                         CorePS::ProcessStartTime(), aSinceTime);
       firstSampleTime = std::min(thisThreadFirstSampleTime, firstSampleTime);
     }
 
 #if defined(GP_OS_android)
     if (ActivePS::FeatureJava(aLock)) {
       java::GeckoJavaSampler::Pause();
 
       aWriter.Start();
@@ -1649,41 +1665,60 @@ locked_profiler_stream_json_for_this_pro
       aWriter.End();
 
       java::GeckoJavaSampler::Unpause();
     }
 #endif
   }
   aWriter.EndArray();
 
+  aWriter.StartArrayProperty("pausedRanges",
+                             SpliceableJSONWriter::SingleLineStyle);
+  {
+    buffer.StreamPausedRangesToJSON(aWriter, aSinceTime);
+  }
+  aWriter.EndArray();
+
+  double collectionEnd = profiler_time();
+
+  // Record timestamps for the collection into the buffer, so that consumers
+  // know why we didn't collect any samples for its duration.
+  // We put these entries into the buffer after we've collected the profile,
+  // so they'll be visible for the *next* profile collection (if they haven't
+  // been overwritten due to buffer wraparound by then).
+  buffer.AddEntry(ProfileBufferEntry::CollectionStart(collectionStart));
+  buffer.AddEntry(ProfileBufferEntry::CollectionEnd(collectionEnd));
+
   if (firstSampleTime != INFINITY) {
     return CorePS::ProcessStartTime() +
            TimeDuration::FromMilliseconds(firstSampleTime);
   }
 
   return TimeStamp();
 }
 
 bool
 profiler_stream_json_for_this_process(SpliceableJSONWriter& aWriter,
                                       double aSinceTime,
+                                      bool aIsShuttingDown,
                                       TimeStamp* aOutFirstSampleTime)
 {
   LOG("profiler_stream_json_for_this_process");
 
   MOZ_RELEASE_ASSERT(CorePS::Exists());
 
   PSAutoLock lock(gPSMutex);
 
   if (!ActivePS::Exists(lock)) {
     return false;
   }
 
   TimeStamp firstSampleTime =
-    locked_profiler_stream_json_for_this_process(lock, aWriter, aSinceTime);
+    locked_profiler_stream_json_for_this_process(lock, aWriter, aSinceTime,
+                                                 aIsShuttingDown);
 
   if (aOutFirstSampleTime) {
     *aOutFirstSampleTime = firstSampleTime;
   }
 
   return true;
 }
 
@@ -2344,17 +2379,18 @@ profiler_init(void* aStackTop)
 
   // We do this with gPSMutex unlocked. The comment in profiler_stop() explains
   // why.
   NotifyProfilerStarted(entries, interval, features,
                         filters.Elements(), filters.Length());
 }
 
 static void
-locked_profiler_save_profile_to_file(PSLockRef aLock, const char* aFilename);
+locked_profiler_save_profile_to_file(PSLockRef aLock, const char* aFilename,
+                                     bool aIsShuttingDown);
 
 static SamplerThread*
 locked_profiler_stop(PSLockRef aLock);
 
 void
 profiler_shutdown()
 {
   LOG("profiler_shutdown");
@@ -2367,17 +2403,18 @@ profiler_shutdown()
   SamplerThread* samplerThread = nullptr;
   {
     PSAutoLock lock(gPSMutex);
 
     // Save the profile on shutdown if requested.
     if (ActivePS::Exists(lock)) {
       const char* filename = getenv("MOZ_PROFILER_SHUTDOWN");
       if (filename) {
-        locked_profiler_save_profile_to_file(lock, filename);
+        locked_profiler_save_profile_to_file(lock, filename,
+                                             /* aIsShuttingDown */ true);
       }
 
       samplerThread = locked_profiler_stop(lock);
     }
 
     CorePS::Destroy(lock);
 
     // We just destroyed CorePS and the ThreadInfos it contains, so we can
@@ -2394,26 +2431,27 @@ profiler_shutdown()
   if (samplerThread) {
     ProfilerParent::ProfilerStopped();
     NotifyObservers("profiler-stopped");
     delete samplerThread;
   }
 }
 
 UniquePtr<char[]>
-profiler_get_profile(double aSinceTime)
+profiler_get_profile(double aSinceTime, bool aIsShuttingDown)
 {
   LOG("profiler_get_profile");
 
   MOZ_RELEASE_ASSERT(CorePS::Exists());
 
   SpliceableChunkedJSONWriter b;
   b.Start(SpliceableJSONWriter::SingleLineStyle);
   {
-    if (!profiler_stream_json_for_this_process(b, aSinceTime)) {
+    if (!profiler_stream_json_for_this_process(b, aSinceTime,
+                                               aIsShuttingDown)) {
       return nullptr;
     }
 
     // Don't include profiles from other processes because this is a
     // synchronous function.
     b.StartArrayProperty("processes");
     b.EndArray();
   }
@@ -2504,29 +2542,31 @@ AutoSetProfilerEnvVarsForChildProcess::~
   PR_SetEnv("MOZ_PROFILER_STARTUP=");
   PR_SetEnv("MOZ_PROFILER_STARTUP_ENTRIES=");
   PR_SetEnv("MOZ_PROFILER_STARTUP_INTERVAL=");
   PR_SetEnv("MOZ_PROFILER_STARTUP_FEATURES_BITFIELD=");
   PR_SetEnv("MOZ_PROFILER_STARTUP_FILTERS=");
 }
 
 static void
-locked_profiler_save_profile_to_file(PSLockRef aLock, const char* aFilename)
+locked_profiler_save_profile_to_file(PSLockRef aLock, const char* aFilename,
+                                     bool aIsShuttingDown = false)
 {
   LOG("locked_profiler_save_profile_to_file(%s)", aFilename);
 
   MOZ_RELEASE_ASSERT(CorePS::Exists() && ActivePS::Exists(aLock));
 
   std::ofstream stream;
   stream.open(aFilename);
   if (stream.is_open()) {
     SpliceableJSONWriter w(MakeUnique<OStreamJSONWriteFunc>(stream));
     w.Start(SpliceableJSONWriter::SingleLineStyle);
     {
-      locked_profiler_stream_json_for_this_process(aLock, w, /* sinceTime */ 0);
+      locked_profiler_stream_json_for_this_process(aLock, w, /* sinceTime */ 0,
+                                                   aIsShuttingDown);
 
       // Don't include profiles from other processes because this is a
       // synchronous function.
       w.StartArrayProperty("processes");
       w.EndArray();
     }
     w.End();
 
@@ -2875,16 +2915,17 @@ profiler_pause()
   {
     PSAutoLock lock(gPSMutex);
 
     if (!ActivePS::Exists(lock)) {
       return;
     }
 
     ActivePS::SetIsPaused(lock, true);
+    ActivePS::Buffer(lock).AddEntry(ProfileBufferEntry::Pause(profiler_time()));
   }
 
   // gPSMutex must be unlocked when we notify, to avoid potential deadlocks.
   ProfilerParent::ProfilerPaused();
   NotifyObservers("profiler-paused");
 }
 
 void
@@ -2896,16 +2937,17 @@ profiler_resume()
 
   {
     PSAutoLock lock(gPSMutex);
 
     if (!ActivePS::Exists(lock)) {
       return;
     }
 
+    ActivePS::Buffer(lock).AddEntry(ProfileBufferEntry::Resume(profiler_time()));
     ActivePS::SetIsPaused(lock, false);
   }
 
   // gPSMutex must be unlocked when we notify, to avoid potential deadlocks.
   ProfilerParent::ProfilerResumed();
   NotifyObservers("profiler-resumed");
 }
 
@@ -2957,16 +2999,17 @@ profiler_unregister_thread()
   // that for a JS thread that is in the process of disappearing.
 
   int i;
   ThreadInfo* info = FindLiveThreadInfo(lock, &i);
   MOZ_RELEASE_ASSERT(info == TLSInfo::Info(lock));
   if (info) {
     DEBUG_LOG("profiler_unregister_thread: %s", info->Name());
     if (ActivePS::Exists(lock) && info->IsBeingProfiled()) {
+      info->NotifyUnregistered();
       CorePS::DeadThreads(lock).push_back(info);
     } else {
       delete info;
     }
     CorePS::ThreadVector& liveThreads = CorePS::LiveThreads(lock);
     liveThreads.erase(liveThreads.begin() + i);
 
     // Whether or not we just destroyed the ThreadInfo or transferred it to the
--- a/tools/profiler/gecko/ProfilerChild.cpp
+++ b/tools/profiler/gecko/ProfilerChild.cpp
@@ -70,32 +70,33 @@ ProfilerChild::RecvPause()
 mozilla::ipc::IPCResult
 ProfilerChild::RecvResume()
 {
   profiler_resume();
   return IPC_OK();
 }
 
 static nsCString
-CollectProfileOrEmptyString()
+CollectProfileOrEmptyString(bool aIsShuttingDown)
 {
   nsCString profileCString;
-  UniquePtr<char[]> profile = profiler_get_profile();
+  UniquePtr<char[]> profile =
+    profiler_get_profile(/* aSinceTime */ 0, aIsShuttingDown);
   if (profile) {
     profileCString = nsCString(profile.get(), strlen(profile.get()));
   } else {
     profileCString = EmptyCString();
   }
   return profileCString;
 }
 
 mozilla::ipc::IPCResult
 ProfilerChild::RecvGatherProfile(GatherProfileResolver&& aResolve)
 {
-  aResolve(CollectProfileOrEmptyString());
+  aResolve(CollectProfileOrEmptyString(/* aIsShuttingDown */ false));
   return IPC_OK();
 }
 
 void
 ProfilerChild::ActorDestroy(ActorDestroyReason aActorDestroyReason)
 {
   mDestroyed = true;
 }
@@ -106,12 +107,12 @@ ProfilerChild::Destroy()
   if (!mDestroyed) {
     Close();
   }
 }
 
 nsCString
 ProfilerChild::GrabShutdownProfile()
 {
-  return CollectProfileOrEmptyString();
+  return CollectProfileOrEmptyString(/* aIsShuttingDown */ true);
 }
 
 } // namespace mozilla
--- a/tools/profiler/gecko/nsProfiler.cpp
+++ b/tools/profiler/gecko/nsProfiler.cpp
@@ -565,16 +565,17 @@ nsProfiler::StartGathering(double aSince
 
   mWriter.emplace();
 
   TimeStamp thisProcessFirstSampleTime;
 
   // Start building up the JSON result and grab the profile from this process.
   mWriter->Start(SpliceableJSONWriter::SingleLineStyle);
   if (!profiler_stream_json_for_this_process(*mWriter, aSinceTime,
+                                             /* aIsShuttingDown */ true,
                                              &thisProcessFirstSampleTime)) {
     // The profiler is inactive. This either means that it was inactive even
     // at the time that ProfileGatherer::Start() was called, or that it was
     // stopped on a different thread since that call. Either way, we need to
     // reject the promise and stop gathering.
     return GatheringPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
   }
 
--- a/tools/profiler/public/GeckoProfiler.h
+++ b/tools/profiler/public/GeckoProfiler.h
@@ -448,25 +448,29 @@ PROFILER_FUNC_VOID(profiler_tracing(cons
                                     TracingKind aKind = TRACING_EVENT))
 
 //---------------------------------------------------------------------------
 // Output profiles
 //---------------------------------------------------------------------------
 
 // Get the profile encoded as a JSON string. A no-op (returning nullptr) if the
 // profiler is inactive.
+// If aIsShuttingDown is true, the current time is included as the process
+// shutdown time in the JSON's "meta" object.
 PROFILER_FUNC(
-  mozilla::UniquePtr<char[]> profiler_get_profile(double aSinceTime = 0),
+  mozilla::UniquePtr<char[]> profiler_get_profile(double aSinceTime = 0,
+                                                  bool aIsShuttingDown = false),
   nullptr)
 
 // Write the profile for this process (excluding subprocesses) into aWriter.
 // Returns false if the profiler is inactive.
 PROFILER_FUNC(
   bool profiler_stream_json_for_this_process(SpliceableJSONWriter& aWriter,
                                              double aSinceTime = 0,
+                                             bool aIsShuttingDown = false,
                                              mozilla::TimeStamp* aOutFirstSampleTime = nullptr),
   false)
 
 // Get the profile and write it into a file. A no-op if the profile is
 // inactive.
 //
 // This function is 'extern "C"' so that it is easily callable from a debugger
 // in a build without debugging information (a workaround for
--- a/uriloader/exthandler/ExternalHelperAppChild.cpp
+++ b/uriloader/exthandler/ExternalHelperAppChild.cpp
@@ -65,17 +65,20 @@ ExternalHelperAppChild::OnStartRequest(n
   NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
 
   // Calling OnStartRequest could cause mHandler to close the window it was
   // loaded for. In that case, the TabParent in the parent context might then
   // point to the wrong window. Re-send the window context along with either
   // DivertToParent or SendOnStartRequest just in case.
   nsCOMPtr<nsPIDOMWindowOuter> window =
     do_GetInterface(mHandler->GetDialogParent());
-  TabChild *tabChild = window ? mozilla::dom::TabChild::GetFrom(window) : nullptr;
+  NS_ENSURE_TRUE(window, NS_ERROR_NOT_AVAILABLE);
+
+  TabChild *tabChild = mozilla::dom::TabChild::GetFrom(window);
+  MOZ_ASSERT(tabChild);
 
   nsCOMPtr<nsIDivertableChannel> divertable = do_QueryInterface(request);
   if (divertable) {
     return DivertToParent(divertable, request, tabChild);
   }
 
   nsCString entityID;
   nsCOMPtr<nsIResumableChannel> resumable(do_QueryInterface(request));
--- a/xpcom/base/nsCycleCollectionNoteRootCallback.h
+++ b/xpcom/base/nsCycleCollectionNoteRootCallback.h
@@ -8,17 +8,20 @@
 #define nsCycleCollectionNoteRootCallback_h__
 
 class nsCycleCollectionParticipant;
 class nsISupports;
 
 class nsCycleCollectionNoteRootCallback
 {
 public:
-  NS_IMETHOD_(void) NoteXPCOMRoot(nsISupports* aRoot) = 0;
+  // aRoot must be canonical (ie the result of QIing to nsCycleCollectionISupports).
+  NS_IMETHOD_(void) NoteXPCOMRoot(nsISupports* aRoot,
+                                  nsCycleCollectionParticipant* aParticipant) = 0;
+
   NS_IMETHOD_(void) NoteJSRoot(JSObject* aRoot) = 0;
   NS_IMETHOD_(void) NoteNativeRoot(void* aRoot,
                                    nsCycleCollectionParticipant* aParticipant) = 0;
 
   NS_IMETHOD_(void) NoteWeakMapping(JSObject* aMap, JS::GCCellPtr aKey,
                                     JSObject* aKeyDelegate, JS::GCCellPtr aVal) = 0;
 
   bool WantAllTraces() const { return mWantAllTraces; }
--- a/xpcom/base/nsCycleCollector.cpp
+++ b/xpcom/base/nsCycleCollector.cpp
@@ -2097,17 +2097,18 @@ private:
 
   void SetLastChild()
   {
     mCurrPi->SetLastChild(mEdgeBuilder.Mark());
   }
 
 public:
   // nsCycleCollectionNoteRootCallback methods.
-  NS_IMETHOD_(void) NoteXPCOMRoot(nsISupports* aRoot);
+  NS_IMETHOD_(void) NoteXPCOMRoot(nsISupports* aRoot,
+                                  nsCycleCollectionParticipant* aParticipant);
   NS_IMETHOD_(void) NoteJSRoot(JSObject* aRoot);
   NS_IMETHOD_(void) NoteNativeRoot(void* aRoot,
                                    nsCycleCollectionParticipant* aParticipant);
   NS_IMETHOD_(void) NoteWeakMapping(JSObject* aMap, JS::GCCellPtr aKey,
                                     JSObject* aKdelegate, JS::GCCellPtr aVal);
 
   // nsCycleCollectionTraversalCallback methods.
   NS_IMETHOD_(void) DescribeRefCountedNode(nsrefcnt aRefCount,
@@ -2290,26 +2291,28 @@ CCGraphBuilder::BuildGraph(SliceBudget& 
   }
 
   mCurrNode = nullptr;
 
   return true;
 }
 
 NS_IMETHODIMP_(void)
-CCGraphBuilder::NoteXPCOMRoot(nsISupports* aRoot)
+CCGraphBuilder::NoteXPCOMRoot(nsISupports* aRoot,
+                              nsCycleCollectionParticipant* aParticipant)
 {
-  aRoot = CanonicalizeXPCOMParticipant(aRoot);
-  NS_ASSERTION(aRoot,
-               "Don't add objects that don't participate in collection!");
-
+  MOZ_ASSERT(aRoot == CanonicalizeXPCOMParticipant(aRoot));
+
+#ifdef DEBUG
   nsXPCOMCycleCollectionParticipant* cp;
   ToParticipant(aRoot, &cp);
-
-  NoteRoot(aRoot, cp);
+  MOZ_ASSERT(aParticipant == cp);
+#endif
+
+  NoteRoot(aRoot, aParticipant);
 }
 
 NS_IMETHODIMP_(void)
 CCGraphBuilder::NoteJSRoot(JSObject* aRoot)
 {
   if (JS::Zone* zone = MergeZone(JS::GCCellPtr(aRoot))) {
     NoteRoot(zone, mJSZoneParticipant);
   } else {