Merge inbound to mozilla-central a=merge default tip
authorCoroiu Cristina <ccoroiu@mozilla.com>
Mon, 23 Apr 2018 12:59:31 +0300
changeset 415026 dfb15917c057
parent 415015 f3ad6a992444 (current diff)
parent 415025 4ac461885d8c (diff)
push id33887
push userccoroiu@mozilla.com
push date2018-04-23 09:59 +0000
treeherdermozilla-central@dfb15917c057 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone61.0a1
first release with
nightly linux32
dfb15917c057 / 61.0a1 / 20180423100754 / files
nightly linux64
dfb15917c057 / 61.0a1 / 20180423100754 / files
nightly mac
dfb15917c057 / 61.0a1 / 20180423100754 / files
nightly win32
dfb15917c057 / 61.0a1 / 20180423100754 / files
nightly win64
dfb15917c057 / 61.0a1 / 20180423100754 / 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 inbound to mozilla-central a=merge
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1263,16 +1263,18 @@ var gBrowserInit = {
       }
       initBrowser.removeAttribute("blank");
     }
 
     gBrowser.updateBrowserRemoteness(initBrowser, isRemote, {
       remoteType, sameProcessAsFrameLoader
     });
 
+    BrowserSearch.initPlaceHolder();
+
     // Hack to ensure that the about:home favicon is loaded
     // instantaneously, to avoid flickering and improve perceived performance.
     this._callWithURIToLoad(uriToLoad => {
       if (uriToLoad == "about:home") {
         gBrowser.setIcon(gBrowser.selectedTab, "chrome://branding/content/icon32.png");
       } else if (uriToLoad == "about:privatebrowsing") {
         gBrowser.setIcon(gBrowser.selectedTab, "chrome://browser/skin/privatebrowsing/favicon.svg");
       }
@@ -1458,16 +1460,17 @@ var gBrowserInit = {
     // We do this before the session restore service gets initialized so we can
     // apply full zoom settings to tabs restored by the session restore service.
     FullZoom.init();
     PanelUI.init();
 
     UpdateUrlbarSearchSplitterState();
 
     BookmarkingUI.init();
+    BrowserSearch.delayedStartupInit();
     AutoShowBookmarksToolbar.init();
 
     Services.prefs.addObserver(gHomeButton.prefDomain, gHomeButton);
 
     var homeButton = document.getElementById("home-button");
     gHomeButton.updateTooltip(homeButton);
 
     let safeMode = document.getElementById("helpSafeMode");
@@ -3760,20 +3763,35 @@ const DOMEventHandler = {
     if (!tab)
       return;
 
     BrowserSearch.addEngine(aBrowser, aEngine, makeURI(aURL));
   },
 };
 
 const BrowserSearch = {
+  _searchInitComplete: false,
+
   init() {
     Services.obs.addObserver(this, "browser-search-engine-modified");
   },
 
+  delayedStartupInit() {
+    // Asynchronously initialize the search service if necessary, to get the
+    // current engine for working out the placeholder.
+    Services.search.init(rv => {
+      if (Components.isSuccessCode(rv)) {
+        // Delay the update for this until so that we don't change it while
+        // the user is looking at it / isn't expecting it.
+        this._updateURLBarPlaceholder(Services.search.currentEngine, true);
+        this._searchInitComplete = true;
+      }
+    });
+  },
+
   uninit() {
     Services.obs.removeObserver(this, "browser-search-engine-modified");
   },
 
   observe(engine, topic, data) {
     // There are two kinds of search engine objects, nsISearchEngine objects and
     // plain { uri, title, icon } objects.  `engine` in this method is the
     // former.  The browser.engines and browser.hiddenEngines arrays are the
@@ -3790,16 +3808,21 @@ const BrowserSearch = {
       this._addMaybeOfferedEngine(engineName);
       break;
     case "engine-added":
       // An engine was added to the search service.  If a page is offering the
       // engine, then the engine needs to be removed from the corresponding
       // browser's offered engines.
       this._removeMaybeOfferedEngine(engineName);
       break;
+    case "engine-current":
+      if (this._searchInitComplete) {
+        this._updateURLBarPlaceholder(engine);
+      }
+      break;
     }
   },
 
   _addMaybeOfferedEngine(engineName) {
     let selectedBrowserOffersEngine = false;
     for (let browser of gBrowser.browsers) {
       for (let i = 0; i < (browser.hiddenEngines || []).length; i++) {
         if (browser.hiddenEngines[i].title == engineName) {
@@ -3837,16 +3860,98 @@ const BrowserSearch = {
         }
       }
     }
     if (selectedBrowserOffersEngine) {
       this.updateOpenSearchBadge();
     }
   },
 
+  /**
+   * Initializes the urlbar placeholder to the pre-saved engine name. We do this
+   * via a preference, to avoid needing to synchronously init the search service.
+   *
+   * This should be called around the time of DOMContentLoaded, so that it is
+   * initialized quickly before the user sees anything.
+   *
+   * Note: If the preference doesn't exist, we don't do anything as the default
+   * placeholder is a string which doesn't have the engine name.
+   */
+  initPlaceHolder() {
+    let engineName = Services.prefs.getStringPref("browser.urlbar.placeholderName", "");
+    if (engineName) {
+      // We can do this directly, since we know we're at DOMContentLoaded.
+      this._setURLBarPlaceholder(engineName);
+    }
+  },
+
+  /**
+   * Updates the URLBar placeholder for the specified engine, delaying the
+   * update if required. This also saves the current engine name in preferences
+   * for the next restart.
+   *
+   * Note: The engine name will only be displayed for built-in engines, as we
+   * know they should have short names.
+   *
+   * @param {nsISearchEngine} engine The search engine to use for the update.
+   * @param {Boolean} delayUpdate    Set to true, to delay update until the
+   *                                 placeholder is not displayed.
+   */
+  _updateURLBarPlaceholder(engine, delayUpdate = false) {
+    if (!engine) {
+      throw new Error("Expected an engine to be specified");
+    }
+
+    let engineName = "";
+    if (Services.search.getDefaultEngines().includes(engine)) {
+      engineName = engine.name;
+      Services.prefs.setStringPref("browser.urlbar.placeholderName", engineName);
+    } else {
+      Services.prefs.clearUserPref("browser.urlbar.placeholderName");
+    }
+
+    // Only delay if requested, and we're not displaying text in the URL bar
+    // currently.
+    if (delayUpdate && !gURLBar.value) {
+      // Delays changing the URL Bar placeholder until the user is not going to be
+      // seeing it, e.g. when there is a value entered in the bar, or if there is
+      // a tab switch to a tab which has a url loaded.
+      let placeholderUpdateListener = () => {
+        if (gURLBar.value) {
+          this._setURLBarPlaceholder(engineName);
+          gURLBar.removeEventListener("input", placeholderUpdateListener);
+          gBrowser.tabContainer.removeEventListener("TabSelect", placeholderUpdateListener);
+        }
+      };
+
+      gURLBar.addEventListener("input", placeholderUpdateListener);
+      gBrowser.tabContainer.addEventListener("TabSelect", placeholderUpdateListener);
+    } else {
+      this._setURLBarPlaceholder(engineName);
+    }
+  },
+
+  /**
+   * Sets the URLBar placeholder to either something based on the engine name,
+   * or the default placeholder.
+   *
+   * @param {String} name The name of the engine to use, an empty string if to
+   *                      use the default placeholder.
+   */
+  _setURLBarPlaceholder(name) {
+    let placeholder;
+    if (name) {
+      placeholder = gBrowserBundle.formatStringFromName("urlbar.placeholder",
+        [name], 1);
+    } else {
+      placeholder = gURLBar.getAttribute("defaultPlaceholder");
+    }
+    gURLBar.setAttribute("placeholder", placeholder);
+  },
+
   addEngine(browser, engine, uri) {
     // Check to see whether we've already added an engine with this title
     if (browser.engines) {
       if (browser.engines.some(e => e.title == engine.title))
         return;
     }
 
     var hidden = false;
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -772,16 +772,17 @@
                        cui-areatype="toolbar"
                        aboutHomeOverrideTooltip="&abouthome.pageTitle;"/>
         <toolbarspring cui-areatype="toolbar" class="chromeclass-toolbar-additional"/>
         <toolbaritem id="urlbar-container" flex="400" persist="width"
                      removable="false"
                      class="chromeclass-location" overflows="false">
             <textbox id="urlbar" flex="1"
                      placeholder="&urlbar.placeholder2;"
+                     defaultPlaceholder="&urlbar.placeholder2;"
                      focused="true"
                      type="autocomplete"
                      autocompletesearch="unifiedcomplete"
                      autocompletesearchparam="enable-actions"
                      autocompletepopup="PopupAutoCompleteRichResult"
                      completeselectedindex="true"
                      shrinkdelay="250"
                      tabscrolling="true"
--- a/browser/base/content/test/urlbar/browser.ini
+++ b/browser/base/content/test/urlbar/browser.ini
@@ -90,16 +90,20 @@ uses-unsafe-cpows = true
 [browser_urlbarOneOffs.js]
 support-files =
   searchSuggestionEngine.xml
   searchSuggestionEngine.sjs
 [browser_urlbarOneOffs_searchSuggestions.js]
 support-files =
   searchSuggestionEngine.xml
   searchSuggestionEngine.sjs
+[browser_urlbarPlaceholder.js]
+support-files =
+  searchSuggestionEngine.xml
+  searchSuggestionEngine.sjs
 [browser_urlbarPrivateBrowsingWindowChange.js]
 [browser_urlbarRaceWithTabs.js]
 [browser_urlbarRevert.js]
 [browser_urlbarSearchSingleWordNotification.js]
 [browser_urlbarSearchSuggestions.js]
 support-files =
   searchSuggestionEngine.xml
   searchSuggestionEngine.sjs
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarPlaceholder.js
@@ -0,0 +1,93 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This test ensures the placeholder is set correctly for different search
+ * engines.
+ */
+
+"use strict";
+
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+
+const originalEngine = Services.search.currentEngine;
+const expectedString = gBrowserBundle.formatStringFromName("urlbar.placeholder",
+  [originalEngine.name], 1);
+var extraEngine;
+var tabs = [];
+
+add_task(async function setup() {
+  extraEngine = await promiseNewSearchEngine(TEST_ENGINE_BASENAME);
+
+  // Force display of a tab with a URL bar, to clear out any possible placeholder
+  // initialization listeners that happen on startup.
+  let urlTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+  BrowserTestUtils.removeTab(urlTab);
+
+  registerCleanupFunction(() => {
+    Services.search.currentEngine = originalEngine;
+    for (let tab of tabs) {
+      BrowserTestUtils.removeTab(tab);
+    }
+  });
+});
+
+add_task(async function test_change_default_engine_updates_placeholder() {
+  tabs.push(await BrowserTestUtils.openNewForegroundTab(gBrowser));
+
+  Services.search.currentEngine = extraEngine;
+
+  await TestUtils.waitForCondition(
+    () => gURLBar.getAttribute("placeholder") == gURLBar.getAttribute("defaultPlaceholder"),
+    "The placeholder should match the default placeholder for non-built-in engines.");
+
+  Services.search.currentEngine = originalEngine;
+
+  await TestUtils.waitForCondition(
+    () => gURLBar.getAttribute("placeholder") == expectedString,
+    "The placeholder should include the engine name for built-in engines.");
+});
+
+add_task(async function test_delayed_update_placeholder() {
+  // Since we can't easily test for startup changes, we'll at least test the delay
+  // of update for the placeholder works.
+  let urlTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+  tabs.push(urlTab);
+
+  // Open a tab with a blank URL bar.
+  let blankTab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+  tabs.push(blankTab);
+
+  // Pretend we've "initialized".
+  BrowserSearch._updateURLBarPlaceholder(extraEngine, true);
+
+  Assert.equal(gURLBar.getAttribute("placeholder"), expectedString,
+    "Placeholder should be unchanged.");
+
+  // Now switch to a tab with something in the URL Bar.
+  await BrowserTestUtils.switchTab(gBrowser, urlTab);
+
+  await TestUtils.waitForCondition(
+    () => gURLBar.getAttribute("placeholder") == gURLBar.getAttribute("defaultPlaceholder"),
+    "The placeholder should have updated in the background.");
+
+  // Do it the other way to check both named engine and fallback code paths.
+  await BrowserTestUtils.switchTab(gBrowser, blankTab);
+
+  BrowserSearch._updateURLBarPlaceholder(originalEngine, true);
+
+  Assert.equal(gURLBar.getAttribute("placeholder"), gURLBar.getAttribute("defaultPlaceholder"),
+    "Placeholder should be unchanged.");
+
+  await BrowserTestUtils.switchTab(gBrowser, urlTab);
+
+  await TestUtils.waitForCondition(
+    () => gURLBar.getAttribute("placeholder") == expectedString,
+    "The placeholder should include the engine name for built-in engines.");
+
+  // Now check when we have a URL displayed, the placeholder is updated straight away.
+  BrowserSearch._updateURLBarPlaceholder(extraEngine);
+
+  Assert.equal(gURLBar.getAttribute("placeholder"), gURLBar.getAttribute("defaultPlaceholder"),
+    "Placeholder should be the default.");
+});
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -429,17 +429,16 @@ These should match what Safari and other
 
 <!-- LOCALIZATION NOTE (moreMenu.label) This label is used in the new Photon
     app (hamburger) menu. When clicked, it opens a subview that contains
     secondary commands. -->
 <!ENTITY moreMenu.label "More">
 
 <!ENTITY openCmd.commandkey           "l">
 <!ENTITY urlbar.placeholder2          "Search or enter address">
-<!ENTITY urlbar.placeholder3          "Enter search terms and addresses here">
 <!ENTITY urlbar.accesskey             "d">
 <!-- LOCALIZATION NOTE (urlbar.extension.label): Used to indicate that a selected autocomplete entry is provided by an extension. -->
 <!ENTITY urlbar.extension.label       "Extension:">
 <!ENTITY urlbar.switchToTab.label     "Switch to tab:">
 
 <!-- LOCALIZATION NOTE (urlbar.searchSuggestionsNotification.hintPrefix): Shown just before the suggestions opt-out hint. -->
 <!ENTITY urlbar.searchSuggestionsNotification.hintPrefix "Tip:">
 <!-- LOCALIZATION NOTE (urlbar.searchSuggestionsNotification.hint): &#x1F50E; is the magnifier icon emoji, please don't change it. -->
--- a/devtools/client/scratchpad/scratchpad.js
+++ b/devtools/client/scratchpad/scratchpad.js
@@ -332,24 +332,24 @@ var Scratchpad = {
   get notificationBox() {
     return document.getElementById("scratchpad-notificationbox");
   },
 
   /**
    * Hide the menu bar.
    */
   hideMenu: function SP_hideMenu() {
-    document.getElementById("sp-menubar").style.display = "none";
+    document.getElementById("sp-menu-toolbar").style.display = "none";
   },
 
   /**
    * Show the menu bar.
    */
   showMenu: function SP_showMenu() {
-    document.getElementById("sp-menubar").style.display = "";
+    document.getElementById("sp-menu-toolbar").style.display = "";
   },
 
   /**
    * Get the editor content, in the given range. If no range is given you get
    * the entire editor content.
    *
    * @param number [aStart=0]
    *        Optional, start from the given offset.
--- a/devtools/client/scratchpad/scratchpad.xul
+++ b/devtools/client/scratchpad/scratchpad.xul
@@ -136,18 +136,18 @@
        command="sp-cmd-documentationLink"/>
   <key id="key_gotoLine"
        key="&gotoLineCmd.key;"
        command="key_gotoLine"
        modifiers="accel"/>
 
 </keyset>
 
-<toolbar type="menubar">
-<menubar id="sp-menubar">
+<toolbar type="menubar" id="sp-menu-toolbar">
+<menubar>
   <menu id="sp-file-menu" label="&fileMenu.label;" accesskey="&fileMenu.accesskey;">
     <menupopup id="sp-menu-filepopup">
       <menuitem id="sp-menu-newscratchpad"
                 label="&newWindowCmd.label;"
                 accesskey="&newWindowCmd.accesskey;"
                 key="sp-key-window"
                 command="sp-cmd-newWindow"/>
       <menuseparator/>
--- a/devtools/client/scratchpad/test/browser.ini
+++ b/devtools/client/scratchpad/test/browser.ini
@@ -40,8 +40,9 @@ support-files = NS_ERROR_ILLEGAL_INPUT.t
 [browser_scratchpad_pprint.js]
 [browser_scratchpad_pprint_error_goto_line.js]
 [browser_scratchpad_restore.js]
 [browser_scratchpad_tab_switch.js]
 [browser_scratchpad_ui.js]
 [browser_scratchpad_close_toolbox.js]
 [browser_scratchpad_remember_view_options.js]
 [browser_scratchpad_disable_view_menu_items.js]
+[browser_scratchpad_menubar.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/scratchpad/test/browser_scratchpad_menubar.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Sanity test that menu bar is displayed. If open the scratchpad as toolbox panel,
+// this menu should be hidden.
+
+var {TargetFactory} = require("devtools/client/framework/target");
+
+add_task(async function() {
+  // Now open the scratchpad as window.
+  info("Test existence of menu bar of scratchpad.");
+  const options = {
+    tabContent: "Sanity test for scratchpad panel."
+  };
+
+  info("Open scratchpad.");
+  let [win] = await openTabAndScratchpad(options);
+
+  let menuToolbar = win.document.getElementById("sp-menu-toolbar");
+  ok(menuToolbar, "The scratchpad should have a menu bar.");
+});
+
+add_task(async function() {
+  // Now open the scratchpad panel after setting visibility preference.
+  info("Test existence of menu bar of scratchpad panel.");
+  await new Promise(resolve => {
+    SpecialPowers.pushPrefEnv({"set": [["devtools.scratchpad.enabled", true]]}, resolve);
+  });
+
+  info("Open devtools on the Scratchpad.");
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
+  let toolbox = await gDevTools.showToolbox(target, "scratchpad");
+
+  let menuToolbar = toolbox.doc.getElementById("sp-menu-toolbar");
+  ok(!menuToolbar, "The scratchpad panel should not have a menu bar.");
+});
--- a/editor/libeditor/EditorDOMPoint.h
+++ b/editor/libeditor/EditorDOMPoint.h
@@ -230,16 +230,26 @@ public:
    */
   bool
   IsInTextNode() const
   {
     return mParent && mParent->IsText();
   }
 
   /**
+   * IsInNativeAnonymousSubtree() returns true if the container is in
+   * native anonymous subtree.
+   */
+  bool
+  IsInNativeAnonymousSubtree() const
+  {
+    return mParent && mParent->IsInNativeAnonymousSubtree();
+  }
+
+  /**
    * IsContainerHTMLElement() returns true if the container node is an HTML
    * element node and its node name is aTag.
    */
   bool
   IsContainerHTMLElement(nsAtom* aTag) const
   {
     return mParent && mParent->IsHTMLElement(aTag);
   }
@@ -526,16 +536,43 @@ public:
 
     if (mOffset.isSome()) {
       mOffset = mozilla::Some(mOffset.value() - 1);
     }
     mChild = previousSibling;
     return true;
   }
 
+  /**
+   * GetNonAnonymousSubtreePoint() returns a DOM point which is NOT in
+   * native-anonymous subtree.  If the instance isn't in native-anonymous
+   * subtree, this returns same point.  Otherwise, climbs up until finding
+   * non-native-anonymous parent and returns the point of it.  I.e.,
+   * container is parent of the found non-anonymous-native node.
+   */
+  EditorRawDOMPoint
+  GetNonAnonymousSubtreePoint() const
+  {
+    if (NS_WARN_IF(!IsSet())) {
+      return EditorRawDOMPoint();
+    }
+    if (!IsInNativeAnonymousSubtree()) {
+      return EditorRawDOMPoint(*this);
+    }
+    nsINode* parent;
+    for (parent = mParent->GetParentNode();
+         parent && parent->IsInNativeAnonymousSubtree();
+         parent = mParent->GetParentNode()) {
+    }
+    if (!parent) {
+      return EditorRawDOMPoint();
+    }
+    return EditorRawDOMPoint(parent);
+  }
+
   bool
   IsSet() const
   {
     return mParent && (mIsChildInitialized || mOffset.isSome());
   }
 
   bool
   IsSetAndValid() const
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -1454,58 +1454,64 @@ HTMLEditor::RebuildDocumentFromSource(co
 EditorRawDOMPoint
 HTMLEditor::GetBetterInsertionPointFor(nsINode& aNodeToInsert,
                                        const EditorRawDOMPoint& aPointToInsert)
 {
   if (NS_WARN_IF(!aPointToInsert.IsSet())) {
     return aPointToInsert;
   }
 
+  EditorRawDOMPoint pointToInsert(aPointToInsert.GetNonAnonymousSubtreePoint());
+  if (NS_WARN_IF(!pointToInsert.IsSet())) {
+    // Cannot insert aNodeToInsert into this DOM tree.
+    return EditorRawDOMPoint();
+  }
+
   // If the node to insert is not a block level element, we can insert it
   // at any point.
   if (!IsBlockNode(&aNodeToInsert)) {
-    return aPointToInsert;
-  }
-
-  WSRunObject wsObj(this, aPointToInsert.GetContainer(),
-                    aPointToInsert.Offset());
+    return pointToInsert;
+  }
+
+  WSRunObject wsObj(this, pointToInsert.GetContainer(),
+                    pointToInsert.Offset());
 
   // If the insertion position is after the last visible item in a line,
   // i.e., the insertion position is just before a visible line break <br>,
   // we want to skip to the position just after the line break (see bug 68767).
   nsCOMPtr<nsINode> nextVisibleNode;
   int32_t nextVisibleOffset = 0;
   WSType nextVisibleType;
-  wsObj.NextVisibleNode(aPointToInsert, address_of(nextVisibleNode),
+  wsObj.NextVisibleNode(pointToInsert, address_of(nextVisibleNode),
                         &nextVisibleOffset, &nextVisibleType);
   // So, if the next visible node isn't a <br> element, we can insert the block
   // level element to the point.
   if (!nextVisibleNode ||
       !(nextVisibleType & WSType::br)) {
-    return aPointToInsert;
+    return pointToInsert;
   }
 
   // However, we must not skip next <br> element when the caret appears to be
   // positioned at the beginning of a block, in that case skipping the <br>
   // would not insert the <br> at the caret position, but after the current
   // empty line.
   nsCOMPtr<nsINode> previousVisibleNode;
   int32_t previousVisibleOffset = 0;
   WSType previousVisibleType;
-  wsObj.PriorVisibleNode(aPointToInsert, address_of(previousVisibleNode),
+  wsObj.PriorVisibleNode(pointToInsert, address_of(previousVisibleNode),
                          &previousVisibleOffset, &previousVisibleType);
   // So, if there is no previous visible node,
   // or, if both nodes of the insertion point is <br> elements,
   // or, if the previous visible node is different block,
   // we need to skip the following <br>.  So, otherwise, we can insert the
   // block at the insertion point.
   if (!previousVisibleNode ||
       (previousVisibleType & WSType::br) ||
       (previousVisibleType & WSType::thisBlock)) {
-    return aPointToInsert;
+    return pointToInsert;
   }
 
   EditorRawDOMPoint afterBRNode(nextVisibleNode);
   DebugOnly<bool> advanced = afterBRNode.AdvanceOffset();
   NS_WARNING_ASSERTION(advanced,
     "Failed to advance offset to after the <br> node");
   return afterBRNode;
 }
--- a/layout/style/nsCSSAnonBoxes.h
+++ b/layout/style/nsCSSAnonBoxes.h
@@ -38,18 +38,16 @@ struct CSSAnonBoxAtoms
     #include "nsCSSAnonBoxList.h"
     #undef CSS_ANON_BOX
     AtomsCount
   };
 
   const nsICSSAnonBoxPseudo mAtoms[static_cast<size_t>(Atoms::AtomsCount)];
 };
 
-extern const CSSAnonBoxAtoms gCSSAnonBoxAtoms;
-
 } // namespace detail
 } // namespace mozilla
 
 class nsCSSAnonBoxes {
 public:
 
   static void RegisterStaticAtoms();
 
@@ -61,17 +59,17 @@ public:
   {
     return aPseudo == mozText || aPseudo == oofPlaceholder ||
            aPseudo == firstLetterContinuation;
   }
 
 private:
   static const nsStaticAtom* const sAtoms;
   static constexpr size_t sAtomsLen =
-    mozilla::ArrayLength(mozilla::detail::gCSSAnonBoxAtoms.mAtoms);
+    static_cast<size_t>(mozilla::detail::CSSAnonBoxAtoms::Atoms::AtomsCount);
 
 public:
   #define CSS_ANON_BOX(name_, value_) \
     NS_STATIC_ATOM_DECL_PTR(nsICSSAnonBoxPseudo, name_)
   #include "nsCSSAnonBoxList.h"
   #undef CSS_ANON_BOX
 
   typedef uint8_t NonInheritingBase;
--- a/layout/style/nsCSSPseudoElements.h
+++ b/layout/style/nsCSSPseudoElements.h
@@ -105,18 +105,16 @@ struct CSSPseudoElementAtoms
     #include "nsCSSPseudoElementList.h"
     #undef CSS_PSEUDO_ELEMENT
     AtomsCount
   };
 
   const nsICSSPseudoElement mAtoms[static_cast<size_t>(Atoms::AtomsCount)];
 };
 
-extern const CSSPseudoElementAtoms gCSSPseudoElementAtoms;
-
 } // namespace detail
 
 } // namespace mozilla
 
 class nsCSSPseudoElements
 {
   typedef mozilla::CSSPseudoElementType Type;
   typedef mozilla::CSSEnabledState EnabledState;
@@ -134,17 +132,17 @@ public:
   static bool IsEagerlyCascadedInServo(const Type aType)
   {
     return PseudoElementHasFlags(aType, CSS_PSEUDO_ELEMENT_IS_CSS2);
   }
 
 private:
   static const nsStaticAtom* const sAtoms;
   static constexpr size_t sAtomsLen =
-    mozilla::ArrayLength(mozilla::detail::gCSSPseudoElementAtoms.mAtoms);
+    static_cast<size_t>(mozilla::detail::CSSPseudoElementAtoms::Atoms::AtomsCount);
 
 public:
   #define CSS_PSEUDO_ELEMENT(name_, value_, flags_) \
     NS_STATIC_ATOM_DECL_PTR(nsICSSPseudoElement, name_);
   #include "nsCSSPseudoElementList.h"
   #undef CSS_PSEUDO_ELEMENT
 
   static Type GetPseudoType(nsAtom* aAtom, EnabledState aEnabledState);
--- a/xpcom/ds/nsGkAtoms.h
+++ b/xpcom/ds/nsGkAtoms.h
@@ -24,27 +24,25 @@ struct GkAtoms
     #include "nsGkAtomList.h"
     #undef GK_ATOM
     AtomsCount
   };
 
   const nsStaticAtom mAtoms[static_cast<size_t>(Atoms::AtomsCount)];
 };
 
