Merge inbound to mozilla-central a=merge
authorCoroiu Cristina <ccoroiu@mozilla.com>
Mon, 23 Apr 2018 12:59:31 +0300
changeset 786478 dfb15917c057f17e5143f7d7c6e1972ba53efc49
parent 786477 f3ad6a992444517319141bee521e512ce4faa006 (current diff)
parent 786424 4ac461885d8c424010d4e442e649d06acb9d2d60 (diff)
child 786479 7219854cc6566f2c96143ef32e2634c7e270ae04
child 786480 78f074180cf7c1bf59f00ed77fdc25fefdc621f0
child 786483 032612d389288470c4884e7d3701d7943b9ac087
child 786495 36451c14f05cb113f5ccdee5b1a6309d362e28a8
child 786498 9447762f0a2ed4f80a01ba51c7f1a2499efcf627
child 786502 6ce8c84ac9a2ee6886483c3e2bb91320c557c371
child 786510 ef5d4295bb5ce780d28ab88b0f262a5a2f4b02a2
child 786512 00dd3d5450a15fb7bd77c4e1a3512533f89823b2
child 786518 5e83a6f189527705101ff73387f5e892ecd90a2d
child 786531 5945af455a6e3816b29aded7ddf1fcc653512446
child 786538 c1d890d85d46c1501077264ff2fd11a55cdfb408
child 786554 4928bbfd289f8f8d79fe14324d0acea5bc9dae9a
child 786555 4b03586da4555c3a5bfc8970c5cc8c6463ff3f84
child 786564 fa5377b9b77762214d0d46f610b81ab4c33299a5
child 786565 cb75c28f544fb507cd31e449609e87f68b58a68d
child 786571 34cab02e1129bcca7e9395098381b3f1dccdf54e
child 786574 fc047854de8abf90bedabe04728e837056d7ceab
child 786585 19caeb2025ebb9a51b642b66bf180f169ace57f2
child 786587 826f8ff2d309c111956af3e40981a187c1d8a70e
child 786588 5706f4251c7727dbd8bea441f2e2cd2878665e7d
child 786597 af7d8dd8063f0e29d4b690bc40902071df91c497
child 786598 d3213b0797d8ca2ed6c5f4b5f42b606b323edf92
child 786599 6e4a4dae8601025a746bb907ce368a989bfdad78
child 786600 cad1ec5d7d153a21deebb151a2f1776deb4bccda
child 786620 b26b75090c6956bc9e2de762c237f7b0bf468ebb
child 786621 ad11e24d63198273fff12ce73f04c44c47140d91
child 786630 0eaeb17809fede07f6b9fc4ee5d856d0078f83be
child 786632 cd675f40c1d9a8df75975de593c6b62fed9a849a
child 786660 855486174a2f4c42351334ba0a4dd0dc1e05b35a
child 786661 edee080c58fc710bc9bfebd681e504c0fe72a800
child 786662 402f4c2dc00b38a795e1c75834117e09ba96d8d6
child 786664 540a2e630daefb46666dba7324da3919506a15c4
child 786667 7384bcc1de743ecdd25c8f1105f8a6341ffe7f50
child 786723 db212900f7ea4e19f29a543f7cacb5e2dcc201fe
child 786768 d65a818d9994e193704711bfaeac44482d4d85b0
child 786775 f17590d444f381d60d13eb2d7658ca5a4c6a97b8
child 786782 1b67c4a2aca71fc975b163ddf9c374269afeec54
child 786784 2cc5a196aae152c69da901f04838426d8981d8a4
child 786785 aaa9f031906968b5013f3f4feceb6773827ae579
child 786787 6510c0a2fc6b69f4559f2d0d21702c9c47749607
child 786813 a7b3dbe511cbfcfae695093eefc5021120a3d658
child 786814 e9762120eb95c0e000dbb9af63cde3ad2ab1a678
child 786815 b32a2061c94694be838d0d2b119587a779001875
child 786818 ab89ad5a97dfca6511a2445fe430b3a87714aae4
child 786828 a22d1fd349787608a1f8e739a0582fb8c8415baa
child 786874 60c0ca0f01218c258226c9a0a7073bdbd4635928
child 786882 a8e18873bce8624ef51c9ecd42d0da82c3e1cf68
child 786956 018572df322345dcb73a07316acca43c4169d429
child 786961 372538ec2cf5eae6bdfcfc27c6a717882673a9c8
child 786964 93f5fc482af51ba5e8dcb32f877256f3ac7184bb
child 786965 c2a9553ec5ee0bb348d62dfe20bebf01005c9fe0
child 786968 942670fae01b7058d671aaaea169dab2264eac35
child 786998 69a6db99c9ad4c9c940abebc2f7512654e2b611d
child 787000 a4c2b1639b9aa741e5e76667ca5a46791712303b
child 787065 edf5673be59a3714c3dd4eb239efd17d6a91ec32
child 787069 c7d892159ef875cf385c0d8c88233c900a09e5dc
child 787078 4e7437e1d5329277e6d01d97226cbd1486b3c17b
child 787242 e7d050293644995b86f26d89d331245e5bea68d4
child 787245 6059cc975097bdbd46679f013ab7ef08caf28114
child 787273 3fb318bf73f99d6e7747d17e7ce8068bbb92fb57
child 787276 b30a610d1f7353d955bca3886a15f1667de1f6d7
child 787282 cebf5fb2c38b8d9616469b4f77f69b6f46efefbe
child 787387 3047a862c4147ce430985d57ba57748617c94f96
child 787388 77bd6d893de865d3674dd49af2b521d927ae0e2e
child 787389 be1e525b66b6ae82913f8a72df62f73ee1adc62d
child 787410 6f0bbaf6e38f0624aa7cd769ad17bd8c9fea3ac2
child 787413 75e67479a3306a29b8ea45491984d08ceb4d6907
child 787415 3c0824f154ec288a241a23213b337962b1e2cd76
child 787460 0bac4cf7c64563e1ac1e5b743a0b977a16148b82
child 787586 fbc088b36c5d520b6842c05507b1ec75819408ca
child 787675 e8a0ca87c9c77950e90eac0e1878c55b93fa6903
child 787678 9caf84c61f0a7011ae7606782a1086689b431909
child 787679 4c92374fbb706371d2e000dbf41e898bc233cf7b
child 787831 985ade05d74f45fff49c1749702a5518b860a2ed
child 787849 7f767686c65426a1079c0b10092d32dee91a4e3b
child 787926 a241e31159668493c4ef2e22be94618ac423283e
child 787935 7a0199af416dc1ed119e72db4338d01ad4ff9ffc
child 787936 7eef023a21715e813b7ce542668f4d0a76ff11e5
child 787938 0ad0c5d4c9b3256fea18fcc51955aed8500f7f57
child 788084 d59da3cf761151f939ef361c5150052f6740c585
child 788100 5816dccdf9efff9f1a9f8f026ef6786e648f24df
child 788496 558b4b8723d43ab99814230d3efae406fd7acd6b
child 788585 8f6184645d8e2b331bcf6e31dfca67d97db2b4df
child 788674 7089551dfe7f5d7e10cabefd591d7ab316038865
child 788751 a12cd62487796e94840b44d7a4febf40433c7c1b
child 790028 60e920133e2848723825dcbc6c12e84ddadbf42c
child 790029 862c4ef0040f9c6c67384e59646063e5c86cf99a
push id107478
push userbmo:standard8@mozilla.com
push dateMon, 23 Apr 2018 10:40:33 +0000
reviewersmerge
milestone61.0a1
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