Bug 1523332 - Allow to instant switch on and off the Quantum Bar. r=dao
authorMarco Bonardo <mbonardo@mozilla.com>
Fri, 01 Mar 2019 14:52:17 +0000
changeset 519835 7b3e30ae92a935dd09df08cf7d862f67472cd5c3
parent 519834 93f7dc3084a1350e5c2c21d599ec6634ebe0ec8f
child 519836 ccb0b59aa78e5ad68baf10c479cad30ecf44c5f3
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdao
bugs1523332
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1523332 - Allow to instant switch on and off the Quantum Bar. r=dao Differential Revision: https://phabricator.services.mozilla.com/D20534
browser/base/content/browser-customization.js
browser/base/content/browser.css
browser/base/content/browser.js
browser/base/content/browser.xul
browser/base/content/urlbarBindings.xml
browser/components/urlbar/UrlbarInput.jsm
browser/components/urlbar/tests/browser/browser_UrlbarInput_unit.js
browser/themes/shared/urlbar-searchbar.inc.css
--- a/browser/base/content/browser-customization.js
+++ b/browser/base/content/browser-customization.js
@@ -59,16 +59,18 @@ var CustomizationHandler = {
     // Re-enable parts of the UI we disabled during the dialog
     let menubar = document.getElementById("main-menubar");
     for (let childNode of menubar.children)
       childNode.setAttribute("disabled", false);
     let cmd = document.getElementById("cmd_CustomizeToolbars");
     cmd.removeAttribute("disabled");
 
     gBrowser.selectedBrowser.focus();
+
+    gURLBarHandler.customizeEnd();
   },
 };
 
 var AutoHideMenubar = {
   get _node() {
     delete this._node;
     return this._node = document.getElementById("toolbar-menubar");
   },
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -556,24 +556,22 @@ toolbar:not(#TabsToolbar) > #personal-bo
 #urlbar,
 .searchbar-textbox {
   /* Setting a width and min-width to let the location & search bars maintain
      a constant width in case they haven't be resized manually. (bug 965772) */
   width: 1px;
   min-width: 1px;
 }
 
-#urlbar {
+#urlbar[quantumbar="false"] {
   -moz-binding: url(chrome://browser/content/urlbarBindings.xml#legacy-urlbar);
 }
 
-@supports -moz-bool-pref("browser.urlbar.quantumbar") {
-  #urlbar {
-    -moz-binding: url(chrome://browser/content/urlbarBindings.xml#urlbar);
-  }
+#urlbar[quantumbar="true"] {
+  -moz-binding: url(chrome://browser/content/urlbarBindings.xml#urlbar);
 }
 
 /* Display URLs left-to-right but right aligned in RTL mode. */
 html|input.urlbar-input:-moz-locale-dir(rtl) {
   direction: ltr !important;
   text-align: right !important;
 }
 
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -43,16 +43,18 @@ XPCOMUtils.defineLazyModuleGetters(this,
   PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
   PlacesUIUtils: "resource:///modules/PlacesUIUtils.jsm",
   PlacesTransactions: "resource://gre/modules/PlacesTransactions.jsm",
   PluralForm: "resource://gre/modules/PluralForm.jsm",
   Pocket: "chrome://pocket/content/Pocket.jsm",
   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
   ProcessHangMonitor: "resource:///modules/ProcessHangMonitor.jsm",
   PromiseUtils: "resource://gre/modules/PromiseUtils.jsm",
+  // TODO (Bug 1529552): Remove once old urlbar code goes away.
+  ReaderMode: "resource://gre/modules/ReaderMode.jsm",
   ReaderParent: "resource:///modules/ReaderParent.jsm",
   RFPHelper: "resource://gre/modules/RFPHelper.jsm",
   SafeBrowsing: "resource://gre/modules/SafeBrowsing.jsm",
   Sanitizer: "resource:///modules/Sanitizer.jsm",
   SessionStartup: "resource:///modules/sessionstore/SessionStartup.jsm",
   SessionStore: "resource:///modules/sessionstore/SessionStore.jsm",
   ShortcutUtils: "resource://gre/modules/ShortcutUtils.jsm",
   SimpleServiceDiscovery: "resource://gre/modules/SimpleServiceDiscovery.jsm",
@@ -76,21 +78,16 @@ XPCOMUtils.defineLazyModuleGetters(this,
   ZoomUI: "resource:///modules/ZoomUI.jsm",
 });
 
 if (AppConstants.MOZ_CRASHREPORTER) {
   ChromeUtils.defineModuleGetter(this, "PluginCrashReporter",
     "resource:///modules/ContentCrashHandlers.jsm");
 }
 
-if (!Services.prefs.getBoolPref("browser.urlbar.quantumbar", false)) {
-  ChromeUtils.defineModuleGetter(this, "ReaderMode",
-    "resource://gre/modules/ReaderMode.jsm");
-}
-
 XPCOMUtils.defineLazyScriptGetter(this, "PlacesTreeView",
                                   "chrome://browser/content/places/treeView.js");
 XPCOMUtils.defineLazyScriptGetter(this, ["PlacesInsertionPoint", "PlacesController",
                                          "PlacesControllerDragHelper"],
                                   "chrome://browser/content/places/controller.js");
 XPCOMUtils.defineLazyScriptGetter(this, "PrintUtils",
                                   "chrome://global/content/printUtils.js");
 XPCOMUtils.defineLazyScriptGetter(this, "ZoomManager",
@@ -185,28 +182,92 @@ XPCOMUtils.defineLazyGetter(this, "gCust
     ChromeUtils.import("resource:///modules/CustomizeMode.jsm");
   return new CustomizeMode(window);
 });
 
 XPCOMUtils.defineLazyGetter(this, "gNavToolbox", () => {
   return document.getElementById("navigator-toolbox");
 });
 
-XPCOMUtils.defineLazyGetter(this, "gURLBar", () => {
-  let element = document.getElementById("urlbar");
-
-  if (!Services.prefs.getBoolPref("browser.urlbar.quantumbar", false)) {
-    return element;
-  }
-
-  return new UrlbarInput({
-    textbox: element,
-    panel: document.getElementById("urlbar-results"),
-  });
-});
+XPCOMUtils.defineLazyGetter(this, "gURLBar", () => gURLBarHandler.urlbar);
+
+/**
+ * Tracks the urlbar object, allowing to reinitiate it when necessary, e.g. on
+ * customization or when the quantumbar pref changes.
+ */
+var gURLBarHandler = {
+  /**
+   * The urlbar binding or object.
+   */
+  get urlbar() {
+    if (!this._urlbar) {
+      this.textbox = document.getElementById("urlbar");
+      this._updateBinding();
+      if (this.quantumbar) {
+        this._urlbar = new UrlbarInput({textbox: this.textbox});
+        if (this._lastValue) {
+          this._urlbar.value = this._lastValue;
+          delete this._lastValue;
+        }
+      } else {
+        this._urlbar = this.textbox;
+      }
+      gBrowser.tabContainer.addEventListener("TabSelect", this._urlbar);
+    }
+    return this._urlbar;
+  },
+
+  /**
+   * Invoked when the quantumbar pref changes.
+   */
+  handlePrefChange() {
+    this._updateBinding();
+    this._reset();
+  },
+
+  /**
+   * Invoked by CustomizationHandler when a customization ends.
+   */
+  customizeEnd() {
+    this._reset();
+  },
+
+  /**
+   * Rebuilds the textbox binding by detaching the element when necessary.
+   */
+  _updateBinding() {
+    let quantumbarApplied = this.textbox.getAttribute("quantumbar") == "true";
+    if (quantumbarApplied != this.quantumbar) {
+      let placeholder = document.createXULElement("toolbarpaletteitem");
+      let parent = this.textbox.parentNode;
+      parent.replaceChild(placeholder, this.textbox);
+      this.textbox.setAttribute("quantumbar", this.quantumbar);
+      parent.replaceChild(this.textbox, placeholder);
+    }
+  },
+
+  /**
+   *  Used to reset the gURLBar value.
+   */
+  _reset() {
+    if (this._urlbar) {
+      gBrowser.tabContainer.removeEventListener("TabSelect", this._urlbar);
+      if (this._urlbar.constructor.name == "UrlbarInput") {
+        this._lastValue = this._urlbar.value;
+        this._urlbar.uninit();
+      }
+      delete this._urlbar;
+      gURLBar = this.urlbar;
+    }
+  },
+};
+
+XPCOMUtils.defineLazyPreferenceGetter(gURLBarHandler, "quantumbar",
+                                      "browser.urlbar.quantumbar", false,
+                                      gURLBarHandler.handlePrefChange.bind(gURLBarHandler));
 
 // High priority notification bars shown at the top of the window.
 XPCOMUtils.defineLazyGetter(this, "gHighPriorityNotificationBox", () => {
   return new MozElements.NotificationBox(element => {
     element.classList.add("global-notificationbox");
     element.setAttribute("notificationside", "top");
     document.getElementById("appcontent").prepend(element);
   });
@@ -1583,18 +1644,16 @@ var gBrowserInit = {
       // Enable the Restore Last Session command if needed
       RestoreLastSessionObserver.init();
 
       SidebarUI.startDelayedLoad();
 
       PanicButtonNotifier.init();
     });
 
-    gBrowser.tabContainer.addEventListener("TabSelect", gURLBar);
-
     gBrowser.tabContainer.addEventListener("TabSelect", function() {
       for (let panel of document.querySelectorAll("panel[tabspecific='true']")) {
         if (panel.state == "open") {
           panel.hidePopup();
         }
       }
     });
 
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -257,37 +257,16 @@ xmlns="http://www.w3.org/1999/xhtml"
            id="PopupAutoCompleteRichResult"
            role="group"
            noautofocus="true"
            hidden="true"
            flip="none"
            level="parent"
            overflowpadding="15" />
 
-    <!-- for url bar autocomplete -->
-    <panel id="urlbar-results"
-           role="group"
-           noautofocus="true"
-           hidden="true"
-           flip="none"
-           consumeoutsideclicks="never"
-           norolluponanchor="true"
-           level="parent">
-      <html:div class="urlbarView-body-outer">
-        <html:div class="urlbarView-body-inner">
-          <!-- TODO: add search suggestions notification -->
-          <html:div class="urlbarView-results"/>
-        </html:div>
-      </html:div>
-      <hbox class="search-one-offs"
-            compact="true"
-            includecurrentengine="true"
-            disabletab="true"/>
-    </panel>
-
     <!-- for date/time picker. consumeoutsideclicks is set to never, so that
          clicks on the anchored input box are never consumed. -->
     <panel id="DateTimePickerPanel"
            type="arrow"
            hidden="true"
            orient="vertical"
            noautofocus="true"
            norolluponanchor="true"
@@ -892,16 +871,17 @@ xmlns="http://www.w3.org/1999/xhtml"
                      removable="false"
                      class="chromeclass-location" overflows="false">
             <toolbartabstop/>
             <textbox id="urlbar" flex="1"
                      placeholder="&urlbar.placeholder2;"
                      defaultPlaceholder="&urlbar.placeholder2;"
                      focused="true"
                      type="autocomplete"
+                     quantumbar="false"
                      autocompletesearch="unifiedcomplete"
                      autocompletesearchparam="enable-actions"
                      autocompletepopup="PopupAutoCompleteRichResult"
                      completeselectedindex="true"
                      tabscrolling="true"
                      newlines="stripsurroundingwhitespace"
                      ontextentered="this.handleCommand(param);"
                      ontextreverted="return this.handleRevert();"
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -176,17 +176,23 @@ file, You can obtain one at http://mozil
         // Somehow, it's possible for the XBL destructor to fire without the
         // constructor ever having fired. Fix:
         if (!this._prefs) {
           return;
         }
         this._prefs.removeObserver("", this);
         this._prefs = null;
         Services.prefs.removeObserver("browser.search.suggest.enabled", this);
-        this.inputField.controllers.removeController(this._copyCutController);
+        try {
+          this.inputField.controllers.removeController(this._copyCutController);
+        } catch (ex) {
+          // Sometimes this fails for unclear reasons; anyway we want to
+          // continue cleaning up.
+          Cu.reportError(ex);
+        }
         this.inputField.removeEventListener("paste", this);
         this.inputField.removeEventListener("mousedown", this);
         this.inputField.removeEventListener("mouseover", this);
         this.inputField.removeEventListener("overflow", this);
         this.inputField.removeEventListener("underflow", this);
         this.inputField.removeEventListener("scrollend", this);
         window.removeEventListener("resize", this);
 
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -34,29 +34,54 @@ XPCOMUtils.defineLazyServiceGetter(this,
  * Also forwards important textbox properties and methods.
  */
 class UrlbarInput {
   /**
    * @param {object} options
    *   The initial options for UrlbarInput.
    * @param {object} options.textbox
    *   The <textbox> element.
-   * @param {object} options.panel
-   *   The <panel> element.
    * @param {UrlbarController} [options.controller]
    *   Optional fake controller to override the built-in UrlbarController.
    *   Intended for use in unit tests only.
    */
   constructor(options = {}) {
     this.textbox = options.textbox;
     this.textbox.clickSelectsAll = UrlbarPrefs.get("clickSelectsAll");
 
-    this.panel = options.panel;
     this.window = this.textbox.ownerGlobal;
     this.document = this.window.document;
+
+    // Create the panel to contain results.
+    // In the future this may be moved to the view, so it can customize
+    // the container element.
+    let MozXULElement = this.window.MozXULElement;
+    this.document.getElementById("mainPopupSet").appendChild(
+      MozXULElement.parseXULToFragment(`
+        <panel id="urlbar-results"
+                role="group"
+                noautofocus="true"
+                hidden="true"
+                flip="none"
+                consumeoutsideclicks="never"
+                norolluponanchor="true"
+                level="parent">
+          <html:div class="urlbarView-body-outer">
+            <html:div class="urlbarView-body-inner">
+              <html:div class="urlbarView-results"/>
+            </html:div>
+          </html:div>
+          <hbox class="search-one-offs"
+                compact="true"
+                includecurrentengine="true"
+                disabletab="true"/>
+        </panel>
+      `));
+    this.panel = this.document.getElementById("urlbar-results");
+
     this.controller = options.controller || new UrlbarController({
       browserWindow: this.window,
     });
     this.controller.setInput(this);
     this.view = new UrlbarView(this);
     this.valueIsTyped = false;
     this.userInitiatedFocus = false;
     this.isPrivate = PrivateBrowsingUtils.isWindowPrivate(this.window);
@@ -127,16 +152,45 @@ class UrlbarInput {
     this.inputField.controllers.insertControllerAt(0, new CopyCutController(this));
     this._initPasteAndGo();
 
     // Tracks IME composition.
     this._compositionState == UrlbarUtils.COMPOSITION.NONE;
   }
 
   /**
+   * Uninitializes this input object, detaching it from the inputField.
+   */
+  uninit() {
+    this.inputField.removeEventListener("blur", this.eventBufferer);
+    this.inputField.removeEventListener("keydown", this.eventBufferer);
+    delete this.eventBufferer;
+    const inputFieldEvents = [
+      "focus", "input", "keyup", "mouseover", "paste", "scrollend", "select",
+      "overflow", "underflow", "dragstart", "dragover", "drop",
+    ];
+    for (let name of inputFieldEvents) {
+      this.inputField.removeEventListener(name, this);
+    }
+    this.removeEventListener("mousedown", this);
+
+    this.view.panel.remove();
+
+    this.inputField.controllers.removeControllerAt(0);
+
+    delete this.document;
+    delete this.window;
+    delete this.valueFormatter;
+    delete this.panel;
+    delete this.view;
+    delete this.controller;
+    delete this.textbox;
+  }
+
+  /**
    * Shortens the given value, usually by removing http:// and trailing slashes,
    * such that calling nsIURIFixup::createFixupURI with the result will produce
    * the same URI.
    *
    * @param {string} val
    *   The string to be trimmed if it appears to be URI
    * @returns {string}
    *   The trimmed string
--- a/browser/components/urlbar/tests/browser/browser_UrlbarInput_unit.js
+++ b/browser/components/urlbar/tests/browser/browser_UrlbarInput_unit.js
@@ -72,22 +72,21 @@ async function withNewWindow(callback) {
 
   win.gBrowser = {};
 
   // Clone the elements into the new window, so we get exact copies without having
   // to replicate the xul.
   let doc = win.document;
   let textbox = doc.importNode(document.getElementById("urlbar"), true);
   doc.documentElement.appendChild(textbox);
-  let panel = doc.importNode(document.getElementById("urlbar-results"), true);
-  doc.documentElement.appendChild(panel);
+  let popupset = doc.importNode(document.getElementById("mainPopupSet"), true);
+  doc.documentElement.appendChild(popupset);
 
   let inputOptions = {
     textbox,
-    panel,
     controller: fakeController,
   };
 
   let input = new UrlbarInput(inputOptions);
 
   await callback(input);
 
   await BrowserTestUtils.closeWindow(win);
--- a/browser/themes/shared/urlbar-searchbar.inc.css
+++ b/browser/themes/shared/urlbar-searchbar.inc.css
@@ -329,20 +329,18 @@
 
 .urlbar-history-dropmarker {
   -moz-appearance: none;
   list-style-image: url(chrome://global/skin/icons/arrow-dropdown-16.svg);
   transition: opacity 0.15s ease;
 }
 
 /* Avoid re-opening the popup when the dropmarker is clicked while the popup is still open. */
-@supports not -moz-bool-pref("browser.urlbar.quantumbar") {
-  .urlbar-history-dropmarker[open] {
-    pointer-events: none;
-  }
+#urlbar[quantumbar="false"] > .urlbar-history-dropmarker[open] {
+  pointer-events: none;
 }
 
 #urlbar[switchingtabs] > .urlbar-history-dropmarker {
   transition: none;
 }
 
 #nav-bar:not([customizing="true"]) > #nav-bar-customization-target > #urlbar-container:not(:hover) > #urlbar:not([focused]) > .urlbar-history-dropmarker,
 #nav-bar:not([customizing="true"]) > #nav-bar-customization-target > #urlbar-container:not(:hover) > #urlbar.hidden-focus > .urlbar-history-dropmarker {