-extern const GkAtoms gGkAtoms;
-
 } // namespace detail
 } // namespace mozilla
 
 class nsGkAtoms
 {
 private:
   static const nsStaticAtom* const sAtoms;
   static constexpr size_t sAtomsLen =
-    mozilla::ArrayLength(mozilla::detail::gGkAtoms.mAtoms);
+    static_cast<size_t>(mozilla::detail::GkAtoms::Atoms::AtomsCount);
 
 public:
   static void RegisterStaticAtoms();
 
   #define GK_ATOM(name_, value_) NS_STATIC_ATOM_DECL_PTR(nsStaticAtom, name_)
   #include "nsGkAtomList.h"
   #undef GK_ATOM
 };
--- a/xpcom/ds/nsStaticAtom.h
+++ b/xpcom/ds/nsStaticAtom.h
@@ -91,33 +91,29 @@
 //       three,
 //       >>>
 //       AtomsCount
 //     };
 //
 //     const nsStaticAtom mAtoms[static_cast<size_t>(Atoms::AtomsCount)];
 //   };
 //
-//   // The MyAtoms instance is `extern const` so it can be defined in a .cpp
-//   // file.
-//   extern const MyAtoms gMyAtoms;
-//
 //   } // namespace detail
 //
 //   // This class holds the pointers to the individual atoms.
 //   class nsMyAtoms
 //   {
 //   private:
 //     // This is a useful handle to the array of atoms, used below and also
 //     // possibly by Rust code.
 //     static const nsStaticAtom* const sAtoms;
 //
 //     // The number of atoms, used below.
 //     static constexpr size_t sAtomsLen =
