Merge mozilla-inbound to mozilla-central a=merge
authorRazvan Maries <rmaries@mozilla.com>
Fri, 26 Apr 2019 12:46:15 +0300
changeset 471446 7d47e7fa2489550ffa83aae67715c5497048923f
parent 471423 094b212a3cbf55d92b85db2b5e1d04f8d46a5dfb (current diff)
parent 471445 1ac79bd52a1106f850bc78af12c964cba0d3af5c (diff)
child 471467 adc67b6b7dd512953e42d3a128cfa97cc0b804a7
child 471470 bb90214b7f33f68a0a716edcb80142d9e983b635
push id35917
push userrmaries@mozilla.com
push dateFri, 26 Apr 2019 09:49:13 +0000
treeherdermozilla-central@7d47e7fa2489 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone68.0a1
first release with
nightly linux32
7d47e7fa2489 / 68.0a1 / 20190426094913 / files
nightly linux64
7d47e7fa2489 / 68.0a1 / 20190426094913 / files
nightly mac
7d47e7fa2489 / 68.0a1 / 20190426094913 / files
nightly win32
7d47e7fa2489 / 68.0a1 / 20190426094913 / files
nightly win64
7d47e7fa2489 / 68.0a1 / 20190426094913 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-inbound to mozilla-central a=merge
js/src/jsapi.cpp
toolkit/content/customElements.js
toolkit/content/jar.mn
toolkit/content/xul.css
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -170,18 +170,17 @@ CustomizeMode.prototype = {
       this.exit();
     } else {
       this.enter();
     }
   },
 
   async _updateThemeButtonIcon() {
     let lwthemeButton = this.$("customization-lwtheme-button");
-    let lwthemeIcon = this.document.getAnonymousElementByAttribute(lwthemeButton,
-                        "class", "button-icon");
+    let lwthemeIcon = lwthemeButton.icon;
     let theme = (await AddonManager.getAddonsByTypes(["theme"])).find(addon => addon.isActive);
     lwthemeIcon.style.backgroundImage = theme ? "url(" + theme.iconURL + ")" : "";
   },
 
   setTab(aTab) {
     if (gTab == aTab) {
       return;
     }
--- a/browser/components/customizableui/test/browser_1007336_lwthemes_in_customize_mode.js
+++ b/browser/components/customizableui/test/browser_1007336_lwthemes_in_customize_mode.js
@@ -10,18 +10,17 @@ const DARK_THEME_ID = "firefox-compact-d
 
 add_task(async function() {
   await startCustomizing();
   // Check restore defaults button is disabled.
   ok(document.getElementById("customization-reset-button").disabled,
      "Reset button should start out disabled");
 
   let themesButton = document.getElementById("customization-lwtheme-button");
-  let themesButtonIcon = document.getAnonymousElementByAttribute(themesButton,
-      "class", "button-icon");
+  let themesButtonIcon = themesButton.icon;
   let iconURL = themesButtonIcon.style.backgroundImage;
   // If we've run other tests before, we might have set the image to the
   // default theme's icon explicitly, otherwise it might be empty, in which
   // case the icon is determined by CSS (which will be the default
   // theme's icon).
   if (iconURL) {
     ok((/default/i).test(themesButtonIcon.style.backgroundImage),
        `Button should show default theme thumbnail - was: "${iconURL}"`);
--- a/browser/components/preferences/in-content/containers.xul
+++ b/browser/components/preferences/in-content/containers.xul
@@ -22,12 +22,15 @@
 <!-- Containers -->
 <groupbox id="browserContainersGroupPane" data-category="paneContainers" hidden="true"
           data-hidden-from-search="true" data-subpanel="true">
   <vbox id="browserContainersbox">
     <richlistbox id="containersView"/>
   </vbox>
   <vbox>
     <hbox flex="1">
-      <button id="containersAdd" oncommand="gContainersPane.onAddButtonCommand();" data-l10n-id="containers-add-button"/>
+      <button id="containersAdd"
+              is="highlightable-button"
+              oncommand="gContainersPane.onAddButtonCommand();"
+              data-l10n-id="containers-add-button"/>
     </hbox>
   </vbox>
 </groupbox>
--- a/browser/components/preferences/in-content/findInPage.js
+++ b/browser/components/preferences/in-content/findInPage.js
@@ -1,15 +1,29 @@
 /* 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/. */
 
 /* import-globals-from extensionControlled.js */
 /* import-globals-from preferences.js */
 
+// A tweak to the standard <button> CE to use textContent on the <label>
+// inside the button, which allows the text to be highlighted when the user
+// is searching.
+
+const MozButton = customElements.get("button");
+class HighlightableButton extends MozButton {
+  static get inheritedAttributes() {
+    return Object.assign({}, super.inheritedAttributes, {
+      ".button-text": "text=label,accesskey,crop",
+    });
+  }
+}
+customElements.define("highlightable-button", HighlightableButton, {extends: "button"});
+
 var gSearchResultsPane = {
   listSearchTooltips: new Set(),
   listSearchMenuitemIndicators: new Set(),
   searchInput: null,
   // A map of DOM Elements to a string of keywords used in search
   // XXX: We should invalidate this cache on `intl:app-locales-changed`
   searchKeywords: new WeakMap(),
   inited: false,
@@ -327,16 +341,17 @@ var gSearchResultsPane = {
    *    DOM Element
    * @param String searchPhrase
    * @returns boolean
    *    Returns true when found in at least one childNode, false otherwise
    */
   async searchWithinNode(nodeObject, searchPhrase) {
     let matchesFound = false;
     if (nodeObject.childElementCount == 0 ||
+        nodeObject.tagName == "button" ||
         nodeObject.tagName == "label" ||
         nodeObject.tagName == "description" ||
         nodeObject.tagName == "menulist") {
       let simpleTextNodes = this.textNodeDescendants(nodeObject);
       for (let node of simpleTextNodes) {
         let result = this.highlightMatches([node], [node.length], node.textContent.toLowerCase(), searchPhrase);
         matchesFound = matchesFound || result;
       }
@@ -399,18 +414,17 @@ var gSearchResultsPane = {
         nodeObject.setAttribute("indicator", "true");
         this.listSearchMenuitemIndicators.add(nodeObject);
         let menulist = nodeObject.closest("menulist");
 
         menulist.setAttribute("indicator", "true");
         this.listSearchMenuitemIndicators.add(menulist);
       }
 
-      if ((nodeObject.tagName == "button" ||
-           nodeObject.tagName == "menulist" ||
+      if ((nodeObject.tagName == "menulist" ||
            nodeObject.tagName == "menuitem") &&
            (labelResult || valueResult || keywordsResult)) {
         nodeObject.setAttribute("highlightable", "true");
       }
 
       matchesFound = matchesFound || complexTextNodesResult || labelResult || valueResult || keywordsResult;
     }
 
--- a/browser/components/preferences/in-content/home.xul
+++ b/browser/components/preferences/in-content/home.xul
@@ -7,16 +7,17 @@
 <script src="chrome://browser/content/preferences/in-content/home.js"/>
 <html:template id="template-paneHome">
 <hbox id="firefoxHomeCategory"
       class="subcategory"
       hidden="true"
       data-category="paneHome">
   <html:h1 style="-moz-box-flex: 1;" data-l10n-id="pane-home-title"/>
   <button id="restoreDefaultHomePageBtn"
+          is="highlightable-button"
           class="homepage-button check-home-page-controlled"
           data-preference-related="browser.startup.homepage"
           data-l10n-id="home-restore-defaults"
           preference="pref.browser.homepage.disable_button.restore_default"/>
 </hbox>
 
 <groupbox id="homepageGroup"
           data-category="paneHome"
@@ -46,23 +47,25 @@
         <textbox id="homePageUrl"
                 class="uri-element check-home-page-controlled"
                 data-preference-related="browser.startup.homepage"
                 type="autocomplete"
                 data-l10n-id="home-homepage-custom-url"
                 autocompletesearch="unifiedcomplete" />
         <hbox class="homepage-buttons">
           <button id="useCurrentBtn"
+                  is="highlightable-button"
                   flex="1"
                   class="homepage-button check-home-page-controlled"
                   data-l10n-id="use-current-pages"
                   data-l10n-args='{"tabCount": 0}'
                   disabled="true"
                   preference="pref.browser.homepage.disable_button.current_page"/>
           <button id="useBookmarkBtn"
+                  is="highlightable-button"
                   flex="1"
                   class="homepage-button check-home-page-controlled"
                   data-l10n-id="choose-bookmark"
                   preference="pref.browser.homepage.disable_button.bookmark_page"
                   search-l10n-ids="select-bookmark-window.title, select-bookmark-desc"/>
         </hbox>
       </vbox>
     </vbox>
@@ -81,20 +84,22 @@
       </menupopup>
     </menulist>
   </hbox>
 
   <hbox id="browserHomePageExtensionContent"
         align="center" hidden="true" class="extension-controlled">
     <description control="disableHomePageExtension" flex="1" />
     <button id="disableHomePageExtension"
+            is="highlightable-button"
             class="extension-controlled-button accessory-button"
             data-l10n-id="disable-extension" />
   </hbox>
   <hbox id="browserNewTabExtensionContent"
         align="center" hidden="true" class="extension-controlled">
     <description control="disableNewTabExtension" flex="1" />
     <button id="disableNewTabExtension"
+            is="highlightable-button"
             class="extension-controlled-button accessory-button"
             data-l10n-id="disable-extension" />
   </hbox>
 </groupbox>
 </html:template>
--- a/browser/components/preferences/in-content/main.xul
+++ b/browser/components/preferences/in-content/main.xul
@@ -43,16 +43,17 @@
   <vbox id="defaultBrowserBox">
     <checkbox id="alwaysCheckDefault" preference="browser.shell.checkDefaultBrowser"
               data-l10n-id="always-check-default"/>
     <deck id="setDefaultPane">
       <hbox align="center" class="indent">
         <image class="face-sad"/>
         <label id="isNotDefaultLabel" flex="1" data-l10n-id="is-not-default"/>
         <button id="setDefaultButton"
+                is="highlightable-button"
                 class="accessory-button"
                 data-l10n-id="set-as-my-default-browser"
                 preference="pref.general.disable_button.default_browser"/>
       </hbox>
       <hbox align="center" class="indent">
         <image class="face-smile"/>
         <label id="isDefaultLabel" flex="1" data-l10n-id="is-default"/>
       </hbox>
@@ -90,30 +91,32 @@
               preference="browser.taskbar.previews.enable"/>
 #endif
 
     <vbox id="browserContainersbox" hidden="true">
       <hbox id="browserContainersExtensionContent"
             align="center" class="extension-controlled">
         <description control="disableContainersExtension" flex="1" />
         <button id="disableContainersExtension"
+                is="highlightable-button"
                 class="extension-controlled-button accessory-button"
                 data-l10n-id="disable-extension" />
       </hbox>
       <hbox align="center">
         <checkbox id="browserContainersCheckbox"
                   class="tail-with-learn-more"
                   data-l10n-id="browser-containers-enabled"
                   preference="privacy.userContext.enabled"
                   onsyncfrompreference="return gMainPane.readBrowserContainersCheckbox();"/>
         <label id="browserContainersLearnMore" is="text-link" class="learnMore" data-l10n-id="browser-containers-learn-more"/>
         <spacer 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="browserContainersSettings"
+                  is="highlightable-button"
                   class="accessory-button"
                   data-l10n-id="browser-containers-settings"
                   search-l10n-ids="containers-add-button.label,
                     containers-preferences-button.label,
                     containers-remove-button.label"
                   />
         </hbox>
       </hbox>
@@ -171,16 +174,17 @@
           </menupopup>
         </menulist>
       </hbox>
     </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="advancedFonts"
+              is="highlightable-button"
               class="accessory-button"
               icon="select-font"
               data-l10n-id="advanced-fonts"
               search-l10n-ids="
                 fonts-window.title,
                 fonts-langgroup-header,
                 fonts-proportional-size,
                 fonts-proportional-header,
@@ -243,16 +247,17 @@
               " />
     </hbox>
   </hbox>
   <hbox id="colorsSettings">
     <spacer 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="colors"
+              is="highlightable-button"
               class="accessory-button"
               icon="select-color"
               data-l10n-id="colors-settings"
               search-l10n-ids="
                 colors-page-override,
                 colors-page-override-option-always.label,
                 colors-page-override-option-auto.label,
                 colors-page-override-option-never.label,
@@ -275,31 +280,33 @@
 
   <vbox id="browserLanguagesBox" align="start" hidden="true">
     <description flex="1" controls="chooseBrowserLanguage" data-l10n-id="choose-browser-language-description"/>
     <hbox>
       <menulist id="defaultBrowserLanguage">
         <menupopup/>
       </menulist>
       <button id="manageBrowserLanguagesButton"
+              is="highlightable-button"
               class="accessory-button"
               data-l10n-id="manage-browser-languages-button"
               oncommand="gMainPane.showBrowserLanguages({search: false})"/>
     </hbox>
   </vbox>
   <hbox id="confirmBrowserLanguage" class="message-bar" align="center" hidden="true">
     <image class="message-bar-icon"/>
     <vbox class="message-bar-content-container" align="stretch" flex="1"/>
   </hbox>
 
   <hbox id="languagesBox" align="center">
     <description flex="1" control="chooseLanguage" data-l10n-id="choose-language-description"/>
     <!-- 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="chooseLanguage"
+              is="highlightable-button"
               class="accessory-button"
               data-l10n-id="choose-button"
               search-l10n-ids="
                 webpage-languages-window.title,
                 languages-description,
                 languages-customize-moveup.label,
                 languages-customize-movedown.label,
                 languages-customize-remove.label,
@@ -319,16 +326,17 @@
         <label data-l10n-id="translate-attribution">
           <html:img id="translationAttributionImage" aria-label="Microsoft Translator"
             src="chrome://browser/content/microsoft-translator-attribution.png"
             data-l10n-name="logo"/>
         </label>
       </hbox>
     </hbox>
     <button id="translateButton"
+            is="highlightable-button"
             class="accessory-button"
             data-l10n-id="translate-exceptions"/>
   </hbox>
   <checkbox id="checkSpelling"
           data-l10n-id="check-user-spelling"
           onsyncfrompreference="return gMainPane.readCheckSpelling();"
           onsynctopreference="return gMainPane.writeCheckSpelling();"
           preference="layout.spellcheckDefault"/>
@@ -352,16 +360,17 @@
     <hbox>
       <radio id="saveTo"
              value="true"
              data-l10n-id="download-save-to"/>
       <textbox id="downloadFolder" flex="1"
                readonly="true"
                aria-labelledby="saveTo"/>
       <button id="chooseFolder"
+              is="highlightable-button"
               class="accessory-button"
               data-l10n-id="download-choose-folder"/>
     </hbox>
     <!-- Additional radio button added to support CloudStorage - Bug 1357171 -->
     <radio id="saveToCloud"
            value="true"
            hidden="true"/>
     <radio id="alwaysAsk"
@@ -426,110 +435,122 @@
       <description id="distribution" class="text-blurb" hidden="true"/>
       <description id="distributionId" class="text-blurb" hidden="true"/>
     </vbox>
 #ifdef MOZ_UPDATER
     <spacer flex="1"/>
     <!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
     <vbox>
       <button id="showUpdateHistory"
+              is="highlightable-button"
               class="accessory-button"
               data-l10n-id="update-history"
               preference="app.update.disable_button.showUpdateHistory"
               search-l10n-ids="
                 history-title,
                 history-intro
               "/>
     </vbox>
 #endif
   </hbox>
 #ifdef MOZ_UPDATER
   <vbox id="updateBox">
     <deck id="updateDeck" orient="vertical">
       <hbox id="checkForUpdates" align="start">
         <spacer flex="1"/>
         <button id="checkForUpdatesButton"
+                is="highlightable-button"
                 data-l10n-id="update-checkForUpdatesButton"
                 oncommand="gAppUpdater.checkForUpdates();"/>
       </hbox>
       <hbox id="downloadAndInstall" align="start">
         <spacer flex="1"/>
         <button id="downloadAndInstallButton"
+                is="highlightable-button"
                 oncommand="gAppUpdater.startDownload();"/>
                 <!-- label and accesskey will be filled by JS -->
       </hbox>
       <hbox id="apply" align="start">
         <spacer flex="1"/>
         <button id="updateButton"
+                is="highlightable-button"
                 data-l10n-id="update-updateButton"
                 oncommand="gAppUpdater.buttonRestartAfterDownload();"/>
       </hbox>
       <hbox id="checkingForUpdates" align="start">
         <image class="update-throbber"/>
         <label data-l10n-id="update-checkingForUpdates"></label>
         <spacer flex="1"/>
         <button data-l10n-id="update-checkForUpdatesButton"
+                is="highlightable-button"
                 disabled="true"/>
       </hbox>
       <hbox id="downloading" align="start" data-l10n-id="update-downloading">
         <html:img class="update-throbber" src="chrome://global/skin/icons/loading.png" data-l10n-name="icon"/>
         <label id="downloadStatus" data-l10n-name="download-status"/>
       </hbox>
       <hbox id="applying" align="start">
         <image class="update-throbber"/>
         <label data-l10n-id="update-applying"></label>
       </hbox>
       <hbox id="downloadFailed" align="start">
         <label data-l10n-id="update-failed-main">
           <html:a id="failedLink" target="_blank" class="learnMore text-link" data-l10n-name="failed-link-main"></html:a>
         </label>
         <spacer flex="1"/>
         <button data-l10n-id="update-checkForUpdatesButton"
+                is="highlightable-button"
                 oncommand="gAppUpdater.checkForUpdates();"/>
       </hbox>
       <hbox id="policyDisabled" align="start">
         <label data-l10n-id="update-adminDisabled"></label>
         <spacer flex="1"/>
         <button data-l10n-id="update-checkForUpdatesButton"
+                is="highlightable-button"
                 disabled="true"/>
       </hbox>
       <hbox id="noUpdatesFound" align="start">
         <image class="face-smile"/>
         <label data-l10n-id="update-noUpdatesFound"></label>
         <spacer flex="1"/>
         <button data-l10n-id="update-checkForUpdatesButton"
+                is="highlightable-button"
                 oncommand="gAppUpdater.checkForUpdates();"/>
       </hbox>
       <hbox id="otherInstanceHandlingUpdates" align="start">
         <label data-l10n-id="update-otherInstanceHandlingUpdates"></label>
         <spacer flex="1"/>
         <button data-l10n-id="update-checkForUpdatesButton"
+                is="highlightable-button"
                 disabled="true"/>
       </hbox>
       <hbox id="manualUpdate" align="start">
         <image class="face-sad"/>
         <description flex="1" data-l10n-id="update-manual">
           <label id="manualLink" data-l10n-name="manual-link" is="text-link"/>
         </description>
         <spacer flex="1"/>
         <button data-l10n-id="update-checkForUpdatesButton"
+                is="highlightable-button"
                 disabled="true"/>
       </hbox>
       <hbox id="unsupportedSystem" align="start">
         <description flex="1" data-l10n-id="update-unsupported">
           <label id="unsupportedLink" class="learnMore" data-l10n-name="unsupported-link" is="text-link"></label>
         </description>
         <spacer flex="1"/>
         <button data-l10n-id="update-checkForUpdatesButton"
+                is="highlightable-button"
                 disabled="true"/>
       </hbox>
       <hbox id="restarting" align="start">
         <image class="update-throbber"/><label data-l10n-id="update-restarting"></label>
         <spacer flex="1"/>
         <button data-l10n-id="update-updateButton"
+                is="highlightable-button"
                 disabled="true"/>
       </hbox>
     </deck>
   </vbox>
 #endif
 
 #ifdef MOZ_UPDATER
   <description id="updateAllowDescription" data-l10n-id="update-application-allow-description"></description>
@@ -668,16 +689,17 @@
         data-l10n-id="network-proxy-connection-learn-more">
       </label>
       <separator orient="vertical"/>
     </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="connectionSettings"
+              is="highlightable-button"
               class="accessory-button"
               icon="network"
               data-l10n-id="network-proxy-connection-settings"
               searchkeywords="doh trr"
               search-l10n-ids="
                 connection-window.title,
                 connection-proxy-option-no.label,
                 connection-proxy-option-auto.label,
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -26,16 +26,17 @@
           <html:span id="contentBlockingDescription" class="tail-with-learn-more" data-l10n-id="content-blocking-section-description"></html:span>
           <label id="contentBlockingLearnMore" class="learnMore" data-l10n-id="content-blocking-learn-more" is="text-link"/>
         </description>
       </vbox>
       <vbox>
         <!-- 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"
+                  is="highlightable-button"
                   class="accessory-button"
                   flex="1"
                   data-l10n-id="tracking-manage-exceptions"
                   preference="pref.privacy.disable_button.tracking_protection_exceptions"
                   search-l10n-ids="
                     permissions-remove.label,
                     permissions-remove-all.label,
                     permissions-button-cancel.label,
@@ -52,16 +53,17 @@
                     aria-labelledby="trackingProtectionMenuDesc">
           <vbox id="contentBlockingOptionStandard" class="content-blocking-category">
             <hbox>
               <radio id="standardRadio"
                      value="standard"
                      data-l10n-id="content-blocking-setting-standard"
                      flex="1"/>
               <button id="standardArrow"
+                      is="highlightable-button"
                       class="arrowhead default-content-blocking-ui"
                       data-l10n-id="content-blocking-expand-section"
                       aria-expanded="false"/>
             </hbox>
             <vbox class="indent default-content-blocking-ui">
               <description data-l10n-id="content-blocking-standard-desc"></description>
               <vbox class="content-blocking-extra-information">
                 <vbox class="indent">
@@ -111,31 +113,33 @@
                           <html:span data-l10n-id="content-blocking-reload-description"></html:span>
                         </description>
                       </hbox>
                       <spacer flex="1"/>
                     </vbox>
                     <vbox>
                       <spacer flex="1"/>
                       <button class="accessory-button reload-tabs-button"
+                              is="highlightable-button"
                               data-l10n-id="content-blocking-reload-tabs-button"/>
                       <spacer flex="1"/>
                     </vbox>
                   </hbox>
                 </vbox>
               </vbox>
             </vbox>
           </vbox>
           <vbox id="contentBlockingOptionStrict" class="content-blocking-category">
             <hbox>
               <radio id="strictRadio"
                      value="strict"
                      data-l10n-id="content-blocking-setting-strict"
                      flex="1"/>
               <button id="strictArrow"
+                      is="highlightable-button"
                       class="arrowhead"
                       data-l10n-id="content-blocking-expand-section"
                       aria-expanded="false"/>
             </hbox>
             <vbox class="indent">
               <label data-l10n-id="content-blocking-strict-description"></label>
               <vbox class="content-blocking-extra-information">
                 <vbox class="indent">
@@ -185,16 +189,17 @@
                           <html:span data-l10n-id="content-blocking-reload-description"></html:span>
                         </description>
                       </hbox>
                       <spacer flex="1"/>
                     </vbox>
                     <vbox>
                       <spacer flex="1"/>
                       <button class="accessory-button reload-tabs-button"
+                              is="highlightable-button"
                               data-l10n-id="content-blocking-reload-tabs-button"/>
                       <spacer flex="1"/>
                     </vbox>
                   </hbox>
                 </vbox>
                 <vbox class="content-blocking-warning">
                   <vbox class="indent">
                     <hbox>
@@ -212,27 +217,29 @@
           </vbox>
         <vbox id="contentBlockingOptionCustom" class="tracking-protection-ui content-blocking-category">
           <hbox>
             <radio id="customRadio"
                    value="custom"
                    data-l10n-id="content-blocking-setting-custom"
                    flex="1"/>
             <button id="customArrow"
+                    is="highlightable-button"
                     class="arrowhead"
                     data-l10n-id="content-blocking-expand-section"
                     aria-expanded="false"/>
           </hbox>
           <vbox class="indent">
             <description id="contentBlockingCustomDesc" data-l10n-id="content-blocking-custom-desc"></description>
             <vbox class="content-blocking-extra-information">
               <hbox id="contentBlockingTrackingProtectionExtensionContentLabel"
                     align="center" hidden="true" class="extension-controlled">
                 <description control="contentBlockingDisableTrackingProtectionExtension" flex="1"/>
                 <button id="contentBlockingDisableTrackingProtectionExtension"
+                        is="highlightable-button"
                         class="extension-controlled-button accessory-button"
                         data-l10n-id="disable-extension" hidden="true"/>
               </hbox>
                 <hbox class="custom-option">
                   <checkbox id="contentBlockingTrackingProtectionCheckbox"
                             class="content-blocking-checkbox" flex="1"
                             data-l10n-id="content-blocking-trackers-label"
                             aria-describedby="contentBlockingCustomDesc"/>
@@ -301,16 +308,17 @@
                         <html:span data-l10n-id="content-blocking-reload-description"></html:span>
                       </description>
                     </hbox>
                     <spacer flex="1"/>
                   </vbox>
                   <vbox>
                     <spacer flex="1"/>
                     <button class="accessory-button reload-tabs-button"
+                            is="highlightable-button"
                             data-l10n-id="content-blocking-reload-tabs-button"/>
                     <spacer flex="1"/>
                   </vbox>
                 </hbox>
               </vbox>
               <vbox class="content-blocking-warning">
                 <vbox class="indent">
                   <hbox>
@@ -363,36 +371,39 @@
                   onsynctopreference="return gPrivacyPane.writeDeleteOnClose();"
                   flex="1" />
       </hbox>
     </vbox>
     <vbox align="end">
       <!-- 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="clearSiteDataButton"
+            is="highlightable-button"
             class="accessory-button"
             icon="clear"
             search-l10n-ids="clear-site-data-cookies-empty.label, clear-site-data-cache-empty.label"
             data-l10n-id="sitedata-clear"/>
       </hbox>
       <hbox>
         <button id="siteDataSettings"
+                is="highlightable-button"
                 class="accessory-button"
                 data-l10n-id="sitedata-settings"
                 search-l10n-ids="
                   site-data-settings-window.title,
                   site-data-column-host.label,
                   site-data-column-cookies.label,
                   site-data-column-storage.label,
                   site-data-settings-description,
                   site-data-remove-all.label,
                 "/>
       </hbox>
       <hbox>
         <button id="cookieExceptions"
+                is="highlightable-button"
                 class="accessory-button"
                 data-l10n-id="sitedata-cookies-permissions"
                 preference="pref.privacy.disable_button.cookie_exceptions"
                 search-l10n-ids="
                   permissions-address,
                   permissions-block.label,
                   permissions-allow.label,
                   permissions-remove.label,
@@ -415,44 +426,47 @@
       <checkbox id="savePasswords"
                 data-l10n-id="forms-ask-to-save-logins"
                 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"
+                is="highlightable-button"
                 class="accessory-button"
                 data-l10n-id="forms-exceptions"
                 preference="pref.privacy.disable_button.view_passwords_exceptions"
                 search-l10n-ids="
                   permissions-address,
                   permissions-exceptions-saved-logins-window.title,
                   permissions-exceptions-saved-logins-desc,
                 "/>
       </hbox>
     </hbox>
     <hbox id="showPasswordBox" pack="end">
       <!-- 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="showPasswords"
+                is="highlightable-button"
                 class="accessory-button"
                 data-l10n-id="forms-saved-logins"
                 search-l10n-ids="forms-saved-logins.label"
                 preference="pref.privacy.disable_button.view_passwords"/>
       </hbox>
     </hbox>
   </vbox>
   <hbox id="masterPasswordRow">
     <checkbox id="useMasterPassword"
               data-l10n-id="forms-master-pw-use"
               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="changeMasterPassword"
+              is="highlightable-button"
               class="accessory-button"
               data-l10n-id="forms-master-pw-change"/>
     </hbox>
   </hbox>
 </groupbox>
 
 <!-- The form autofill section is inserted in to this box
      after the form autofill extension has initialized. -->
@@ -532,22 +546,24 @@
                         flex="1" />
             </hbox>
           </vbox>
         </vbox>
       </vbox>
     </deck>
     <vbox id="historyButtons" align="end">
       <button id="clearHistoryButton"
+              is="highlightable-button"
               class="accessory-button"
               icon="clear"
               data-l10n-id="history-clear-button"/>
       <!-- 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="clearDataSettings"
+                is="highlightable-button"
                 class="accessory-button"
                 data-l10n-id="history-clear-on-close-settings"
                 search-l10n-ids="
                   clear-data-settings-label,
                   history-section-label,
                   item-history-and-downloads.label,
                   item-cookies.label,
                   item-active-logins.label,
@@ -594,16 +610,17 @@
     <hbox id="locationSettingsRow" align="center" role="group" aria-labelledby="locationPermissionsLabel">
       <description flex="1">
         <image class="geo-icon permission-icon" />
         <separator orient="vertical" class="thin"/>
         <label id="locationPermissionsLabel" data-l10n-id="permissions-location"/>
       </description>
       <hbox pack="end">
         <button id="locationSettingsButton"
+                is="highlightable-button"
                 class="accessory-button"
                 data-l10n-id="permissions-location-settings"
                 search-l10n-ids="
                   permissions-remove.label,
                   permissions-remove-all.label,
                   permissions-button-cancel.label,
                   permissions-button-ok.label,
                   permissions-site-location-window.title,
@@ -617,16 +634,17 @@
     <hbox id="cameraSettingsRow" align="center" role="group" aria-labelledby="cameraPermissionsLabel">
       <description flex="1">
         <image class="camera-icon permission-icon" />
         <separator orient="vertical" class="thin"/>
         <label id="cameraPermissionsLabel" data-l10n-id="permissions-camera"/>
       </description>
       <hbox pack="end">
         <button id="cameraSettingsButton"
+                is="highlightable-button"
                 class="accessory-button"
                 data-l10n-id="permissions-camera-settings"
                 search-l10n-ids="
                   permissions-remove.label,
                   permissions-remove-all.label,
                   permissions-button-cancel.label,
                   permissions-button-ok.label,
                   permissions-site-camera-window.title,
@@ -640,16 +658,17 @@
     <hbox id="microphoneSettingsRow" align="center" role="group" aria-labelledby="microphonePermissionsLabel">
       <description flex="1">
         <image class="microphone-icon permission-icon" />
         <separator orient="vertical" class="thin"/>
         <label id="microphonePermissionsLabel" data-l10n-id="permissions-microphone"/>
       </description>
       <hbox pack="end">
         <button id="microphoneSettingsButton"
+                is="highlightable-button"
                 class="accessory-button"
                 data-l10n-id="permissions-microphone-settings"
                 search-l10n-ids="
                   permissions-remove.label,
                   permissions-remove-all.label,
                   permissions-button-cancel.label,
                   permissions-button-ok.label,
                   permissions-site-microphone-window.title,
@@ -669,16 +688,17 @@
                data-l10n-id="permissions-notification"/>
         <label id="notificationPermissionsLearnMore"
                class="learnMore"
                is="text-link"
                data-l10n-id="permissions-notification-link"/>
       </description>
       <hbox pack="end">
         <button id="notificationSettingsButton"
+                is="highlightable-button"
                 class="accessory-button"
                 data-l10n-id="permissions-notification-settings"
                 search-l10n-ids="
                   permissions-remove.label,
                   permissions-remove-all.label,
                   permissions-button-cancel.label,
                   permissions-button-ok.label,
                   permissions-site-notification-window.title,
@@ -698,16 +718,17 @@
 
   <hbox align="start" id="autoplayMediaCheckboxWrapper">
     <checkbox id="autoplayMediaCheckbox"
               data-l10n-id="permissions-block-autoplay-media2"
               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="autoplayMediaPolicyButton"
+              is="highlightable-button"
               class="accessory-button"
               data-l10n-id="permissions-block-autoplay-media-exceptions"
               search-l10n-ids="permissions-address,
                                permissions-button-cancel.label,
                                permissions-button-ok.label,
                                permissions-exceptions-autoplay-media-window2.title,
                                permissions-exceptions-autoplay-media-desc2
                                " />
@@ -718,16 +739,17 @@
     <checkbox id="popupPolicy" preference="dom.disable_open_during_load"
               data-l10n-id="permissions-block-popups"
               onsyncfrompreference="return gPrivacyPane.updateButtons('popupPolicyButton',
                                          'dom.disable_open_during_load');"
               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="popupPolicyButton"
+              is="highlightable-button"
               class="accessory-button"
               data-l10n-id="permissions-block-popups-exceptions"
               search-l10n-ids="
                 permissions-address,
                 permissions-button-cancel.label,
                 permissions-button-ok.label,
                 permissions-exceptions-popup-window.title,
                 permissions-exceptions-popup-desc,
@@ -739,16 +761,17 @@
     <checkbox id="warnAddonInstall"
               data-l10n-id="permissions-addon-install-warning"
               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"
+              is="highlightable-button"
               class="accessory-button"
               data-l10n-id="permissions-addon-exceptions"
               search-l10n-ids="
                 permissions-address,
                 permissions-allow.label,
                 permissions-remove.label,
                 permissions-remove-all.label,
                 permissions-button-cancel.label,
@@ -890,16 +913,17 @@
               onsyncfrompreference="return gPrivacyPane.readEnableOCSP();"
               onsynctopreference="return gPrivacyPane.writeEnableOCSP();"
               preference="security.OCSP.enabled"
               flex="1" />
     <vbox>
       <!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
       <hbox pack="end">
         <button id="viewCertificatesButton"
+                is="highlightable-button"
                 class="accessory-button"
                 data-l10n-id="certs-view"
                 preference="security.disable_button.openCertManager"
                 search-l10n-ids="
                   certmgr-tab-mine.label,
                   certmgr-tab-people.label,
                   certmgr-tab-servers.label,
                   certmgr-tab-ca.label,
@@ -912,16 +936,17 @@
                   certmgr-view.label,
                   certmgr-export.label,
                   certmgr-delete.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 pack="end">
         <button id="viewSecurityDevicesButton"
+                is="highlightable-button"
                 class="accessory-button"
                 data-l10n-id="certs-devices"
                 preference="security.disable_button.openDeviceManager"
                 search-l10n-ids="
                   devmgr.title,
                   devmgr-devlist.label,
                   devmgr-header-details.label,
                   devmgr-header-value.label,
--- a/browser/components/preferences/in-content/search.xul
+++ b/browser/components/preferences/in-content/search.xul
@@ -62,20 +62,22 @@
           <treecol id="engineName" flex="4" data-l10n-id="search-choose-engine-column" sortable="false"/>
           <treecol id="engineKeyword" flex="1" data-l10n-id="search-choose-keyword-column" editable="true"
                    sortable="false"/>
         </treecols>
       </tree>
 
       <hbox>
         <button id="restoreDefaultSearchEngines"
+                is="highlightable-button"
                 data-l10n-id="search-restore-default"
                 />
         <spacer flex="1"/>
         <button id="removeEngineButton"
+                is="highlightable-button"
                 class="searchEngineAction"
                 data-l10n-id="search-remove-engine"
                 disabled="true"
                 />
       </hbox>
       <hbox id="addEnginesBox" pack="start">
         <label id="addEngines" data-l10n-id="search-find-more-link" is="text-link"></label>
       </hbox>
--- a/browser/components/preferences/in-content/sync.xul
+++ b/browser/components/preferences/in-content/sync.xul
@@ -30,16 +30,17 @@
         <image class="fxaProfileImage"/>
       </vbox>
       <vbox flex="1">
         <hbox align="center" flex="1">
           <hbox align="center" flex="1">
             <label id="signedOutAccountBoxTitle"><html:h2 data-l10n-id="sync-signedout-account-title"/></label>
           </hbox>
           <button id="noFxaSignIn"
+                  is="highlightable-button"
                   class="accessory-button"
                   data-l10n-id="sync-signedout-account-signin"/>
         </hbox>
         <hbox align="center" flex="1">
           <html:a id="noFxaSignUp"
                   class="openLink"
                   data-l10n-id="sync-signedout-account-create" />
         </hbox>
@@ -82,16 +83,17 @@
                      data-l10n-id="sync-profile-picture"/>
               <vbox flex="1" pack="center">
                 <hbox flex="1" align="baseline">
                   <label id="fxaDisplayName" hidden="true">
                     <html:h2 id="fxaDisplayNameHeading"/>
                   </label>
                   <label id="fxaEmailAddress" flex="1" crop="end"/>
                   <button id="fxaUnlinkButton"
+                          is="highlightable-button"
                           class="accessory-button"
                           data-l10n-id="sync-disconnect"/>
                 </hbox>
                 <hbox>
                   <html:a id="verifiedManage" class="openLink"
                           data-l10n-id="sync-manage-account"
                           onkeypress="gSyncPane.openManageFirefoxAccount(event);"/>
                 </hbox>
@@ -107,18 +109,22 @@
                 <hbox align="center">
                   <image class="fxaLoginRejectedWarning"/>
                   <description flex="1"
                     class="l10nArgsEmailAddress"
                     data-l10n-id="sync-signedin-unverified"
                     data-l10n-args='{"email": ""}'/>
                 </hbox>
                 <hbox class="fxaAccountBoxButtons">
-                  <button id="verifyFxaAccount" data-l10n-id="sync-resend-verification"/>
-                  <button id="unverifiedUnlinkFxaAccount" data-l10n-id="sync-remove-account"/>
+                  <button id="verifyFxaAccount"
+                          is="highlightable-button"
+                          data-l10n-id="sync-resend-verification"/>
+                  <button id="unverifiedUnlinkFxaAccount"
+                          is="highlightable-button"
+                          data-l10n-id="sync-remove-account"/>
                 </hbox>
               </vbox>
             </hbox>
 
             <!-- logged in locally but server rejected credentials -->
             <hbox id="fxaLoginRejected">
               <vbox>
                 <image class="fxaProfileImage"/>
@@ -127,18 +133,22 @@
                 <hbox align="center">
                   <image class="fxaLoginRejectedWarning"/>
                   <description flex="1"
                     class="l10nArgsEmailAddress"
                     data-l10n-id="sync-signedin-login-failure"
                     data-l10n-args='{"email": ""}'/>
                 </hbox>
                 <hbox class="fxaAccountBoxButtons">
-                  <button id="rejectReSignIn" data-l10n-id="sync-sign-in"/>
-                  <button id="rejectUnlinkFxaAccount" data-l10n-id="sync-remove-account"/>
+                  <button id="rejectReSignIn"
+                          is="highlightable-button"
+                          data-l10n-id="sync-sign-in"/>
+                  <button id="rejectUnlinkFxaAccount"
+                          is="highlightable-button"
+                          data-l10n-id="sync-remove-account"/>
                 </hbox>
               </vbox>
             </hbox>
           </deck>
         </groupbox>
         <groupbox id="syncOptions">
           <label><html:h2 data-l10n-id="sync-signedin-settings-header"/></label>
           <description data-l10n-id="sync-signedin-settings-desc"/>
@@ -169,21 +179,24 @@
         </groupbox>
       </vbox>
     </hbox>
     <groupbox>
       <label control="fxaSyncComputerName"><html:h2 data-l10n-id="sync-device-name-header"/></label>
       <hbox id="fxaDeviceName">
         <textbox id="fxaSyncComputerName" flex="1" disabled="true"/>
         <button id="fxaChangeDeviceName"
+                is="highlightable-button"
                 data-l10n-id="sync-device-name-change"/>
         <button id="fxaCancelChangeDeviceName"
+                is="highlightable-button"
                 data-l10n-id="sync-device-name-cancel"
                 hidden="true"/>
         <button id="fxaSaveChangeDeviceName"
+                is="highlightable-button"
                 data-l10n-id="sync-device-name-save"
                 hidden="true"/>
       </hbox>
     </groupbox>
     <vbox align="start">
       <label id="connect-another-device" is="text-link"
              class="fxaMobilePromo" data-l10n-id="sync-connect-another-device"/>
       <label id="manage-devices" is="text-link"
--- a/browser/components/preferences/in-content/syncDisconnect.xul
+++ b/browser/components/preferences/in-content/syncDisconnect.xul
@@ -44,20 +44,22 @@
         </vbox>
       </hbox>
     </vbox>
 
     <vbox>
       <spacer flex="1"/>
       <hbox class="actionButtons" align="right" flex="1">
         <button id="butCancel"
+                is="highlightable-button"
                 oncommand="close(event);"
                 class="syncDisconnectButton"
                 data-l10n-id="sync-disconnect-cancel"/>
         <button id="butDisconnect"
+                is="highlightable-button"
                 oncommand="gSyncDisconnectDialog.accept(event);"
                 class="syncDisconnectButton"
                 data-l10n-id="sync-disconnect-confirm-disconnect"/>
       </hbox>
     </vbox>
   </vbox>
 
   <vbox id="deletingContent" align="center" pack="center" flex="1" hidden="true">
--- a/browser/extensions/formautofill/FormAutofillPreferences.jsm
+++ b/browser/extensions/formautofill/FormAutofillPreferences.jsm
@@ -74,17 +74,17 @@ FormAutofillPreferences.prototype = {
     let formAutofillFragment = document.createDocumentFragment();
     let formAutofillGroupBoxLabel = document.createXULElement("label");
     let formAutofillGroupBoxLabelHeading = document.createElementNS(HTML_NS, "h2");
     let formAutofillGroup = document.createXULElement("vbox");
     let addressAutofill = document.createXULElement("hbox");
     let addressAutofillCheckboxGroup = document.createXULElement("hbox");
     let addressAutofillCheckbox = document.createXULElement("checkbox");
     let addressAutofillLearnMore = document.createXULElement("label", {is: "text-link"});
-    let savedAddressesBtn = document.createXULElement("button");
+    let savedAddressesBtn = document.createXULElement("button", {is: "highlightable-button"});
     // Wrappers are used to properly compute the search tooltip positions
     let savedAddressesBtnWrapper = document.createXULElement("hbox");
     let savedCreditCardsBtnWrapper = document.createXULElement("hbox");
 
     savedAddressesBtn.className = "accessory-button";
     addressAutofillCheckbox.className = "tail-with-learn-more";
     addressAutofillLearnMore.className = "learnMore";
 
@@ -133,17 +133,17 @@ FormAutofillPreferences.prototype = {
       savedAddressesBtn,
     };
 
     if (FormAutofill.isAutofillCreditCardsAvailable) {
       let creditCardAutofill = document.createXULElement("hbox");
       let creditCardAutofillCheckboxGroup = document.createXULElement("hbox");
       let creditCardAutofillCheckbox = document.createXULElement("checkbox");
       let creditCardAutofillLearnMore = document.createXULElement("label", {is: "text-link"});
-      let savedCreditCardsBtn = document.createXULElement("button");
+      let savedCreditCardsBtn = document.createXULElement("button", {is: "highlightable-button"});
       savedCreditCardsBtn.className = "accessory-button";
       creditCardAutofillCheckbox.className = "tail-with-learn-more";
       creditCardAutofillLearnMore.className = "learnMore";
 
       creditCardAutofill.id = "creditCardAutofill";
       creditCardAutofillLearnMore.id = "creditCardAutofillLearnMore";
 
       creditCardAutofill.setAttribute("data-subcategory", "credit-card-autofill");
--- a/dom/base/test/chrome/test_bug683852.xul
+++ b/dom/base/test/chrome/test_bug683852.xul
@@ -2,17 +2,24 @@
 <?xml-stylesheet type="text/css" href="chrome://global/skin"?>
 <?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=683852
 -->
 <window title="Mozilla Bug 683852"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
   <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
-  <button value="testbutton" id="testbutton"/>
+  <xbl:bindings xmlns:xbl="http://www.mozilla.org/xbl"
+                xmlns:xul="http://www.mozilla.org/keymaster/gaktekeeper/there.is.only.xul">
+    <xbl:binding id="test">
+      <xbl:content><xul:box anonid="xbl-anon">Anonymous</xul:box></xbl:content>
+    </xbl:binding>
+  </xbl:bindings>
+  <box id="xbl-host" style="-moz-binding: url(#test)"/>
+
   <!-- test results are displayed in the html:body -->
   <body xmlns="http://www.w3.org/1999/xhtml">
   <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=683852"
      target="_blank" id="link">Mozilla Bug 683852</a>
   </body>
 
   <!-- test code goes here -->
   <script type="application/javascript">
@@ -20,24 +27,24 @@ https://bugzilla.mozilla.org/show_bug.cg
   /** Test for Bug 683852 **/
   SimpleTest.waitForExplicitFinish();
 
   const NS_HTML = "http://www.w3.org/1999/xhtml";
 
   function startTest() {
     is(document.contains(document), true, "Document should contain itself!");
 
-    var tb = document.getElementById("testbutton");
-    is(document.contains(tb), true, "Document should contain element in it!");
-    is(tb.contains(tb), true, "Element should contain itself.")
-    var anon = document.getAnonymousElementByAttribute(tb, "anonid", "button-box");
+    let box = document.getElementById("xbl-host");
+    is(document.contains(box), true, "Document should contain element in it!");
+    is(box.contains(box), true, "Element should contain itself.")
+    let anon = document.getAnonymousElementByAttribute(box, "anonid", "xbl-anon");
     is(document.contains(anon), false, "Document should not contain anonymous element in it!");
-    is(tb.contains(anon), false, "Element should not contain anonymous element in it!");
+    is(box.contains(anon), false, "Element should not contain anonymous element in it!");
     is(anon.contains(anon), true, "Anonymous element should contain itself.")
-    is(document.documentElement.contains(tb), true, "Element should contain element in it!");
+    is(document.documentElement.contains(box), true, "Element should contain element in it!");
     is(document.contains(document.createElement("foo")), false, "Document shouldn't contain element which is't in the document");
     is(document.contains(document.createTextNode("foo")), false, "Document shouldn't contain text node which is't in the document");
 
     var link = document.getElementById("link");
     is(document.contains(link.firstChild), true,
        "Document should contain a text node in it.");
     is(link.contains(link.firstChild), true,
        "Element should contain a text node in it.");
index 732920171d6cdd06162a11d6bb5c7be1183bc387..6ee654e4223e64a0443995bed683e4f044e83efc
GIT binary patch
literal 4095
zc$}412{;pM9G`1K?jvVZ&fMkrGL#&VEK|zSVY8L{zQ!k0<jdSsc!Z)<jzpMigmP=4
zB1hy(wxt|}#JA~tz9wY7&+|Up`#kS{zyI;u`~R7nGBC0ND9a`rV+NpF+W?{fZy$Fr
z=tYFG<w+I*gXnGHGbhN~H-HU5$2dm^05I~A@0pVk_*U;nIi`S<XUiLU=qSIZ2MAFi
zxOgi<z3d$v{SZpto-U3)h*iiPH)_umU$6ZYlGLH+++`;S7`me<vX{wShrPP*RPye?
zS(q>6>RtQ&;R8m5%*6K6;y2?;&fnyhl9uUolHCR#@jkWX)a6{59uLN74t|vq|B{N+
zX3GF(DW_>y{gVw5JyLM^pUnG3g4mX?(`ABpC#LpxbTuB#nHkS@?|}Qv;kE&Ozm_Wf
zkol#B+{dTEU)4Y#`EKv8Mqk%tHrZo#xHn+nSe>gcL0cp`=*p!Y*SpP?qY}kmI$t<x
zrb1<qeGZykpYn9kri2oi)XzjjLCDQuv8cd|pzm22;`D@8O;bhMI}h#W%vnSvQq{Dw
zq;e1(iTx-U@$S-*9_R485A#!Hgps<FzW8aED%=Dz<E&dmW`S-HUqQD9TD`bH6Z;aE
zn0TYK+(&*%)yx=EKZ`9FSePHNOICb%{A14hw64dgPB^WV-Y)(X*DbQNDvX%v@_B~d
z;u?xSS71;2QdA_v78jR6&Ylk}B276j(V1^5HZCfuz!4XqzAipnif&`1JsTi7`1p9p
z%gZAIpspS+2qkx~i!>^l0v;Hno+?E?DUr7VZQx3-2p=dM>I40sMRYxjy<NT;dwHMK
z6Y@&4iBGnT!{j8LPvjV7MI<KjS$!bvLDT;dN@WvFNs@d5k+<?6hN9@VkEUo)z_Jta
zJl<?x7IV|+&d@WrqY@J6<%5Iaklz$tnzmqEy#Ju?MaLzwSjeaC<SkD#zOOg=&hHZS
z9`+iBp#U0GZ1>1)DqMPJk}2<&AON6BGaIF_)bH5?GGEMkPEr#7X#F9shKm@D*OB)u
zCJhD6AHhCPncxJ~Bdu~1w*|8H`Pp7EIf02TzRWx)_qQ5%Mjb{|)73#wHGtPQq>U(j
z{;JCPCdSU$D&e`p4+~0TWk5Lz#!@c2r@Iab8_5;|3&94kst`j~0dx{3hd!r>)#;SA
zWjareR1K=q8j%-=$9!V~J99XjgR_p2PO=ZEy?BXj1sWaYR<`Ce8)<urnXHXJr_-oE
zQWe(ylI6{z0#UTKVv?!V*Zvs%Wt94-TQK2oM!o^u{?R($i(Q7jSj2=qaOZY}XOoJ;
zIu{OD%DarS^pJoA;OUt56hr-wbNKxG2c4yCvNR8LedUiUteaIsT8S^_bl8>UW-J*x
zaqA4ay&=W=)uBogdPV*!eCVjN{VZB0=o;EFRSPHbrG0Y#b!aeVK6$YoWFyJbd=O?K
z3CYk4GuXWVYU6SZcMux8JoCJvlZRk|9eHGkcXy1dMvS-GyX{S1InA{z-gsG;S2C>?
zv9EnOBSau`A%uaO=h|q^tq0|vpIPQVXpkZCpE=eFPLE?MR)wNwdH{gqrv#|`yNRaQ
zIA)s_Oe~FCm0QddASxH^V0Li0hq<{Od4<9HQP)t~#x?0i8p;MFS;4Shg{}ow++RSE
z9*PdYOXl@Qz@Q!;-V}OT$Vo41!MqSY;802?kNVj{yAhb*q08)$)VPX5ryT~D_4>lZ
zQYP=J@m<&_R_u}&taH8|haZxNwore_zEIr{Dt`}OzM1;XFT~&K>Zq(dxN9F$G&sho
zWWqYZ0CF<-i^Xg!BA8$!h7Ve?KnoLO=349Txpx%b*WKdTUt+%C>580tXQLgYr5z+L
zZLKQ09nW#R?%BK8Mf-_+NZp-D*4#T4aRFV9{vZ=~_lq^%CVpSe`Eeu+@-`E2@LeUB
z=8jY<prI>O+eg}w<CbMT$8!1`h?lCWDozdDKArs?Vr8t%SF*rVL3&@A_Drr>+&;nW
z{jDI^*Ht@fTBlVGW-Jje86SCndLTM*Ztv$=)02u@gj`0K{?vc6eY~aRR+BI^F(VuE
z!a*zv@wWHvqV1Gy>!PD)!O@1U0!YagI78Aw9+hv;l!Ii8j0DH-eUMbMX+)%8OR+IU
zM9|OPSCD%j?aO&JirmS5ddfp53b6M=Kz$JB;V>m9@71Q`$qmP^Z+dWity`Z8m%-Fx
zWH{JMB95f4$Dzl{2(*#7AJ2KKxuostFzaYbzVQ5DWhSDsdb9#G^H=>VfBq*YZa(Q$
zks2fk{SAzGZd*TrVhM=-q~&%6`zbnbB#Wmt9a+}hE3HQ4ba*f&cfs&MmwrdZ?pGnI
z@yj{Y6O-W6uV!>1vF=3uQ`O?Ko+o75)TGge2|SbO{emh4_UtRz<W~j#-MJAt`;d_W
ze)h#gIePnX_K~<$%eh^ldD?gtN%4H~V^)n~wR_q`P^#fU_mi6D8)x;O9B>+FS(GI{
z9uU&l(G3C*_j2<KJ*f@jMii-kI5;9@9<h{`$0Cvq*K%>o%rih{0^!XgW7kdfkMA9x
z!e$U`V-tETmRu@`LxpWatW6pVp_&d%ZC#waW8d$zxPD3zbu(+|evIRWu!%P^rNuS!
z6)2SVbq?L|e#qa*)$5!rFj)~7GjlQE+Q3WYCZav_0$i>e<ZC5uu{<*vSXP&QGu3(q
zA@G78B7|)3(VRKjT-dwYV(eI^_Nf)}znPAHC&OkYTJXPbTmX_D<@2YdX8Q4$UN;0O
zXqy>o!T*s_p@*r^Hn7)BKkW5}@SB-1>R?^z0UKDd-vzyyA)`jcZh*R>J=?lE>&@BE
z>f8zCW1xwM%Bs=ExYmPFGiLlD<EkV3^Lk3Jsiy)XO{A;Ni?#&U`Y!4OqiE*(&xLJG
zxZZkED}0e=!Zk*W>4y>9BG0wso;uI(K)|};Ph0KQj&f>DUuM9@gMDk@^>g9}@G4KJ
eC%IQ^rv}rHsj&``2?$ti>vljkxvu(bl+(YqGeaBz
--- a/dom/quota/ActorsParent.cpp
+++ b/dom/quota/ActorsParent.cpp
@@ -218,16 +218,18 @@ enum AppId {
 #define METADATA_TMP_FILE_NAME ".metadata-tmp"
 #define METADATA_V2_FILE_NAME ".metadata-v2"
 #define METADATA_V2_TMP_FILE_NAME ".metadata-v2-tmp"
 
 #define WEB_APPS_STORE_FILE_NAME "webappsstore.sqlite"
 #define LS_ARCHIVE_FILE_NAME "ls-archive.sqlite"
 #define LS_ARCHIVE_TMP_FILE_NAME "ls-archive-tmp.sqlite"
 
+const uint32_t kLocalStorageArchiveVersion = 1;
+
 const char kProfileDoChangeTopic[] = "profile-do-change";
 
 /******************************************************************************
  * SQLite functions
  ******************************************************************************/
 
 int32_t MakeStorageVersion(uint32_t aMajorStorageVersion,
                            uint32_t aMinorStorageVersion) {
@@ -320,16 +322,168 @@ nsresult CreateWebAppsStoreConnection(ns
     *aConnection = nullptr;
     return NS_OK;
   }
 
   connection.forget(aConnection);
   return NS_OK;
 }
 
+nsresult GetLocalStorageArchiveFile(const nsAString& aDirectoryPath,
+                                    nsIFile** aLsArchiveFile) {
+  AssertIsOnIOThread();
+  MOZ_ASSERT(!aDirectoryPath.IsEmpty());
+  MOZ_ASSERT(aLsArchiveFile);
+
+  nsCOMPtr<nsIFile> lsArchiveFile;
+  nsresult rv =
+      NS_NewLocalFile(aDirectoryPath, false, getter_AddRefs(lsArchiveFile));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = lsArchiveFile->Append(NS_LITERAL_STRING(LS_ARCHIVE_FILE_NAME));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  lsArchiveFile.forget(aLsArchiveFile);
+  return NS_OK;
+}
+
+nsresult GetLocalStorageArchiveTmpFile(const nsAString& aDirectoryPath,
+                                       nsIFile** aLsArchiveTmpFile) {
+  AssertIsOnIOThread();
+  MOZ_ASSERT(!aDirectoryPath.IsEmpty());
+  MOZ_ASSERT(aLsArchiveTmpFile);
+
+  nsCOMPtr<nsIFile> lsArchiveTmpFile;
+  nsresult rv =
+      NS_NewLocalFile(aDirectoryPath, false, getter_AddRefs(lsArchiveTmpFile));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = lsArchiveTmpFile->Append(NS_LITERAL_STRING(LS_ARCHIVE_TMP_FILE_NAME));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  lsArchiveTmpFile.forget(aLsArchiveTmpFile);
+  return NS_OK;
+}
+
+nsresult InitializeLocalStorageArchive(mozIStorageConnection* aConnection,
+                                       uint32_t aVersion) {
+  AssertIsOnIOThread();
+  MOZ_ASSERT(aConnection);
+
+  nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+      "CREATE TABLE database(version INTEGER NOT NULL DEFAULT 0);"));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCOMPtr<mozIStorageStatement> stmt;
+  rv = aConnection->CreateStatement(
+      NS_LITERAL_CSTRING("INSERT INTO database (version) VALUES (:version)"),
+      getter_AddRefs(stmt));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("version"), aVersion);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->Execute();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult IsLocalStorageArchiveInitialized(mozIStorageConnection* aConnection,
+                                          bool& aInitialized) {
+  AssertIsOnIOThread();
+  MOZ_ASSERT(aConnection);
+
+  bool exists;
+  nsresult rv =
+      aConnection->TableExists(NS_LITERAL_CSTRING("database"), &exists);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  aInitialized = exists;
+  return NS_OK;
+}
+
+nsresult LoadLocalStorageArchiveVersion(mozIStorageConnection* aConnection,
+                                        uint32_t& aVersion) {
+  AssertIsOnIOThread();
+  MOZ_ASSERT(aConnection);
+
+  nsCOMPtr<mozIStorageStatement> stmt;
+  nsresult rv = aConnection->CreateStatement(
+      NS_LITERAL_CSTRING("SELECT version FROM database"), getter_AddRefs(stmt));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  bool hasResult;
+  rv = stmt->ExecuteStep(&hasResult);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (NS_WARN_IF(!hasResult)) {
+    return NS_ERROR_FILE_CORRUPTED;
+  }
+
+  int32_t version;
+  rv = stmt->GetInt32(0, &version);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  aVersion = version;
+  return NS_OK;
+}
+
+/*
+nsresult SaveLocalStorageArchiveVersion(mozIStorageConnection* aConnection,
+                                        uint32_t aVersion) {
+  AssertIsOnIOThread();
+  MOZ_ASSERT(aConnection);
+
+  nsCOMPtr<mozIStorageStatement> stmt;
+  nsresult rv = aConnection->CreateStatement(
+      NS_LITERAL_CSTRING("UPDATE database SET version = :version;"),
+      getter_AddRefs(stmt));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("version"), aVersion);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->Execute();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+*/
+
 /******************************************************************************
  * Quota manager class declarations
  ******************************************************************************/
 
 }  // namespace
 
 class DirectoryLockImpl final : public DirectoryLock {
   RefPtr<QuotaManager> mQuotaManager;
@@ -4548,23 +4702,18 @@ nsresult QuotaManager::UpgradeStorageFro
 }
 
 nsresult QuotaManager::MaybeRemoveLocalStorageData() {
   AssertIsOnIOThread();
   MOZ_ASSERT(!CachedNextGenLocalStorageEnabled());
 
   // Cleanup the tmp file first, if there's any.
   nsCOMPtr<nsIFile> lsArchiveTmpFile;
-  nsresult rv =
-      NS_NewLocalFile(mStoragePath, false, getter_AddRefs(lsArchiveTmpFile));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = lsArchiveTmpFile->Append(NS_LITERAL_STRING(LS_ARCHIVE_TMP_FILE_NAME));
+  nsresult rv = GetLocalStorageArchiveTmpFile(mStoragePath,
+                                              getter_AddRefs(lsArchiveTmpFile));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   bool exists;
   rv = lsArchiveTmpFile->Exists(&exists);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
@@ -4574,22 +4723,17 @@ nsresult QuotaManager::MaybeRemoveLocalS
     rv = lsArchiveTmpFile->Remove(false);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   // Now check the real archive file.
   nsCOMPtr<nsIFile> lsArchiveFile;
-  rv = NS_NewLocalFile(mStoragePath, false, getter_AddRefs(lsArchiveFile));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = lsArchiveFile->Append(NS_LITERAL_STRING(LS_ARCHIVE_FILE_NAME));
+  rv = GetLocalStorageArchiveFile(mStoragePath, getter_AddRefs(lsArchiveFile));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = lsArchiveFile->Exists(&exists);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -4735,43 +4879,37 @@ nsresult QuotaManager::MaybeRemoveLocalS
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   return NS_OK;
 }
 
-nsresult QuotaManager::MaybeCreateLocalStorageArchive() {
+nsresult QuotaManager::CreateLocalStorageArchiveConnectionFromWebAppsStore(
+    mozIStorageConnection** aConnection) {
   AssertIsOnIOThread();
   MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
-
-  // Check if the archive was already successfully created.
+  MOZ_ASSERT(aConnection);
+
   nsCOMPtr<nsIFile> lsArchiveFile;
   nsresult rv =
-      NS_NewLocalFile(mStoragePath, false, getter_AddRefs(lsArchiveFile));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = lsArchiveFile->Append(NS_LITERAL_STRING(LS_ARCHIVE_FILE_NAME));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
+      GetLocalStorageArchiveFile(mStoragePath, getter_AddRefs(lsArchiveFile));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+#ifdef DEBUG
   bool exists;
   rv = lsArchiveFile->Exists(&exists);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
-
-  if (exists) {
-    // ls-archive.sqlite already exists, nothing to create.
-    return NS_OK;
-  }
+  MOZ_ASSERT(!exists);
+#endif
 
   // Get the storage service first, we will need it at multiple places.
   nsCOMPtr<mozIStorageService> ss =
       do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
@@ -4851,22 +4989,18 @@ nsresult QuotaManager::MaybeCreateLocalS
 
     rv = webAppsStoreFile->CopyTo(storageDir,
                                   NS_LITERAL_STRING(LS_ARCHIVE_TMP_FILE_NAME));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     nsCOMPtr<nsIFile> lsArchiveTmpFile;
-    rv = NS_NewLocalFile(mStoragePath, false, getter_AddRefs(lsArchiveTmpFile));
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = lsArchiveTmpFile->Append(NS_LITERAL_STRING(LS_ARCHIVE_TMP_FILE_NAME));
+    rv = GetLocalStorageArchiveTmpFile(mStoragePath,
+                                       getter_AddRefs(lsArchiveTmpFile));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     if (journalMode.EqualsLiteral("wal")) {
       nsCOMPtr<mozIStorageConnection> lsArchiveTmpConnection;
       rv = ss->OpenUnsharedDatabase(lsArchiveTmpFile,
                                     getter_AddRefs(lsArchiveTmpConnection));
@@ -4890,16 +5024,25 @@ nsresult QuotaManager::MaybeCreateLocalS
     }
 
     // Finally, rename ls-archive-tmp.sqlite to ls-archive.sqlite
     rv = lsArchiveTmpFile->MoveTo(nullptr,
                                   NS_LITERAL_STRING(LS_ARCHIVE_FILE_NAME));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
+
+    nsCOMPtr<mozIStorageConnection> lsArchiveConnection;
+    rv = ss->OpenUnsharedDatabase(lsArchiveFile,
+                                  getter_AddRefs(lsArchiveConnection));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    lsArchiveConnection.forget(aConnection);
   } else {
     // If webappsstore database is not useable, just create an empty archive.
 
     // Ensure the storage directory actually exists.
     nsCOMPtr<nsIFile> storageDirectory;
     rv = NS_NewLocalFile(GetStoragePath(), false,
                          getter_AddRefs(storageDirectory));
     if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -4918,20 +5061,236 @@ nsresult QuotaManager::MaybeCreateLocalS
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     rv = StorageDBUpdater::Update(lsArchiveConnection);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
-  }
-
-  return NS_OK;
-}
+
+    lsArchiveConnection.forget(aConnection);
+  }
+
+  return NS_OK;
+}
+
+nsresult QuotaManager::CreateLocalStorageArchiveConnection(
+    mozIStorageConnection** aConnection, bool& aNewlyCreated) {
+  AssertIsOnIOThread();
+  MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
+  MOZ_ASSERT(aConnection);
+
+  nsCOMPtr<nsIFile> lsArchiveTmpFile;
+  nsresult rv = GetLocalStorageArchiveTmpFile(mStoragePath,
+                                              getter_AddRefs(lsArchiveTmpFile));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  bool exists;
+  rv = lsArchiveTmpFile->Exists(&exists);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (exists) {
+    rv = lsArchiveTmpFile->Remove(false);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  // Check if the archive was already successfully created.
+  nsCOMPtr<nsIFile> lsArchiveFile;
+  rv = GetLocalStorageArchiveFile(mStoragePath, getter_AddRefs(lsArchiveFile));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = lsArchiveFile->Exists(&exists);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (exists) {
+    bool removed = false;
+
+    bool isDirectory;
+    rv = lsArchiveFile->IsDirectory(&isDirectory);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (isDirectory) {
+      rv = lsArchiveFile->Remove(true);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      removed = true;
+    }
+
+    nsCOMPtr<mozIStorageService> ss =
+        do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    nsCOMPtr<mozIStorageConnection> connection;
+    rv = ss->OpenUnsharedDatabase(lsArchiveFile, getter_AddRefs(connection));
+    if (!removed && rv == NS_ERROR_FILE_CORRUPTED) {
+      rv = lsArchiveFile->Remove(false);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      removed = true;
+
+      rv = ss->OpenUnsharedDatabase(lsArchiveFile, getter_AddRefs(connection));
+    }
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    rv = StorageDBUpdater::Update(connection);
+    if (!removed && NS_FAILED(rv)) {
+      rv = connection->Close();
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      rv = lsArchiveFile->Remove(false);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      removed = true;
+
+      rv = ss->OpenUnsharedDatabase(lsArchiveFile, getter_AddRefs(connection));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      rv = StorageDBUpdater::Update(connection);
+    }
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    connection.forget(aConnection);
+    aNewlyCreated = removed;
+    return NS_OK;
+  }
+
+  nsCOMPtr<mozIStorageConnection> connection;
+  rv = CreateLocalStorageArchiveConnectionFromWebAppsStore(
+      getter_AddRefs(connection));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  connection.forget(aConnection);
+  aNewlyCreated = true;
+  return NS_OK;
+}
+
+nsresult QuotaManager::RecreateLocalStorageArchive(
+    nsCOMPtr<mozIStorageConnection>& aConnection) {
+  AssertIsOnIOThread();
+  MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
+
+  // Close local storage archive connection. We are going to remove underlying
+  // file.
+  nsresult rv = aConnection->Close();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = MaybeRemoveLocalStorageDirectories();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCOMPtr<nsIFile> lsArchiveFile;
+  rv = GetLocalStorageArchiveFile(mStoragePath, getter_AddRefs(lsArchiveFile));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+#ifdef DEBUG
+  bool exists;
+  rv = lsArchiveFile->Exists(&exists);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  MOZ_ASSERT(exists);
+#endif
+
+  rv = lsArchiveFile->Remove(false);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = CreateLocalStorageArchiveConnectionFromWebAppsStore(
+      getter_AddRefs(aConnection));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult QuotaManager::DowngradeLocalStorageArchive(
+    nsCOMPtr<mozIStorageConnection>& aConnection) {
+  AssertIsOnIOThread();
+  MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
+
+  nsresult rv = RecreateLocalStorageArchive(aConnection);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = InitializeLocalStorageArchive(aConnection, kLocalStorageArchiveVersion);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult QuotaManager::UpgradeLocalStorageArchiveFrom0To1(
+    nsCOMPtr<mozIStorageConnection>& aConnection) {
+  AssertIsOnIOThread();
+  MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
+
+  nsresult rv = RecreateLocalStorageArchive(aConnection);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = InitializeLocalStorageArchive(aConnection, 1);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+/*
+nsresult QuotaManager::UpgradeLocalStorageArchiveFrom1To2(
+    nsCOMPtr<mozIStorageConnection>& aConnection) {
+  nsresult rv = SaveLocalStorageArchiveVersion(aConnection, 2);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+*/
 
 #ifdef DEBUG
 
 void QuotaManager::AssertStorageIsInitialized() const {
   AssertIsOnIOThread();
   MOZ_ASSERT(mStorageInitialized);
 }
 
@@ -5098,22 +5457,97 @@ nsresult QuotaManager::EnsureStorageIsIn
 
     rv = transaction.Commit();
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   if (CachedNextGenLocalStorageEnabled()) {
-    rv = MaybeCreateLocalStorageArchive();
+    nsCOMPtr<mozIStorageConnection> connection;
+    bool newlyCreated;
+    rv = CreateLocalStorageArchiveConnection(getter_AddRefs(connection),
+                                             newlyCreated);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    uint32_t version = 0;
+
+    if (!newlyCreated) {
+      bool initialized;
+      rv = IsLocalStorageArchiveInitialized(connection, initialized);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      if (initialized) {
+        rv = LoadLocalStorageArchiveVersion(connection, version);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return rv;
+        }
+      }
+    }
+
+    if (version > kLocalStorageArchiveVersion) {
+      rv = DowngradeLocalStorageArchive(connection);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      rv = LoadLocalStorageArchiveVersion(connection, version);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      MOZ_ASSERT(version == kLocalStorageArchiveVersion);
+    } else if (version != kLocalStorageArchiveVersion) {
+      if (newlyCreated) {
+        MOZ_ASSERT(version == 0);
+
+        rv = InitializeLocalStorageArchive(connection,
+                                           kLocalStorageArchiveVersion);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return rv;
+        }
+      } else {
+        static_assert(kLocalStorageArchiveVersion == 1,
+                      "Upgrade function needed due to LocalStorage archive "
+                      "version increase.");
+
+        while (version != kLocalStorageArchiveVersion) {
+          if (version == 0) {
+            rv = UpgradeLocalStorageArchiveFrom0To1(connection);
+          } /* else if (version == 1) {
+            rv = UpgradeLocalStorageArchiveFrom1To2(connection);
+          } */ else {
+            QM_WARNING(
+                "Unable to initialize LocalStorage archive, no upgrade path is "
+                "available!");
+            return NS_ERROR_FAILURE;
+          }
+
+          if (NS_WARN_IF(NS_FAILED(rv))) {
+            return rv;
+          }
+
+          rv = LoadLocalStorageArchiveVersion(connection, version);
+          if (NS_WARN_IF(NS_FAILED(rv))) {
+            return rv;
+          }
+        }
+
+        MOZ_ASSERT(version == kLocalStorageArchiveVersion);
+      }
+    }
   } else {
     rv = MaybeRemoveLocalStorageData();
-  }
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
   }
 
   mStorageInitialized = true;
 
   return NS_OK;
 }
 
 already_AddRefed<DirectoryLock> QuotaManager::CreateDirectoryLock(
--- a/dom/quota/QuotaManager.h
+++ b/dom/quota/QuotaManager.h
@@ -420,17 +420,35 @@ class QuotaManager final : public Backgr
   nsresult UpgradeStorageFrom2_0To2_1(mozIStorageConnection* aConnection);
 
   nsresult UpgradeStorageFrom2_1To2_2(mozIStorageConnection* aConnection);
 
   nsresult MaybeRemoveLocalStorageData();
 
   nsresult MaybeRemoveLocalStorageDirectories();
 
-  nsresult MaybeCreateLocalStorageArchive();
+  nsresult CreateLocalStorageArchiveConnectionFromWebAppsStore(
+      mozIStorageConnection** aConnection);
+
+  nsresult CreateLocalStorageArchiveConnection(
+      mozIStorageConnection** aConnection, bool& aNewlyCreated);
+
+  nsresult RecreateLocalStorageArchive(
+      nsCOMPtr<mozIStorageConnection>& aConnection);
+
+  nsresult DowngradeLocalStorageArchive(
+      nsCOMPtr<mozIStorageConnection>& aConnection);
+
+  nsresult UpgradeLocalStorageArchiveFrom0To1(
+      nsCOMPtr<mozIStorageConnection>& aConnection);
+
+/*
+  nsresult UpgradeLocalStorageArchiveFrom1To2(
+      nsCOMPtr<mozIStorageConnection>& aConnection);
+*/
 
   nsresult InitializeRepository(PersistenceType aPersistenceType);
 
   nsresult InitializeOrigin(PersistenceType aPersistenceType,
                             const nsACString& aGroup, const nsACString& aOrigin,
                             int64_t aAccessTime, bool aPersisted,
                             nsIFile* aDirectory);
 
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a17b90dedfccb12bcd151c2d010af48ac46f2729
GIT binary patch
literal 7675
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2f11abe85833b925e280b936e8ccf241900ecb91
GIT binary patch
literal 7799
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
Ldjz%{{{;L87BC<X
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/unit/test_localStorageArchive1upgrade.js
@@ -0,0 +1,65 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify that local storage directories are removed
+ * during local storage archive upgrade from version 0 to version 1.
+ * See bug 1546305.
+ */
+
+async function testSteps() {
+  const lsDirs = [
+    "storage/default/http+++example.com/ls",
+    "storage/default/http+++localhost/ls",
+    "storage/default/http+++www.mozilla.org/ls",
+  ];
+
+  info("Clearing");
+
+  let request = clear();
+  await requestFinished(request);
+
+  info("Installing package");
+
+  // The profile contains three initialized origin directories with local
+  // storage data, local storage archive, a script for origin initialization,
+  // the storage database and the web apps store database:
+  // - storage/default/https+++example.com
+  // - storage/default/https+++localhost
+  // - storage/default/https+++www.mozilla.org
+  // - storage/ls-archive.sqlite
+  // - create_db.js
+  // - storage.sqlite
+  // - webappsstore.sqlite
+  // The file create_db.js in the package was run locally (with a build that
+  // doesn't support local storage archive upgrades), specifically it was
+  // temporarily added to xpcshell.ini and then executed:
+  //   mach xpcshell-test --interactive dom/localstorage/test/unit/create_db.js
+  // Note: to make it become the profile in the test, additional manual steps
+  // are needed.
+  // 1. Remove the folder "storage/temporary".
+  installPackage("localStorageArchive1upgrade_profile");
+
+  info("Checking ls dirs");
+
+  for (let lsDir of lsDirs) {
+    let dir = getRelativeFile(lsDir);
+
+    exists = dir.exists();
+    ok(exists, "ls directory does exist");
+  }
+
+  request = init();
+  request = await requestFinished(request);
+
+  info("Checking ls dirs");
+
+  for (let lsDir of lsDirs) {
+    let dir = getRelativeFile(lsDir);
+
+    exists = dir.exists();
+    ok(!exists, "ls directory doesn't exist");
+  }
+}
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/unit/test_localStorageArchiveDowngrade.js
@@ -0,0 +1,66 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify that local storage directories are removed
+ * during local storage archive downgrade from any future version to current
+ * version. See bug 1546305.
+ */
+
+async function testSteps() {
+  const lsDirs = [
+    "storage/default/http+++example.com/ls",
+    "storage/default/http+++localhost/ls",
+    "storage/default/http+++www.mozilla.org/ls",
+  ];
+
+  info("Clearing");
+
+  let request = clear();
+  await requestFinished(request);
+
+  info("Installing package");
+
+  // The profile contains three initialized origin directories with local
+  // storage data, local storage archive, a script for origin initialization,
+  // the storage database and the web apps store database:
+  // - storage/default/https+++example.com
+  // - storage/default/https+++localhost
+  // - storage/default/https+++www.mozilla.org
+  // - storage/ls-archive.sqlite
+  // - create_db.js
+  // - storage.sqlite
+  // - webappsstore.sqlite
+  // The file create_db.js in the package was run locally (with a build that
+  // supports local storage archive upgrades and local storage archive version
+  // set to max integer), specifically it was temporarily added to xpcshell.ini
+  // and then executed:
+  //   mach xpcshell-test --interactive dom/localstorage/test/unit/create_db.js
+  // Note: to make it become the profile in the test, additional manual steps
+  // are needed.
+  // 1. Remove the folder "storage/temporary".
+  installPackage("localStorageArchiveDowngrade_profile");
+
+  info("Checking ls dirs");
+
+  for (let lsDir of lsDirs) {
+    let dir = getRelativeFile(lsDir);
+
+    exists = dir.exists();
+    ok(exists, "ls directory does exist");
+  }
+
+  request = init();
+  request = await requestFinished(request);
+
+  info("Checking ls dirs");
+
+  for (let lsDir of lsDirs) {
+    let dir = getRelativeFile(lsDir);
+
+    exists = dir.exists();
+    ok(!exists, "ls directory doesn't exist");
+  }
+}
--- a/dom/quota/test/unit/xpcshell.ini
+++ b/dom/quota/test/unit/xpcshell.ini
@@ -8,16 +8,18 @@ support-files =
   basics_profile.zip
   clearStorageForPrincipal_profile.zip
   createLocalStorage_profile.zip
   defaultStorageUpgrade_profile.zip
   getUsage_profile.zip
   groupMismatch_profile.zip
   idbSubdirUpgrade1_profile.zip
   idbSubdirUpgrade2_profile.zip
+  localStorageArchive1upgrade_profile.zip
+  localStorageArchiveDowngrade_profile.zip
   morgueCleanup_profile.zip
   obsoleteOriginAttributes_profile.zip
   originAttributesUpgrade_profile.zip
   removeAppsUpgrade_profile.zip
   removeLocalStorage1_profile.zip
   removeLocalStorage2_profile.zip
   storagePersistentUpgrade_profile.zip
   tempMetadataCleanup_profile.zip
@@ -29,16 +31,18 @@ support-files =
 [test_createLocalStorage.js]
 [test_clearStorageForPrincipal.js]
 [test_defaultStorageUpgrade.js]
 [test_getUsage.js]
 [test_groupMismatch.js]
 [test_idbSubdirUpgrade.js]
 [test_initTemporaryStorage.js]
 [test_listInitializedOrigins.js]
+[test_localStorageArchive1upgrade.js]
+[test_localStorageArchiveDowngrade.js]
 [test_morgueCleanup.js]
 [test_obsoleteOriginAttributesUpgrade.js]
 [test_obsoleteOrigins.js]
 [test_originAttributesUpgrade.js]
 [test_persist.js]
 [test_persist_eviction.js]
 [test_persist_globalLimit.js]
 [test_persist_groupLimit.js]
--- a/dom/svg/SVGContentUtils.cpp
+++ b/dom/svg/SVGContentUtils.cpp
@@ -581,17 +581,17 @@ void SVGContentUtils::RectilinearGetStro
 }
 
 double SVGContentUtils::ComputeNormalizedHypotenuse(double aWidth,
                                                     double aHeight) {
   return NS_hypot(aWidth, aHeight) / M_SQRT2;
 }
 
 float SVGContentUtils::AngleBisect(float a1, float a2) {
-  float delta = fmod(a2 - a1, static_cast<float>(2 * M_PI));
+  float delta = std::fmod(a2 - a1, static_cast<float>(2 * M_PI));
   if (delta < 0) {
     delta += static_cast<float>(2 * M_PI);
   }
   /* delta is now the angle from a1 around to a2, in the range [0, 2*M_PI) */
   float r = a1 + delta / 2;
   if (delta >= M_PI) {
     /* the arc from a2 to a1 is smaller, so use the ray on that side */
     r += static_cast<float>(M_PI);
--- a/dom/svg/SVGLineElement.cpp
+++ b/dom/svg/SVGLineElement.cpp
@@ -99,17 +99,17 @@ SVGElement::LengthAttributesInfo SVGLine
 //----------------------------------------------------------------------
 // SVGGeometryElement methods
 
 void SVGLineElement::GetMarkPoints(nsTArray<SVGMark>* aMarks) {
   float x1, y1, x2, y2;
 
   GetAnimatedLengthValues(&x1, &y1, &x2, &y2, nullptr);
 
-  float angle = atan2(y2 - y1, x2 - x1);
+  float angle = std::atan2(y2 - y1, x2 - x1);
 
   aMarks->AppendElement(SVGMark(x1, y1, angle, SVGMark::eStart));
   aMarks->AppendElement(SVGMark(x2, y2, angle, SVGMark::eEnd));
 }
 
 void SVGLineElement::GetAsSimplePath(SimplePath* aSimplePath) {
   float x1, y1, x2, y2;
   GetAnimatedLengthValues(&x1, &y1, &x2, &y2, nullptr);
--- a/dom/svg/SVGMotionSMILType.cpp
+++ b/dom/svg/SVGMotionSMILType.cpp
@@ -324,17 +324,17 @@ nsresult SVGMotionSMILType::ComputeDista
 
   MOZ_ASSERT(from.mSegmentType == to.mSegmentType,
              "Mismatched MotionSegment types");
   if (from.mSegmentType == eSegmentType_PathPoint) {
     const PathPointParams& fromParams = from.mU.mPathPointParams;
     const PathPointParams& toParams = to.mU.mPathPointParams;
     MOZ_ASSERT(fromParams.mPath == toParams.mPath,
                "Interpolation endpoints should be from same path");
-    aDistance = fabs(toParams.mDistToPoint - fromParams.mDistToPoint);
+    aDistance = std::fabs(toParams.mDistToPoint - fromParams.mDistToPoint);
   } else {
     const TranslationParams& fromParams = from.mU.mTranslationParams;
     const TranslationParams& toParams = to.mU.mTranslationParams;
     float dX = toParams.mX - fromParams.mX;
     float dY = toParams.mY - fromParams.mY;
     aDistance = NS_hypot(dX, dY);
   }
 
--- a/dom/svg/SVGPathSegUtils.cpp
+++ b/dom/svg/SVGPathSegUtils.cpp
@@ -190,46 +190,46 @@ static void TraverseLinetoRel(const floa
   }
   aState.pos = to;
 }
 
 static void TraverseLinetoHorizontalAbs(const float* aArgs,
                                         SVGPathTraversalState& aState) {
   Point to(aArgs[0], aState.pos.y);
   if (aState.ShouldUpdateLengthAndControlPoints()) {
-    aState.length += fabs(to.x - aState.pos.x);
+    aState.length += std::fabs(to.x - aState.pos.x);
     aState.cp1 = aState.cp2 = to;
   }
   aState.pos = to;
 }
 
 static void TraverseLinetoHorizontalRel(const float* aArgs,
                                         SVGPathTraversalState& aState) {
   aState.pos.x += aArgs[0];
   if (aState.ShouldUpdateLengthAndControlPoints()) {
-    aState.length += fabs(aArgs[0]);
+    aState.length += std::fabs(aArgs[0]);
     aState.cp1 = aState.cp2 = aState.pos;
   }
 }
 
 static void TraverseLinetoVerticalAbs(const float* aArgs,
                                       SVGPathTraversalState& aState) {
   Point to(aState.pos.x, aArgs[0]);
   if (aState.ShouldUpdateLengthAndControlPoints()) {
-    aState.length += fabs(to.y - aState.pos.y);
+    aState.length += std::fabs(to.y - aState.pos.y);
     aState.cp1 = aState.cp2 = to;
   }
   aState.pos = to;
 }
 
 static void TraverseLinetoVerticalRel(const float* aArgs,
                                       SVGPathTraversalState& aState) {
   aState.pos.y += aArgs[0];
   if (aState.ShouldUpdateLengthAndControlPoints()) {
-    aState.length += fabs(aArgs[0]);
+    aState.length += std::fabs(aArgs[0]);
     aState.cp1 = aState.cp2 = aState.pos;
   }
 }
 
 static void TraverseCurvetoCubicAbs(const float* aArgs,
                                     SVGPathTraversalState& aState) {
   Point to(aArgs[4], aArgs[5]);
   if (aState.ShouldUpdateLengthAndControlPoints()) {
--- a/dom/svg/SVGPolyElement.cpp
+++ b/dom/svg/SVGPolyElement.cpp
@@ -68,17 +68,17 @@ void SVGPolyElement::GetMarkPoints(nsTAr
 
   float px = points[0].mX, py = points[0].mY, prevAngle = 0.0;
 
   aMarks->AppendElement(SVGMark(px, py, 0, SVGMark::eStart));
 
   for (uint32_t i = 1; i < points.Length(); ++i) {
     float x = points[i].mX;
     float y = points[i].mY;
-    float angle = atan2(y - py, x - px);
+    float angle = std::atan2(y - py, x - px);
 
     // Vertex marker.
     if (i == 1) {
       aMarks->ElementAt(0).angle = angle;
     } else {
       aMarks->ElementAt(aMarks->Length() - 1).angle =
           SVGContentUtils::AngleBisect(prevAngle, angle);
     }
--- a/dom/svg/SVGPolygonElement.cpp
+++ b/dom/svg/SVGPolygonElement.cpp
@@ -41,17 +41,18 @@ void SVGPolygonElement::GetMarkPoints(ns
   SVGPolyElement::GetMarkPoints(aMarks);
 
   if (aMarks->IsEmpty() || aMarks->LastElement().type != SVGMark::eEnd) {
     return;
   }
 
   SVGMark *endMark = &aMarks->LastElement();
   SVGMark *startMark = &aMarks->ElementAt(0);
-  float angle = atan2(startMark->y - endMark->y, startMark->x - endMark->x);
+  float angle =
+      std::atan2(startMark->y - endMark->y, startMark->x - endMark->x);
 
   endMark->type = SVGMark::eMid;
   endMark->angle = SVGContentUtils::AngleBisect(angle, endMark->angle);
   startMark->angle = SVGContentUtils::AngleBisect(angle, startMark->angle);
   // for a polygon (as opposed to a polyline) there's an implicit extra point
   // co-located with the start point that SVGPolyElement::GetMarkPoints
   // doesn't return
   aMarks->AppendElement(
--- a/dom/svg/SVGTransformListSMILType.cpp
+++ b/dom/svg/SVGTransformListSMILType.cpp
@@ -202,17 +202,17 @@ nsresult SVGTransformListSMILType::Compu
       aDistance = sqrt(pow(a_tx - b_tx, 2) + (pow(a_ty - b_ty, 2)));
     } break;
 
     case SVG_TRANSFORM_ROTATE:
     case SVG_TRANSFORM_SKEWX:
     case SVG_TRANSFORM_SKEWY: {
       const float& a = fromTransform.mParams[0];
       const float& b = toTransform.mParams[0];
-      aDistance = fabs(a - b);
+      aDistance = std::fabs(a - b);
     } break;
 
     default:
       NS_ERROR("Got bad transform types for calculating distances");
       aDistance = 1.0;
       return NS_ERROR_FAILURE;
   }
 
--- a/dom/svg/SVGViewBoxSMILType.cpp
+++ b/dom/svg/SVGViewBoxSMILType.cpp
@@ -82,18 +82,18 @@ nsresult SVGViewBoxSMILType::ComputeDist
   // given change in the left side as it is for an equal change in the opposite
   // side. See https://bugzilla.mozilla.org/show_bug.cgi?id=541884#c12
 
   float dLeft = to->x - from->x;
   float dTop = to->y - from->y;
   float dRight = (to->x + to->width) - (from->x + from->width);
   float dBottom = (to->y + to->height) - (from->y + from->height);
 
-  aDistance =
-      sqrt(dLeft * dLeft + dTop * dTop + dRight * dRight + dBottom * dBottom);
+  aDistance = std::sqrt(dLeft * dLeft + dTop * dTop + dRight * dRight +
+                        dBottom * dBottom);
 
   return NS_OK;
 }
 
 nsresult SVGViewBoxSMILType::Interpolate(const SMILValue& aStartVal,
                                          const SMILValue& aEndVal,
                                          double aUnitDistance,
                                          SMILValue& aResult) const {
--- a/dom/tests/mochitest/general/test_offsets.js
+++ b/dom/tests/mochitest/general/test_offsets.js
@@ -8,16 +8,23 @@ function testElements(baseid, callback)
   for (var t = 0; t < elements.length; t++) {
     var element = elements[t];
 
     // Ignore presentational content inside menus
     if (element.closest("menu") && element.closest("[aria-hidden=true]")) {
       continue;
     }
 
+    // Ignore content inside a <button>  This can be removed if/when
+    // button switches to use shadow DOM.
+    let buttonParent = element.closest("button");
+    if (buttonParent && buttonParent !== element) {
+      continue;
+    }
+
     testElement(element);
   }
 
   var nonappended = document.createElement("div");
   nonappended.id = "nonappended";
   nonappended.setAttribute("_offsetParent", "null");
   testElement(nonappended);
 
--- a/js/src/frontend/BytecodeCompilation.h
+++ b/js/src/frontend/BytecodeCompilation.h
@@ -95,17 +95,19 @@ class MOZ_STACK_CLASS BytecodeCompiler {
 
   MOZ_MUST_USE bool emplaceEmitter(mozilla::Maybe<BytecodeEmitter>& emitter,
                                    const EitherParser& parser,
                                    SharedContext* sharedContext);
 
   // This function lives here, not in SourceAwareCompiler, because it mostly
   // uses fields in *this* class.
   template <typename Unit>
-  MOZ_MUST_USE bool assignSource(JS::SourceText<Unit>& sourceBuffer);
+  MOZ_MUST_USE bool assignSource(JS::SourceText<Unit>& sourceBuffer) {
+    return scriptSource->assignSource(cx, options, sourceBuffer);
+  }
 
   bool canLazilyParse() const;
 
   MOZ_MUST_USE bool deoptimizeArgumentsInEnclosingScripts(
       JSContext* cx, JS::Handle<JSObject*> environment);
 };
 
 class MOZ_STACK_CLASS GlobalScriptInfo final : public BytecodeCompiler {
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -395,29 +395,16 @@ bool BytecodeCompiler::createScriptSourc
   if (!sourceObject) {
     return false;
   }
 
   scriptSource = sourceObject->source();
   return true;
 }
 
-template <typename Unit>
-bool BytecodeCompiler::assignSource(SourceText<Unit>& sourceBuffer) {
-  if (!cx->realm()->behaviors().discardSource()) {
-    if (options.sourceIsLazy) {
-      scriptSource->setSourceRetrievable();
-    } else if (!scriptSource->setSourceCopy(cx, sourceBuffer)) {
-      return false;
-    }
-  }
-
-  return true;
-}
-
 bool BytecodeCompiler::canLazilyParse() const {
   return options.canLazilyParse &&
          !cx->realm()->behaviors().disableLazyParsing() &&
          !cx->realm()->behaviors().discardSource() && !options.sourceIsLazy &&
          !coverage::IsLCovEnabled() &&
          // Disabled during record/replay. The replay debugger requires
          // scripts to be constructed in a consistent order, which might not
          // happen with lazy parsing.
--- a/js/src/jit/x86-shared/AssemblerBuffer-x86-shared.h
+++ b/js/src/jit/x86-shared/AssemblerBuffer-x86-shared.h
@@ -29,16 +29,17 @@
 
 #ifndef jit_x86_shared_AssemblerBuffer_x86_shared_h
 #define jit_x86_shared_AssemblerBuffer_x86_shared_h
 
 #include <stdarg.h>
 #include <string.h>
 
 #include "jit/ExecutableAllocator.h"
+#include "jit/Ion.h"
 #include "jit/JitSpewer.h"
 
 // Spew formatting helpers.
 #define PRETTYHEX(x)      \
   (((x) < 0) ? "-" : ""), \
       ((unsigned)((x) ^ ((x) >> 31)) + ((unsigned)(x) >> 31))
 
 #define MEM_o "%s0x%x"
@@ -188,16 +189,22 @@ class AssemblerBuffer {
    * can continue assembling into the buffer, deferring OOM checking
    * until the user wants to read code out of the buffer.
    *
    * See also the |buffer| method.
    */
   void oomDetected() {
     m_oom = true;
     m_buffer.clear();
+#ifdef DEBUG
+    JitContext* context = MaybeGetJitContext();
+    if (context) {
+      context->setOOM();
+    }
+#endif
   }
 
   mozilla::Vector<unsigned char, 256, AssemblerBufferAllocPolicy> m_buffer;
   bool m_oom;
 };
 
 class GenericAssembler {
 #ifdef JS_JITSPEW
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -3654,19 +3654,18 @@ JS_PUBLIC_API JSString* JS_DecompileScri
 
   AssertHeapIsIdle();
   CHECK_THREAD(cx);
   script->ensureNonLazyCanonicalFunction();
   RootedFunction fun(cx, script->functionNonDelazifying());
   if (fun) {
     return JS_DecompileFunction(cx, fun);
   }
-  bool haveSource = script->scriptSource()->hasSourceText();
-  if (!haveSource &&
-      !JSScript::loadSource(cx, script->scriptSource(), &haveSource)) {
+  bool haveSource;
+  if (!ScriptSource::loadSource(cx, script->scriptSource(), &haveSource)) {
     return nullptr;
   }
   return haveSource ? JSScript::sourceData(cx, script)
                     : NewStringCopyZ<CanGC>(cx, "[no source]");
 }
 
 JS_PUBLIC_API JSString* JS_DecompileFunction(JSContext* cx,
                                              HandleFunction fun) {
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -8406,19 +8406,18 @@ class DebuggerSourceGetTextMatcher {
 
  public:
   explicit DebuggerSourceGetTextMatcher(JSContext* cx) : cx_(cx) {}
 
   using ReturnType = JSString*;
 
   ReturnType match(HandleScriptSourceObject sourceObject) {
     ScriptSource* ss = sourceObject->source();
-    bool hasSourceText = ss->hasSourceText();
-    if (!ss->hasSourceText() &&
-        !JSScript::loadSource(cx_, ss, &hasSourceText)) {
+    bool hasSourceText;
+    if (!ScriptSource::loadSource(cx_, ss, &hasSourceText)) {
       return nullptr;
     }
     if (!hasSourceText) {
       return NewStringCopyZ<CanGC>(cx_, "[no source]");
     }
 
     if (ss->isFunctionBody()) {
       return ss->functionBodyString(cx_);
--- a/js/src/vm/JSFunction.cpp
+++ b/js/src/vm/JSFunction.cpp
@@ -864,19 +864,20 @@ JSString* js::FunctionToString(JSContext
   bool haveSource = fun->isInterpreted() &&
                     (fun->isClassConstructor() || !fun->isSelfHostedBuiltin());
 
   // If we're in toSource mode, put parentheses around lambda functions so
   // that eval returns lambda, not function statement.
   bool addParentheses =
       haveSource && isToSource && (fun->isLambda() && !fun->isArrow());
 
-  if (haveSource && !script->scriptSource()->hasSourceText() &&
-      !JSScript::loadSource(cx, script->scriptSource(), &haveSource)) {
-    return nullptr;
+  if (haveSource) {
+    if (!ScriptSource::loadSource(cx, script->scriptSource(), &haveSource)) {
+      return nullptr;
+    }
   }
 
   // Fast path for the common case, to avoid StringBuffer overhead.
   if (!addParentheses && haveSource) {
     FunctionToStringCache& cache = cx->zone()->functionToStringCache();
     if (JSString* str = cache.lookup(script)) {
       return str;
     }
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -1647,40 +1647,77 @@ void ScriptSourceObject::setPrivate(JSRu
   // private data.
   JS::AutoSuppressGCAnalysis nogc;
   Value prevValue = getReservedSlot(PRIVATE_SLOT);
   rt->releaseScriptPrivate(prevValue);
   setReservedSlot(PRIVATE_SLOT, value);
   rt->addRefScriptPrivate(value);
 }
 
-/* static */
-bool JSScript::loadSource(JSContext* cx, ScriptSource* ss, bool* worked) {
-  MOZ_ASSERT(!ss->hasSourceText());
-  *worked = false;
-  if (!cx->runtime()->sourceHook.ref() || !ss->sourceRetrievable()) {
+class ScriptSource::LoadSourceMatcher {
+  JSContext* const cx_;
+  ScriptSource* const ss_;
+  bool* const loaded_;
+
+ public:
+  explicit LoadSourceMatcher(JSContext* cx, ScriptSource* ss, bool* loaded)
+      : cx_(cx), ss_(ss), loaded_(loaded) {}
+
+  template <typename Unit>
+  bool operator()(const Compressed<Unit>&) const {
+    return sourceAlreadyLoaded();
+  }
+
+  template <typename Unit>
+  bool operator()(const Uncompressed<Unit>&) const {
+    return sourceAlreadyLoaded();
+  }
+
+  bool operator()(const Missing&) const { return tryLoadingSource(); }
+
+  bool operator()(const BinAST&) const { return tryLoadingSource(); }
+
+ private:
+  bool sourceAlreadyLoaded() const {
+    *loaded_ = true;
     return true;
   }
-  char16_t* src = nullptr;
-  size_t length;
-  if (!cx->runtime()->sourceHook->load(cx, ss->filename(), &src, &length)) {
-    return false;
-  }
-  if (!src) {
+
+  bool tryLoadingSource() const {
+    // Establish the default outcome first.
+    *loaded_ = false;
+
+    if (!cx_->runtime()->sourceHook.ref() || !ss_->sourceRetrievable()) {
+      return true;
+    }
+
+    char16_t* src = nullptr;
+    size_t length;
+    if (!cx_->runtime()->sourceHook->load(cx_, ss_->filename(), &src,
+                                          &length)) {
+      return false;
+    }
+    if (!src) {
+      return true;
+    }
+
+    // XXX On-demand source is currently only UTF-16.  Perhaps it should be
+    //     changed to UTF-8, or UTF-8 be allowed in addition to UTF-16?
+    if (!ss_->setRetrievedSource(cx_, EntryUnits<char16_t>(src), length)) {
+      return false;
+    }
+
+    *loaded_ = true;
     return true;
   }
-
-  // XXX On-demand source is currently only UTF-16.  Perhaps it should be
-  //     changed to UTF-8, or UTF-8 be allowed in addition to UTF-16?
-  if (!ss->setSource(cx, EntryUnits<char16_t>(src), length)) {
-    return false;
-  }
-
-  *worked = true;
-  return true;
+};
+
+/* static */
+bool ScriptSource::loadSource(JSContext* cx, ScriptSource* ss, bool* loaded) {
+  return ss->data.match(LoadSourceMatcher(cx, ss, loaded));
 }
 
 /* static */
 JSFlatString* JSScript::sourceData(JSContext* cx, HandleScript script) {
   MOZ_ASSERT(script->scriptSource()->hasSourceText());
   return script->scriptSource()->substring(cx, script->sourceStart(),
                                            script->sourceEnd());
 }
@@ -2051,39 +2088,43 @@ JSFlatString* ScriptSource::functionBody
 
   size_t start =
       parameterListEnd_ + (sizeof(FunctionConstructorMedialSigils) - 1);
   size_t stop = length() - (sizeof(FunctionConstructorFinalBrace) - 1);
   return substring(cx, start, stop);
 }
 
 template <typename Unit>
-void ScriptSource::setSource(
-    typename SourceTypeTraits<Unit>::SharedImmutableString uncompressed) {
+MOZ_MUST_USE bool ScriptSource::setUncompressedSourceHelper(
+    JSContext* cx, EntryUnits<Unit>&& source, size_t length) {
   MOZ_ASSERT(data.is<Missing>());
-  data = SourceType(Uncompressed<Unit>(std::move(uncompressed)));
-}
-
-template <typename Unit>
-MOZ_MUST_USE bool ScriptSource::setSource(JSContext* cx,
-                                          EntryUnits<Unit>&& source,
-                                          size_t length) {
+
   auto& cache = cx->zone()->runtimeFromAnyThread()->sharedImmutableStrings();
 
   auto uniqueChars = SourceTypeTraits<Unit>::toCacheable(std::move(source));
   auto deduped = cache.getOrCreate(std::move(uniqueChars), length);
   if (!deduped) {
     ReportOutOfMemory(cx);
     return false;
   }
 
-  setSource<Unit>(std::move(*deduped));
+  data = SourceType(Uncompressed<Unit>(std::move(*deduped)));
   return true;
 }
 
+template <typename Unit>
+MOZ_MUST_USE bool ScriptSource::setRetrievedSource(JSContext* cx,
+                                                   EntryUnits<Unit>&& source,
+                                                   size_t length) {
+  MOZ_ASSERT(sourceRetrievable_);
+  MOZ_ASSERT(data.is<Missing>(),
+             "retrievable source must be indicated as missing");
+  return setUncompressedSourceHelper(cx, std::move(source), length);
+}
+
 #if defined(JS_BUILD_BINAST)
 
 MOZ_MUST_USE bool ScriptSource::setBinASTSourceCopy(JSContext* cx,
                                                     const uint8_t* buf,
                                                     size_t len) {
   auto& cache = cx->zone()->runtimeFromAnyThread()->sharedImmutableStrings();
   auto deduped = cache.getOrCreate(reinterpret_cast<const char*>(buf), len);
   if (!deduped) {
@@ -2156,73 +2197,95 @@ bool ScriptSource::tryCompressOffThread(
   if (!task) {
     ReportOutOfMemory(cx);
     return false;
   }
   return EnqueueOffThreadCompression(cx, std::move(task));
 }
 
 template <typename Unit>
-void ScriptSource::setCompressedSource(SharedImmutableString raw,
-                                       size_t uncompressedLength) {
-  MOZ_ASSERT(data.is<Missing>() || hasUncompressedSource());
-  MOZ_ASSERT_IF(hasUncompressedSource(), length() == uncompressedLength);
+void ScriptSource::convertToCompressedSource(SharedImmutableString compressed,
+                                             size_t uncompressedLength) {
+  MOZ_ASSERT(data.is<Uncompressed<Unit>>(),
+             "should only be converting uncompressed source to compressed "
+             "source identically encoded");
+  MOZ_ASSERT(length() == uncompressedLength);
 
   if (pinnedUnitsStack_) {
     MOZ_ASSERT(pendingCompressed_.empty());
-    pendingCompressed_.construct<Compressed<Unit>>(std::move(raw),
+    pendingCompressed_.construct<Compressed<Unit>>(std::move(compressed),
                                                    uncompressedLength);
   } else {
-    data = SourceType(Compressed<Unit>(std::move(raw), uncompressedLength));
+    data =
+        SourceType(Compressed<Unit>(std::move(compressed), uncompressedLength));
   }
 }
 
 template <typename Unit>
-MOZ_MUST_USE bool ScriptSource::setCompressedSource(JSContext* cx,
-                                                    UniqueChars&& compressed,
-                                                    size_t rawLength,
-                                                    size_t sourceLength) {
-  MOZ_ASSERT(compressed);
+MOZ_MUST_USE bool ScriptSource::initializeWithCompressedSource(
+    JSContext* cx, UniqueChars&& compressed, size_t rawLength,
+    size_t sourceLength) {
+  MOZ_ASSERT(data.is<Missing>(), "shouldn't be double-initializing");
+  MOZ_ASSERT(compressed != nullptr);
 
   auto& cache = cx->zone()->runtimeFromAnyThread()->sharedImmutableStrings();
   auto deduped = cache.getOrCreate(std::move(compressed), rawLength);
   if (!deduped) {
     ReportOutOfMemory(cx);
     return false;
   }
 
-  setCompressedSource<Unit>(std::move(*deduped), sourceLength);
+  MOZ_ASSERT(pinnedUnitsStack_ == nullptr,
+             "shouldn't be initializing a ScriptSource while its characters "
+             "are pinned -- that only makes sense with a ScriptSource actively "
+             "being inspected");
+  data = SourceType(Compressed<Unit>(std::move(*deduped), sourceLength));
+
   return true;
 }
 
 template <typename Unit>
-bool ScriptSource::setSourceCopy(JSContext* cx, SourceText<Unit>& srcBuf) {
-  MOZ_ASSERT(!hasSourceText());
+bool ScriptSource::assignSource(JSContext* cx,
+                                const ReadOnlyCompileOptions& options,
+                                SourceText<Unit>& srcBuf) {
+  MOZ_ASSERT(data.is<Missing>(),
+             "source assignment should only occur on fresh ScriptSources");
+
+  if (cx->realm()->behaviors().discardSource()) {
+    return true;
+  }
+
+  if (options.sourceIsLazy) {
+    sourceRetrievable_ = true;
+    return true;
+  }
 
   JSRuntime* runtime = cx->zone()->runtimeFromAnyThread();
   auto& cache = runtime->sharedImmutableStrings();
   auto deduped = cache.getOrCreate(srcBuf.get(), srcBuf.length(), [&srcBuf]() {
     using CharT = typename SourceTypeTraits<Unit>::CharT;
     return srcBuf.ownsUnits()
                ? UniquePtr<CharT[], JS::FreePolicy>(srcBuf.takeChars())
                : DuplicateString(srcBuf.get(), srcBuf.length());
   });
   if (!deduped) {
     ReportOutOfMemory(cx);
     return false;
   }
 
-  setSource<Unit>(std::move(*deduped));
+  data = SourceType(Uncompressed<Unit>(std::move(*deduped)));
   return true;
 }
 
-template bool ScriptSource::setSourceCopy(JSContext* cx,
-                                          SourceText<char16_t>& srcBuf);
-template bool ScriptSource::setSourceCopy(JSContext* cx,
-                                          SourceText<Utf8Unit>& srcBuf);
+template bool ScriptSource::assignSource(JSContext* cx,
+                                         const ReadOnlyCompileOptions& options,
+                                         SourceText<char16_t>& srcBuf);
+template bool ScriptSource::assignSource(JSContext* cx,
+                                         const ReadOnlyCompileOptions& options,
+                                         SourceText<Utf8Unit>& srcBuf);
 
 void ScriptSource::trace(JSTracer* trc) {
 #ifdef JS_BUILD_BINAST
   if (binASTMetadata_) {
     binASTMetadata_->trace(trc);
   }
 #else
   MOZ_ASSERT(!binASTMetadata_);
@@ -2344,25 +2407,25 @@ void SourceCompressionTask::runTask() {
   }
 
   ScriptSource* source = sourceHolder_.get();
   MOZ_ASSERT(source->hasUncompressedSource());
 
   source->performTaskWork(this);
 }
 
-void ScriptSource::setCompressedSourceFromTask(
+void ScriptSource::convertToCompressedSourceFromTask(
     SharedImmutableString compressed) {
-  data.match(SetCompressedSourceFromTask(this, compressed));
+  data.match(ConvertToCompressedSourceFromTask(this, compressed));
 }
 
 void SourceCompressionTask::complete() {
   if (!shouldCancel() && resultString_.isSome()) {
     ScriptSource* source = sourceHolder_.get();
-    source->setCompressedSourceFromTask(std::move(*resultString_));
+    source->convertToCompressedSourceFromTask(std::move(*resultString_));
   }
 }
 
 void ScriptSource::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
                                           JS::ScriptSourceInfo* info) const {
   info->misc += mallocSizeOf(this) + mallocSizeOf(filename_.get()) +
                 mallocSizeOf(introducerFilename_.get());
   info->numScripts++;
@@ -2429,16 +2492,23 @@ bool ScriptSource::xdrFinalizeEncoder(JS
 
   auto cleanup = mozilla::MakeScopeExit([&] { xdrEncoder_.reset(nullptr); });
 
   XDRResult res = xdrEncoder_->linearize(buffer);
   return res.isOk();
 }
 
 template <typename Unit>
+MOZ_MUST_USE bool ScriptSource::initializeUncompressedSource(
+    JSContext* cx, EntryUnits<Unit>&& source, size_t length) {
+  MOZ_ASSERT(data.is<Missing>(), "must be initializing a fresh ScriptSource");
+  return setUncompressedSourceHelper(cx, std::move(source), length);
+}
+
+template <typename Unit>
 struct SourceDecoder {
   XDRState<XDR_DECODE>* const xdr_;
   ScriptSource* const scriptSource_;
   const uint32_t uncompressedLength_;
 
  public:
   SourceDecoder(XDRState<XDR_DECODE>* xdr, ScriptSource* scriptSource,
                 uint32_t uncompressedLength)
@@ -2450,18 +2520,18 @@ struct SourceDecoder {
     auto sourceUnits =
         xdr_->cx()->make_pod_array<Unit>(Max<size_t>(uncompressedLength_, 1));
     if (!sourceUnits) {
       return xdr_->fail(JS::TranscodeResult_Throw);
     }
 
     MOZ_TRY(xdr_->codeChars(sourceUnits.get(), uncompressedLength_));
 
-    if (!scriptSource_->setSource(xdr_->cx(), std::move(sourceUnits),
-                                  uncompressedLength_)) {
+    if (!scriptSource_->initializeUncompressedSource(
+            xdr_->cx(), std::move(sourceUnits), uncompressedLength_)) {
       return xdr_->fail(JS::TranscodeResult_Throw);
     }
 
     return Ok();
   }
 };
 
 namespace js {
@@ -2611,20 +2681,20 @@ XDRResult ScriptSource::XDR(XDRState<mod
           // Compressed data is always single-byte chars.
           auto bytes =
               xdr->cx()->template make_pod_array<char>(compressedLength);
           if (!bytes) {
             return xdr->fail(JS::TranscodeResult_Throw);
           }
           MOZ_TRY(xdr->codeBytes(bytes.get(), compressedLength));
 
-          if (!(srcCharSize == 1 ? ss->setCompressedSource<Utf8Unit>(
+          if (!(srcCharSize == 1 ? ss->initializeWithCompressedSource<Utf8Unit>(
                                        xdr->cx(), std::move(bytes),
                                        compressedLength, uncompressedLength)
-                                 : ss->setCompressedSource<char16_t>(
+                                 : ss->initializeWithCompressedSource<char16_t>(
                                        xdr->cx(), std::move(bytes),
                                        compressedLength, uncompressedLength))) {
             return xdr->fail(JS::TranscodeResult_Throw);
           }
         } else {
           void* bytes = srcCharSize == 1 ? ss->compressedData<Utf8Unit>()
                                          : ss->compressedData<char16_t>();
           MOZ_TRY(xdr->codeBytes(bytes, compressedLength));
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -541,17 +541,17 @@ class ScriptSource {
   };
 
   using SourceType =
       mozilla::Variant<Compressed<mozilla::Utf8Unit>,
                        Uncompressed<mozilla::Utf8Unit>, Compressed<char16_t>,
                        Uncompressed<char16_t>, Missing, BinAST>;
   SourceType data;
 
-  // If the GC attempts to call setCompressedSource with PinnedUnits
+  // If the GC attempts to call convertToCompressedSource with PinnedUnits
   // present, the first PinnedUnits (that is, bottom of the stack) will set
   // the compressed chars upon destruction.
   PinnedUnitsBase* pinnedUnitsStack_;
   mozilla::MaybeOneOf<Compressed<mozilla::Utf8Unit>, Compressed<char16_t>>
       pendingCompressed_;
 
   // The filename of this script.
   UniqueChars filename_;
@@ -686,20 +686,32 @@ class ScriptSource {
       const mozilla::Maybe<uint32_t>& parameterListEnd = mozilla::Nothing());
 
   /**
    * The minimum script length (in code units) necessary for a script to be
    * eligible to be compressed.
    */
   static constexpr size_t MinimumCompressibleLength = 256;
 
+ private:
+  class LoadSourceMatcher;
+
+ public:
+  // Attempt to load usable source for |ss| -- source text on which substring
+  // operations and the like can be performed.  On success return true and set
+  // |*loaded| to indicate whether usable source could be loaded; otherwise
+  // return false.
+  static bool loadSource(JSContext* cx, ScriptSource* ss, bool* loaded);
+
+  // Assign source data from |srcBuf| to this recently-created |ScriptSource|.
   template <typename Unit>
-  MOZ_MUST_USE bool setSourceCopy(JSContext* cx, JS::SourceText<Unit>& srcBuf);
-
-  void setSourceRetrievable() { sourceRetrievable_ = true; }
+  MOZ_MUST_USE bool assignSource(JSContext* cx,
+                                 const JS::ReadOnlyCompileOptions& options,
+                                 JS::SourceText<Unit>& srcBuf);
+
   bool sourceRetrievable() const { return sourceRetrievable_; }
   bool hasSourceText() const {
     return hasUncompressedSource() || hasCompressedSource();
   }
   bool hasBinASTSource() const { return data.is<BinAST>(); }
 
   void setBinASTSourceMetadata(frontend::BinASTSourceMetadata* metadata) {
     MOZ_ASSERT(hasBinASTSource());
@@ -941,35 +953,55 @@ class ScriptSource {
                                     size_t start, size_t stop);
 
   bool isFunctionBody() { return parameterListEnd_ != 0; }
   JSFlatString* functionBodyString(JSContext* cx);
 
   void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
                               JS::ScriptSourceInfo* info) const;
 
+ private:
+  // Overwrites |data| with the uncompressed data from |source|.  (This function
+  // currently asserts |data.is<Missing>()|, but callers should assert it as
+  // well, because this function shortly will be used in other cases and the
+  // assertion will have to be removed.)
   template <typename Unit>
-  MOZ_MUST_USE bool setSource(JSContext* cx, EntryUnits<Unit>&& source,
-                              size_t length);
-
+  MOZ_MUST_USE bool setUncompressedSourceHelper(JSContext* cx,
+                                                EntryUnits<Unit>&& source,
+                                                size_t length);
+
+ public:
+  // Initialize a fresh |ScriptSource| with uncompressed source.
   template <typename Unit>
-  void setSource(
-      typename SourceTypeTraits<Unit>::SharedImmutableString uncompressed);
+  MOZ_MUST_USE bool initializeUncompressedSource(JSContext* cx,
+                                                 EntryUnits<Unit>&& source,
+                                                 size_t length);
+
+  // Set the retrieved source for a |ScriptSource| whose source was recorded as
+  // missing but retrievable.
+  template <typename Unit>
+  MOZ_MUST_USE bool setRetrievedSource(JSContext* cx, EntryUnits<Unit>&& source,
+                                       size_t length);
 
   MOZ_MUST_USE bool tryCompressOffThread(JSContext* cx);
 
-  // The Unit parameter determines which type of compressed source is
-  // recorded, but raw compressed source is always single-byte.
+  // Convert this ScriptSource from storing uncompressed source of the given
+  // type, to storing compressed source.  (Raw compressed source is always
+  // single-byte; |Unit| just records the encoding of the uncompressed source.)
   template <typename Unit>
-  void setCompressedSource(SharedImmutableString compressed,
-                           size_t sourceLength);
-
+  void convertToCompressedSource(SharedImmutableString compressed,
+                                 size_t sourceLength);
+
+  // Initialize a fresh ScriptSource as containing compressed source of the
+  // indicated original encoding.
   template <typename Unit>
-  MOZ_MUST_USE bool setCompressedSource(JSContext* cx, UniqueChars&& raw,
-                                        size_t rawLength, size_t sourceLength);
+  MOZ_MUST_USE bool initializeWithCompressedSource(JSContext* cx,
+                                                   UniqueChars&& raw,
+                                                   size_t rawLength,
+                                                   size_t sourceLength);
 
 #if defined(JS_BUILD_BINAST)
 
   /*
    * Do not take ownership of the given `buf`. Store the canonical, shared
    * and de-duplicated version. If there is no extant shared version of
    * `buf`, make a copy.
    */
@@ -985,28 +1017,28 @@ class ScriptSource {
 
   const uint8_t* binASTSource();
 
 #endif /* JS_BUILD_BINAST */
 
  private:
   void performTaskWork(SourceCompressionTask* task);
 
-  struct SetCompressedSourceFromTask {
+  struct ConvertToCompressedSourceFromTask {
     ScriptSource* const source_;
     SharedImmutableString& compressed_;
 
-    SetCompressedSourceFromTask(ScriptSource* source,
-                                SharedImmutableString& compressed)
+    ConvertToCompressedSourceFromTask(ScriptSource* source,
+                                      SharedImmutableString& compressed)
         : source_(source), compressed_(compressed) {}
 
     template <typename Unit>
     void operator()(const Uncompressed<Unit>&) {
-      source_->setCompressedSource<Unit>(std::move(compressed_),
-                                         source_->length());
+      source_->convertToCompressedSource<Unit>(std::move(compressed_),
+                                               source_->length());
     }
 
     template <typename Unit>
     void operator()(const Compressed<Unit>&) {
       MOZ_CRASH(
           "can't set compressed source when source is already "
           "compressed -- ScriptSource::tryCompressOffThread "
           "shouldn't have queued up this task?");
@@ -1021,17 +1053,17 @@ class ScriptSource {
     void operator()(const Missing&) {
       MOZ_CRASH(
           "doesn't make sense to set compressed source for "
           "missing source -- ScriptSource::tryCompressOffThread "
           "shouldn't have queued up this task?");
     }
   };
 
-  void setCompressedSourceFromTask(SharedImmutableString compressed);
+  void convertToCompressedSourceFromTask(SharedImmutableString compressed);
 
  private:
   // It'd be better to make this function take <XDRMode, Unit>, as both
   // specializations of this function contain nested Unit-parametrized
   // helper classes that do everything the function needs to do.  But then
   // we'd need template function partial specialization to hold XDRMode
   // constant while varying Unit, so that idea's no dice.
   template <XDRMode mode>
@@ -2489,18 +2521,16 @@ class JSScript : public js::gc::TenuredC
   // directly, via lazy arguments or a rest parameter.
   bool mayReadFrameArgsDirectly();
 
   static JSFlatString* sourceData(JSContext* cx, JS::HandleScript script);
 
   MOZ_MUST_USE bool appendSourceDataForToString(JSContext* cx,
                                                 js::StringBuffer& buf);
 
-  static bool loadSource(JSContext* cx, js::ScriptSource* ss, bool* worked);
-
   void setSourceObject(js::ScriptSourceObject* object);
   js::ScriptSourceObject* sourceObject() const { return sourceObject_; }
   js::ScriptSource* scriptSource() const;
   js::ScriptSource* maybeForwardedScriptSource() const;
 
   void setDefaultClassConstructorSpan(js::ScriptSourceObject* sourceObject,
                                       uint32_t start, uint32_t end,
                                       unsigned line, unsigned column);
--- a/js/src/wasm/AsmJS.cpp
+++ b/js/src/wasm/AsmJS.cpp
@@ -6881,18 +6881,18 @@ static bool HandleInstantiationFailure(J
   if (cx->isExceptionPending()) {
     return false;
   }
 
   ScriptSource* source = metadata.scriptSource.get();
 
   // Source discarding is allowed to affect JS semantics because it is never
   // enabled for normal JS content.
-  bool haveSource = source->hasSourceText();
-  if (!haveSource && !JSScript::loadSource(cx, source, &haveSource)) {
+  bool haveSource;
+  if (!ScriptSource::loadSource(cx, source, &haveSource)) {
     return false;
   }
   if (!haveSource) {
     JS_ReportErrorASCII(cx,
                         "asm.js link failure with source discarding enabled");
     return false;
   }
 
@@ -7213,18 +7213,18 @@ JSString* js::AsmJSModuleToString(JSCont
   ScriptSource* source = metadata.scriptSource.get();
 
   JSStringBuilder out(cx);
 
   if (isToSource && fun->isLambda() && !out.append("(")) {
     return nullptr;
   }
 
-  bool haveSource = source->hasSourceText();
-  if (!haveSource && !JSScript::loadSource(cx, source, &haveSource)) {
+  bool haveSource;
+  if (!ScriptSource::loadSource(cx, source, &haveSource)) {
     return nullptr;
   }
 
   if (!haveSource) {
     if (!out.append("function ")) {
       return nullptr;
     }
     if (fun->explicitName() && !out.append(fun->explicitName())) {
@@ -7264,18 +7264,18 @@ JSString* js::AsmJSFunctionToString(JSCo
 
   ScriptSource* source = metadata.scriptSource.get();
   JSStringBuilder out(cx);
 
   if (!out.append("function ")) {
     return nullptr;
   }
 
-  bool haveSource = source->hasSourceText();
-  if (!haveSource && !JSScript::loadSource(cx, source, &haveSource)) {
+  bool haveSource;
+  if (!ScriptSource::loadSource(cx, source, &haveSource)) {
     return nullptr;
   }
 
   if (!haveSource) {
     // asm.js functions can't be anonymous
     MOZ_ASSERT(fun->explicitName());
     if (!out.append(fun->explicitName())) {
       return nullptr;
--- a/layout/svg/nsSVGGradientFrame.cpp
+++ b/layout/svg/nsSVGGradientFrame.cpp
@@ -572,23 +572,23 @@ already_AddRefed<gfxPattern> nsSVGRadial
     // The focal point (fFx and fFy) must be clamped to be *inside* - not on -
     // the circumference of the gradient or we'll get rendering anomalies. We
     // calculate the distance from the focal point to the gradient center and
     // make sure it is *less* than the gradient radius.
     // 1/128 is the limit of the fractional part of cairo's 24.8 fixed point
     // representation divided by 2 to ensure that we get different cairo
     // fractions
     double dMax = std::max(0.0, r - 1.0 / 128);
-    float dx = fx - cx;
-    float dy = fy - cy;
-    double d = sqrt((dx * dx) + (dy * dy));
+    double dx = fx - cx;
+    double dy = fy - cy;
+    double d = std::sqrt((dx * dx) + (dy * dy));
     if (d > dMax) {
-      double angle = atan2(dy, dx);
-      fx = (float)(dMax * cos(angle)) + cx;
-      fy = (float)(dMax * sin(angle)) + cy;
+      double angle = std::atan2(dy, dx);
+      fx = float(dMax * std::cos(angle)) + cx;
+      fy = float(dMax * std::sin(angle)) + cy;
     }
   }
 
   RefPtr<gfxPattern> pattern = new gfxPattern(fx, fy, fr, cx, cy, r);
   return pattern.forget();
 }
 
 // -------------------------------------------------------------------------
--- a/media/libcubeb/moz.yaml
+++ b/media/libcubeb/moz.yaml
@@ -14,10 +14,10 @@ bugzilla:
 origin:
   name: "cubeb"
   description: "Cross platform audio library"
 
   url: "https://github.com/kinetiknz/cubeb"
   license: "ISC"
 
   # update.sh will update this value
-  release: "3570749942a4fea2b7e372833cb8374ce42cf389 (2019-04-24 13:04:01 +1200)"
+  release: "64aa80f330a3dc510b1e3ac0e92cc6bed129a9a6 (2019-04-25 17:32:33 +0200)"
 
--- a/media/libcubeb/src/cubeb_wasapi.cpp
+++ b/media/libcubeb/src/cubeb_wasapi.cpp
@@ -480,26 +480,22 @@ public:
   }
 
   HRESULT STDMETHODCALLTYPE
   OnDeviceStateChanged(LPCWSTR device_id, DWORD new_state)
   {
     XASSERT(cubeb_context->output_collection_changed_callback ||
             cubeb_context->input_collection_changed_callback);
     LOG("collection: Audio device state changed, id = %S, state = %lu.", device_id, new_state);
-    if (new_state == DEVICE_STATE_ACTIVE ||
-        new_state == DEVICE_STATE_NOTPRESENT ||
-        new_state == DEVICE_STATE_UNPLUGGED) {
-      EDataFlow flow;
-      HRESULT hr = GetDataFlow(device_id, &flow);
-      if (FAILED(hr)) {
-        return hr;
-      }
-      monitor_notifications.notify(flow);
+    EDataFlow flow;
+    HRESULT hr = GetDataFlow(device_id, &flow);
+    if (FAILED(hr)) {
+      return hr;
     }
+    monitor_notifications.notify(flow);
     return S_OK;
   }
 
   HRESULT STDMETHODCALLTYPE
   OnPropertyValueChanged(LPCWSTR device_id, const PROPERTYKEY key)
   {
     //Audio device property value changed.
     return S_OK;
--- a/toolkit/components/bitsdownload/bits_client/src/in_process/tests.rs
+++ b/toolkit/components/bitsdownload/bits_client/src/in_process/tests.rs
@@ -394,27 +394,42 @@ test! {
         let timeout = 10_000;
 
         let (_, mut monitor) =
             client.start_job(server.format_url(name), name.into(), BitsProxyUsage::Preconfig, interval).unwrap();
 
         // Start the timer now, the initial job creation may be delayed by BITS service startup.
         let start = Instant::now();
 
-        // First immediate report
-        monitor.get_status(timeout).expect("should initially be ok").unwrap();
+        // First report, immediate
+        let report_1 = monitor.get_status(timeout);
+        let elapsed_to_report_1 = start.elapsed();
+        report_1.as_ref().expect("should initially be ok").as_ref().unwrap();
 
         // Transferred notification should come when the job completes in ~250 ms, otherwise we
         // will be stuck until timeout.
-        let status = monitor.get_status(timeout).expect("should get status update").unwrap();
-        assert!(start.elapsed() < Duration::from_millis(9_000));
-        assert_eq!(status.state, BitsJobState::Transferred);
+        let report_2 = monitor.get_status(timeout).expect("should get status update").unwrap();
+        let elapsed_to_report_2 = start.elapsed();
+        assert!(elapsed_to_report_2 < Duration::from_millis(9_000));
+        assert_eq!(report_2.state, BitsJobState::Transferred);
 
         let short_timeout = 500;
-        monitor.get_status(short_timeout).expect_err("should be disconnected");
+        let report_3 = monitor.get_status(short_timeout);
+        let elapsed_to_report_3 = start.elapsed();
+
+        if let Ok(report_3) = report_3 {
+          panic!("should be disconnected\n\
+                  report_1 ({}.{:03}): {:?}\n\
+                  report_2 ({}.{:03}): {:?}\n\
+                  report_3 ({}.{:03}): {:?}",
+                  elapsed_to_report_1.as_secs(), elapsed_to_report_1.subsec_millis(), report_1,
+                  elapsed_to_report_2.as_secs(), elapsed_to_report_2.subsec_millis(), report_2,
+                  elapsed_to_report_3.as_secs(), elapsed_to_report_3.subsec_millis(), report_3,
+                  );
+        }
 
         server.shutdown();
 
         // job will be cancelled by macro
     }
 }
 
 test! {
--- a/toolkit/content/customElements.js
+++ b/toolkit/content/customElements.js
@@ -641,16 +641,17 @@ customElements.setElementCreationCallbac
 });
 
 // For now, don't load any elements in the extension dummy document.
 // We will want to load <browser> when that's migrated (bug 1441935).
 const isDummyDocument = document.documentURI == "chrome://extensions/content/dummy.xul";
 if (!isDummyDocument) {
   for (let script of [
     "chrome://global/content/elements/general.js",
+    "chrome://global/content/elements/button.js",
     "chrome://global/content/elements/checkbox.js",
     "chrome://global/content/elements/menu.js",
     "chrome://global/content/elements/menupopup.js",
     "chrome://global/content/elements/notificationbox.js",
     "chrome://global/content/elements/popupnotification.js",
     "chrome://global/content/elements/radio.js",
     "chrome://global/content/elements/richlistbox.js",
     "chrome://global/content/elements/autocomplete-popup.js",
--- a/toolkit/content/jar.mn
+++ b/toolkit/content/jar.mn
@@ -78,16 +78,17 @@ toolkit.jar:
 *  content/global/bindings/textbox.xml         (widgets/textbox.xml)
    content/global/bindings/timekeeper.js       (widgets/timekeeper.js)
    content/global/bindings/timepicker.js       (widgets/timepicker.js)
    content/global/bindings/toolbarbutton.xml   (widgets/toolbarbutton.xml)
    content/global/bindings/wizard.xml          (widgets/wizard.xml)
    content/global/elements/autocomplete-popup.js              (widgets/autocomplete-popup.js)
    content/global/elements/autocomplete-richlistitem.js       (widgets/autocomplete-richlistitem.js)
    content/global/elements/browser-custom-element.js          (widgets/browser-custom-element.js)
+   content/global/elements/button.js           (widgets/button.js)
    content/global/elements/checkbox.js         (widgets/checkbox.js)
    content/global/elements/datetimebox.js      (widgets/datetimebox.js)
    content/global/elements/findbar.js          (widgets/findbar.js)
    content/global/elements/editor.js           (widgets/editor.js)
    content/global/elements/general.js          (widgets/general.js)
    content/global/elements/menu.js             (widgets/menu.js)
    content/global/elements/menupopup.js        (widgets/menupopup.js)
    content/global/elements/notificationbox.js  (widgets/notificationbox.js)
copy from toolkit/content/widgets/button.xml
copy to toolkit/content/widgets/button.js
--- a/toolkit/content/widgets/button.xml
+++ b/toolkit/content/widgets/button.js
@@ -1,164 +1,70 @@
-<?xml version="1.0"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-
-<bindings id="buttonBindings"
-   xmlns="http://www.mozilla.org/xbl"
-   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-   xmlns:xbl="http://www.mozilla.org/xbl">
+/* 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/. */
 
-  <binding id="button-base" extends="chrome://global/content/bindings/general.xml#basetext">
-    <implementation implements="nsIDOMXULButtonElement">
-      <property name="type"
-                onget="return this.getAttribute('type');"
-                onset="this.setAttribute('type', val); return val;"/>
-
-      <property name="dlgType"
-                onget="return this.getAttribute('dlgtype');"
-                onset="this.setAttribute('dlgtype', val); return val;"/>
-
-      <property name="group"
-                onget="return this.getAttribute('group');"
-                onset="this.setAttribute('group', val); return val;"/>
+"use strict";
 
-      <property name="open" onget="return this.hasAttribute('open');">
-        <setter><![CDATA[
-          if (this.hasMenu()) {
-            this.openMenu(val);
-          } else if (val) {
-            // Fall back to just setting the attribute
-            this.setAttribute("open", "true");
-          } else {
-            this.removeAttribute("open");
-          }
-          return val;
-        ]]></setter>
-      </property>
-
-      <property name="checked" onget="return this.hasAttribute('checked');">
-        <setter><![CDATA[
-          if (this.type == "radio" && val) {
-            var sibs = this.parentNode.getElementsByAttribute("group", this.group);
-            for (var i = 0; i < sibs.length; ++i)
-              sibs[i].removeAttribute("checked");
-          }
-
-          if (val)
-            this.setAttribute("checked", "true");
-          else
-            this.removeAttribute("checked");
-
-          return val;
-        ]]></setter>
-      </property>
+// This is loaded into all XUL windows. Wrap in a block to prevent
+// leaking to window scope.
+{
+  class MozButtonBase extends MozElements.BaseText {
+    constructor() {
+      super();
 
-      <method name ="filterButtons">
-        <parameter name="node"/>
-        <body>
-        <![CDATA[
-          // if the node isn't visible, don't descend into it.
-          var cs = node.ownerGlobal.getComputedStyle(node);
-          if (cs.visibility != "visible" || cs.display == "none") {
-            return NodeFilter.FILTER_REJECT;
-          }
-          // but it may be a popup element, in which case we look at "state"...
-          if (cs.display == "-moz-popup" && node.state != "open") {
-            return NodeFilter.FILTER_REJECT;
-          }
-          // OK - the node seems visible, so it is a candidate.
-          if (node.localName == "button" && node.accessKey && !node.disabled)
-            return NodeFilter.FILTER_ACCEPT;
-          return NodeFilter.FILTER_SKIP;
-        ]]>
-        </body>
-      </method>
+      /**
+       * While it would seem we could do this by handling oncommand, we can't
+       * because any external oncommand handlers might get called before ours,
+       * and then they would see the incorrect value of checked. Additionally
+       * a command attribute would redirect the command events anyway.
+       */
+      this.addEventListener("click", (event) => {
+        if (event.button != 0) {
+          return;
+        }
+        this._handleClick();
+      });
 
-      <method name="fireAccessKeyButton">
-        <parameter name="aSubtree"/>
-        <parameter name="aAccessKeyLower"/>
-        <body>
-        <![CDATA[
-          var iterator = aSubtree.ownerDocument.createTreeWalker(aSubtree,
-                                                                 NodeFilter.SHOW_ELEMENT,
-                                                                 this.filterButtons);
-          while (iterator.nextNode()) {
-            var test = iterator.currentNode;
-            if (test.accessKey.toLowerCase() == aAccessKeyLower &&
-                !test.disabled && !test.collapsed && !test.hidden) {
-              test.focus();
-              test.click();
-              return true;
-            }
-          }
-          return false;
-        ]]>
-        </body>
-      </method>
-
-      <method name="_handleClick">
-        <body>
-        <![CDATA[
-          if (!this.disabled) {
-            if (this.type == "checkbox") {
-              this.checked = !this.checked;
-            } else if (this.type == "radio") {
-              this.checked = true;
-            }
-          }
-        ]]>
-        </body>
-      </method>
-    </implementation>
-
-    <handlers>
-      <!-- While it would seem we could do this by handling oncommand, we can't
-           because any external oncommand handlers might get called before ours,
-           and then they would see the incorrect value of checked. Additionally
-           a command attribute would redirect the command events anyway.-->
-      <handler event="click" button="0" action="this._handleClick();"/>
-      <handler event="keypress" key=" ">
-      <![CDATA[
+      this.addEventListener("keypress", (event) => {
+        if (event.key != " ") {
+          return;
+        }
         this._handleClick();
         // Prevent page from scrolling on the space key.
         event.preventDefault();
-      ]]>
-      </handler>
+      });
 
-      <handler event="keypress">
-      <![CDATA[
+      this.addEventListener("keypress", (event) => {
         if (this.hasMenu()) {
           if (this.open)
             return;
         } else {
           if (event.keyCode == KeyEvent.DOM_VK_UP ||
               (event.keyCode == KeyEvent.DOM_VK_LEFT &&
-                document.defaultView.getComputedStyle(this.parentNode)
-                        .direction == "ltr") ||
+               document.defaultView.getComputedStyle(this.parentNode)
+                       .direction == "ltr") ||
               (event.keyCode == KeyEvent.DOM_VK_RIGHT &&
-                document.defaultView.getComputedStyle(this.parentNode)
-                        .direction == "rtl")) {
-            event.preventDefault();
-            window.document.commandDispatcher.rewindFocus();
-            return;
+               document.defaultView.getComputedStyle(this.parentNode)
+                       .direction == "rtl")) {
+                         event.preventDefault();
+                         window.document.commandDispatcher.rewindFocus();
+                         return;
           }
 
           if (event.keyCode == KeyEvent.DOM_VK_DOWN ||
               (event.keyCode == KeyEvent.DOM_VK_RIGHT &&
-                document.defaultView.getComputedStyle(this.parentNode)
-                        .direction == "ltr") ||
+               document.defaultView.getComputedStyle(this.parentNode)
+                       .direction == "ltr") ||
               (event.keyCode == KeyEvent.DOM_VK_LEFT &&
-                document.defaultView.getComputedStyle(this.parentNode)
-                        .direction == "rtl")) {
-            event.preventDefault();
-            window.document.commandDispatcher.advanceFocus();
-            return;
+               document.defaultView.getComputedStyle(this.parentNode)
+                       .direction == "rtl")) {
+                         event.preventDefault();
+                         window.document.commandDispatcher.advanceFocus();
+                         return;
           }
         }
 
         if (event.keyCode || event.charCode <= 32 || event.altKey ||
             event.ctrlKey || event.metaKey)
           return; // No printable char pressed, not a potential accesskey
 
         // Possible accesskey pressed
@@ -169,64 +75,217 @@
           this.click();
           return;
         }
 
         // Search for accesskey in the list of buttons for this doc and each subdoc
         // Get the buttons for the main document and all sub-frames
         for (var frameCount = -1; frameCount < window.top.frames.length; frameCount++) {
           var doc = (frameCount == -1) ? window.top.document :
-            window.top.frames[frameCount].document;
+                    window.top.frames[frameCount].document;
           if (this.fireAccessKeyButton(doc.documentElement, charPressedLower))
             return;
         }
 
         // Test anonymous buttons
         var dlg = window.top.document;
         var buttonBox = dlg.getAnonymousElementByAttribute(dlg.documentElement,
-                                                         "anonid", "buttons");
+                                                           "anonid", "buttons");
         if (buttonBox)
           this.fireAccessKeyButton(buttonBox, charPressedLower);
-      ]]>
-      </handler>
-    </handlers>
-  </binding>
+      });
+    }
+
+    set type(val) {
+      this.setAttribute("type", val);
+      return val;
+    }
+
+    get type() {
+      return this.getAttribute("type");
+    }
+
+    set disabled(val) {
+      if (val) {
+        this.setAttribute("disabled", "true");
+      } else {
+        this.removeAttribute("disabled");
+      }
+      return val;
+    }
+
+    get disabled() {
+      return (this.getAttribute("disabled") == "true");
+    }
+
+    set dlgType(val) {
+      this.setAttribute("dlgtype", val);
+      return val;
+    }
+
+    get dlgType() {
+      return this.getAttribute("dlgtype");
+    }
+
+    set group(val) {
+      this.setAttribute("group", val);
+      return val;
+    }
+
+    get group() {
+      return this.getAttribute("group");
+    }
 
-  <binding id="button"
-           extends="chrome://global/content/bindings/button.xml#button-base">
-    <content>
-      <children includes="observes|template|menupopup|panel|tooltip"/>
-      <xul:hbox class="box-inherit button-box" xbl:inherits="align,dir,pack,orient"
-                align="center" pack="center" flex="1" anonid="button-box">
-        <xul:image class="button-icon" xbl:inherits="src=image"/>
-        <xul:label class="button-text" xbl:inherits="value=label,accesskey,crop,highlightable"/>
-        <xul:label class="button-highlightable-text" xbl:inherits="xbl:text=label,accesskey,crop,highlightable"/>
-      </xul:hbox>
-    </content>
-  </binding>
+    set open(val) {
+      if (this.hasMenu()) {
+        this.openMenu(val);
+      } else if (val) {
+        // Fall back to just setting the attribute
+        this.setAttribute("open", "true");
+      } else {
+        this.removeAttribute("open");
+      }
+      return val;
+    }
+
+    get open() {
+      return this.hasAttribute("open");
+    }
+
+    set checked(val) {
+      if (this.type == "radio" && val) {
+        var sibs = this.parentNode.getElementsByAttribute("group", this.group);
+        for (var i = 0; i < sibs.length; ++i)
+          sibs[i].removeAttribute("checked");
+      }
+
+      if (val)
+        this.setAttribute("checked", "true");
+      else
+        this.removeAttribute("checked");
+
+      return val;
+    }
+
+    get checked() {
+      return this.hasAttribute("checked");
+    }
+
+    filterButtons(node) {
+      // if the node isn't visible, don't descend into it.
+      var cs = node.ownerGlobal.getComputedStyle(node);
+      if (cs.visibility != "visible" || cs.display == "none") {
+        return NodeFilter.FILTER_REJECT;
+      }
+      // but it may be a popup element, in which case we look at "state"...
+      if (cs.display == "-moz-popup" && node.state != "open") {
+        return NodeFilter.FILTER_REJECT;
+      }
+      // OK - the node seems visible, so it is a candidate.
+      if (node.localName == "button" && node.accessKey && !node.disabled)
+        return NodeFilter.FILTER_ACCEPT;
+      return NodeFilter.FILTER_SKIP;
+    }
 
-  <binding id="menu"
-           extends="chrome://global/content/bindings/button.xml#button">
-    <content>
-      <children includes="observes|template|menupopup|panel|tooltip"/>
-      <xul:hbox class="box-inherit button-box" xbl:inherits="align,dir,pack,orient"
-                align="center" pack="center" flex="1">
-        <xul:hbox class="box-inherit" xbl:inherits="align,dir,pack,orient"
-                  align="center" pack="center" flex="1">
-          <xul:image class="button-icon" xbl:inherits="src=image"/>
-          <xul:label class="button-text" xbl:inherits="value=label,accesskey,crop"/>
-        </xul:hbox>
-        <xul:dropmarker class="button-menu-dropmarker" xbl:inherits="open,disabled,label"/>
-      </xul:hbox>
-    </content>
+    fireAccessKeyButton(aSubtree, aAccessKeyLower) {
+      var iterator = aSubtree.ownerDocument.createTreeWalker(aSubtree,
+                                                             NodeFilter.SHOW_ELEMENT,
+                                                             this.filterButtons);
+      while (iterator.nextNode()) {
+        var test = iterator.currentNode;
+        if (test.accessKey.toLowerCase() == aAccessKeyLower &&
+            !test.disabled && !test.collapsed && !test.hidden) {
+              test.focus();
+              test.click();
+              return true;
+        }
+      }
+      return false;
+    }
+
+    _handleClick() {
+      if (!this.disabled) {
+        if (this.type == "checkbox") {
+          this.checked = !this.checked;
+        } else if (this.type == "radio") {
+          this.checked = true;
+        }
+      }
+    }
+  }
+
+  MozXULElement.implementCustomInterface(MozButtonBase, [Ci.nsIDOMXULButtonElement]);
+
+  class MozButton extends MozButtonBase {
+    constructor() {
+      super();
+      this.hasConnected = false;
+    }
+
+    static get inheritedAttributes() {
+      return {
+        ".box-inherit": "align,dir,pack,orient",
+        ".button-icon": "src=image",
+        ".button-text": "value=label,accesskey,crop",
+        ".button-menu-dropmarker": "open,disabled,label",
+      };
+    }
+
+    get icon() {
+      return this.querySelector(".button-icon");
+    }
 
-    <handlers>
-      <handler event="keypress" keycode="VK_RETURN" action="this.open = true;"/>
-      <handler event="keypress" key=" ">
-      <![CDATA[
-        this.open = true;
-        // Prevent page from scrolling on the space key.
-        event.preventDefault();
-      ]]>
-      </handler>
-    </handlers>
-  </binding>
-</bindings>
+    static get buttonFragment() {
+      let frag = document.importNode(MozXULElement.parseXULToFragment(`
+        <hbox class="box-inherit button-box" align="center" pack="center" flex="1" anonid="button-box">
+          <image class="button-icon"></image>
+          <label class="button-text"></label>
+        </hbox>`), true);
+      Object.defineProperty(this, "buttonFragment", {value: frag});
+      return frag;
+    }
+
+    static get menuFragment() {
+      let frag = document.importNode(MozXULElement.parseXULToFragment(`
+        <hbox class="box-inherit button-box" align="center" pack="center" flex="1">
+          <hbox class="box-inherit" align="center" pack="center" flex="1">
+            <image class="button-icon"></image>
+            <label class="button-text"></label>
+          </hbox>
+          <dropmarker class="button-menu-dropmarker"></dropmarker>
+        </hbox>`), true);
+      Object.defineProperty(this, "menuFragment", {value: frag});
+      return frag;
+    }
+
+    connectedCallback() {
+      if (this.delayConnectedCallback() || this.hasConnected) {
+        return;
+      }
+
+      this.hasConnected = true;
+
+      let fragment;
+      if (this.type === "menu") {
+        fragment = MozButton.menuFragment;
+
+        this.addEventListener("keypress", (event) => {
+          if (event.keyCode != KeyEvent.DOM_VK_RETURN && event.key != " ") {
+            return;
+          }
+
+          this.open = true;
+          // Prevent page from scrolling on the space key.
+          if (event.key == " ") {
+            event.preventDefault();
+          }
+        });
+      } else {
+        fragment = this.constructor.buttonFragment;
+      }
+
+      this.appendChild(fragment.cloneNode(true));
+      this.initializeAttributeInheritance();
+    }
+  }
+
+  customElements.define("button", MozButton);
+}
--- a/toolkit/content/widgets/button.xml
+++ b/toolkit/content/widgets/button.xml
@@ -184,49 +184,9 @@
         var buttonBox = dlg.getAnonymousElementByAttribute(dlg.documentElement,
                                                          "anonid", "buttons");
         if (buttonBox)
           this.fireAccessKeyButton(buttonBox, charPressedLower);
       ]]>
       </handler>
     </handlers>
   </binding>
-
-  <binding id="button"
-           extends="chrome://global/content/bindings/button.xml#button-base">
-    <content>
-      <children includes="observes|template|menupopup|panel|tooltip"/>
-      <xul:hbox class="box-inherit button-box" xbl:inherits="align,dir,pack,orient"
-                align="center" pack="center" flex="1" anonid="button-box">
-        <xul:image class="button-icon" xbl:inherits="src=image"/>
-        <xul:label class="button-text" xbl:inherits="value=label,accesskey,crop,highlightable"/>
-        <xul:label class="button-highlightable-text" xbl:inherits="xbl:text=label,accesskey,crop,highlightable"/>
-      </xul:hbox>
-    </content>
-  </binding>
-
-  <binding id="menu"
-           extends="chrome://global/content/bindings/button.xml#button">
-    <content>
-      <children includes="observes|template|menupopup|panel|tooltip"/>
-      <xul:hbox class="box-inherit button-box" xbl:inherits="align,dir,pack,orient"
-                align="center" pack="center" flex="1">
-        <xul:hbox class="box-inherit" xbl:inherits="align,dir,pack,orient"
-                  align="center" pack="center" flex="1">
-          <xul:image class="button-icon" xbl:inherits="src=image"/>
-          <xul:label class="button-text" xbl:inherits="value=label,accesskey,crop"/>
-        </xul:hbox>
-        <xul:dropmarker class="button-menu-dropmarker" xbl:inherits="open,disabled,label"/>
-      </xul:hbox>
-    </content>
-
-    <handlers>
-      <handler event="keypress" keycode="VK_RETURN" action="this.open = true;"/>
-      <handler event="keypress" key=" ">
-      <![CDATA[
-        this.open = true;
-        // Prevent page from scrolling on the space key.
-        event.preventDefault();
-      ]]>
-      </handler>
-    </handlers>
-  </binding>
 </bindings>
--- a/toolkit/content/xul.css
+++ b/toolkit/content/xul.css
@@ -100,26 +100,16 @@ vbox {
 label.text-link, label[onclick] {
   -moz-user-focus: normal;
 }
 
 label html|span.accesskey {
   text-decoration: underline;
 }
 
-/********** button **********/
-
-button {
-  -moz-binding: url("chrome://global/content/bindings/button.xml#button");
-}
-
-button[type="menu"] {
-  -moz-binding: url("chrome://global/content/bindings/button.xml#menu");
-}
-
 /********** toolbarbutton **********/
 
 toolbarbutton {
   -moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton");
 }
 
 toolbarbutton.badged-button > toolbarbutton,
 toolbarbutton.badged-button {
@@ -659,18 +649,16 @@ findbar {
 }
 
 /*********** tabmodalprompt ************/
 tabmodalprompt {
   overflow: hidden;
   text-shadow: none;
 }
 
-.button-highlightable-text:not([highlightable="true"]),
-.button-text[highlightable="true"],
 .menulist-highlightable-label:not([highlightable="true"]),
 .menulist-label[highlightable="true"],
 .menu-iconic-highlightable-text:not([highlightable="true"]),
 .menu-iconic-text[highlightable="true"] {
   display: none;
 }
 
 @supports -moz-bool-pref("layout.css.emulate-moz-box-with-flex") {
--- a/toolkit/mozapps/extensions/content/extensions.xml
+++ b/toolkit/mozapps/extensions/content/extensions.xml
@@ -687,16 +687,18 @@
       <xul:hbox class="description-container privateBrowsing-notice-container">
         <xul:label anonid="privateBrowsing" class="description privateBrowsing-notice" value="&addon.privateBrowsing.label;"/>
       </xul:hbox>
     </content>
 
     <implementation>
       <constructor><![CDATA[
         window.customElements.upgrade(this._stateMenulist);
+        window.customElements.upgrade(this._enableBtn);
+        window.customElements.upgrade(this._disableBtn);
 
         this._installStatus = document.getAnonymousElementByAttribute(this, "anonid", "install-status");
         this._installStatus.mControl = this;
 
         this.setAttribute("contextmenu", "addonitem-popup");
 
         this._showStatus("none");
 
--- a/toolkit/xre/nsNativeAppSupportCocoa.mm
+++ b/toolkit/xre/nsNativeAppSupportCocoa.mm
@@ -62,17 +62,25 @@ nsresult GetNativeWindowPointerFromDOMWi
 // was specified in either of these processes), making the original process handle
 // the arguments passed to this handler.
 void remoteClientNotificationCallback(CFNotificationCenterRef aCenter, void* aObserver,
                                       CFStringRef aName, const void* aObject,
                                       CFDictionaryRef aUserInfo) {
   // Autorelease pool to prevent memory leaks, in case there is no outer pool.
   mozilla::MacAutoreleasePool pool;
   NSDictionary* userInfoDict = (__bridge NSDictionary*)aUserInfo;
-  if (userInfoDict && [userInfoDict objectForKey:@"commandLineArgs"]) {
+  if (userInfoDict && [userInfoDict objectForKey:@"commandLineArgs"] &&
+      [userInfoDict objectForKey:@"senderPath"]) {
+
+    NSString* senderPath = [userInfoDict objectForKey:@"senderPath"];
+    if (![senderPath isEqual:[[NSBundle mainBundle] bundlePath]]) {
+      // The caller is not the process at the same path as we are at. Skipping.
+      return;
+    }
+
     NSArray* args = [userInfoDict objectForKey:@"commandLineArgs"];
     nsCOMPtr<nsICommandLineRunner> cmdLine(new nsCommandLine());
 
     // Converting Objective-C array into a C array,
     // which nsICommandLineRunner understands.
     int argc = [args count];
     const char** argv = new const char*[argc];
     for (int i = 0; i < argc; i++) {
@@ -196,26 +204,38 @@ NS_IMETHODIMP nsNativeAppSupportCocoa::S
   // current user.
   NSString* notificationName = [[[NSBundle mainBundle] bundleIdentifier]
       stringByAppendingString:@".distributedNotification.commandLineArgs"];
 
   BOOL runningInstanceFound = NO;
   if (!shallProceedLikeNoRemote) {
     // We check for other running instances only if -no-remote was not specified.
     // The check is needed so the marAppApplyUpdateSuccess.js test doesn't fail on next call.
-    runningInstanceFound =
-        [[NSRunningApplication
-            runningApplicationsWithBundleIdentifier:[[NSBundle mainBundle] bundleIdentifier]]
-            count] > 1;
+    NSArray* appsWithMatchingId = [NSRunningApplication runningApplicationsWithBundleIdentifier:
+                                    [[NSBundle mainBundle] bundleIdentifier]];
+    NSString* currentAppBundlePath = [[NSBundle mainBundle] bundlePath];
+    NSRunningApplication* currentApp = [NSRunningApplication currentApplication];
+    for (NSRunningApplication* app in appsWithMatchingId) {
+      if ([currentAppBundlePath isEqual:[[app bundleURL] path]] &&
+          ![currentApp isEqual:app]) {
+        runningInstanceFound = YES;
+        break;
+      }
+    }
   }
 
   if (!shallProceedLikeNoRemote && !mozillaRestarting && runningInstanceFound) {
     // There is another instance of this app already running!
     NSArray* arguments = [[NSProcessInfo processInfo] arguments];
-    CFDictionaryRef userInfoDict = (__bridge CFDictionaryRef) @{@"commandLineArgs" : arguments};
+    NSString* senderPath = [[NSBundle mainBundle] bundlePath];
+    CFDictionaryRef userInfoDict = (__bridge CFDictionaryRef) @{@"commandLineArgs" :
+                                                                  arguments,
+                                                                @"senderPath":
+                                                                  senderPath
+                                                                };
 
     // This code is shared between Firefox, Thunderbird and other Mozilla products.
     // So we need a notification name that is unique to the product, so we
     // do not send a notification to Firefox from Thunderbird and so on. I am using
     // bundle Id (assuming all Mozilla products come wrapped in .app bundles) -
     // it should be unique
     // (e.g., org.mozilla.firefox.distributedNotification.commandLineArgs for Firefox).
     // We also need to make sure the notifications are "local" to the current user,