-//       mozilla::ArrayLength(detail::gMyAtoms.mAtoms);
+//       static_cast<size_t>(detail::MyAtoms::Atoms::AtomsCount);
 //
 //   public:
 //     // The type is not `nsStaticAtom* const` -- even though these atoms are
 //     // immutable -- because they are often passed to functions with
 //     // `nsAtom*` parameters, i.e. that can be passed both dynamic and
 //     // static.
 //     <<<
 //     #define MY_ATOM(name_, value_) NS_STATIC_ATOM_DECL_PTR(nsStaticAtom, name_)
--- a/xpcom/io/nsDirectoryService.h
+++ b/xpcom/io/nsDirectoryService.h
@@ -35,18 +35,16 @@ struct DirectoryAtoms
     #include "nsDirectoryServiceAtomList.h"
     #undef DIR_ATOM
     AtomsCount
   };
 
   const nsStaticAtom mAtoms[static_cast<size_t>(Atoms::AtomsCount)];
 };
 
-extern const DirectoryAtoms gDirectoryAtoms;
-
 } // namespace detail
 } // namespace mozilla
 
 class nsDirectoryService final
   : public nsIDirectoryService
   , public nsIProperties
   , public nsIDirectoryServiceProvider2
 {
@@ -76,17 +74,17 @@ private:
 
   nsresult GetCurrentProcessDirectory(nsIFile** aFile);
 
   nsInterfaceHashtable<nsCStringHashKey, nsIFile> mHashtable;
   nsTArray<nsCOMPtr<nsIDirectoryServiceProvider>> mProviders;
 
   static const nsStaticAtom* const sAtoms;
   static constexpr size_t sAtomsLen =
-    mozilla::ArrayLength(mozilla::detail::gDirectoryAtoms.mAtoms);
+    static_cast<size_t>(mozilla::detail::DirectoryAtoms::Atoms::AtomsCount);
 
 public:
   #define DIR_ATOM(name_, value_) NS_STATIC_ATOM_DECL_PTR(nsStaticAtom, name_)
   #include "nsDirectoryServiceAtomList.h"
   #undef DIR_ATOM
 };
 
 #endif