Bug 1547299 - Implement open-view-on-focus mode. r=adw
authorDão Gottwald <dao@mozilla.com>
Fri, 17 May 2019 08:27:08 +0000
changeset 533121 057ee1d004ccdcc2f88d333824bc69080352914a
parent 533120 55e45fc9d529196284843ed9a192e19edda6515c
child 533122 83894232a6bd36b0cf6278f8c878a4f3143b92dd
push id11276
push userrgurzau@mozilla.com
push dateMon, 20 May 2019 13:11:24 +0000
treeherdermozilla-beta@847755a7c325 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersadw
bugs1547299
milestone68.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 1547299 - Implement open-view-on-focus mode. r=adw Differential Revision: https://phabricator.services.mozilla.com/D31419
browser/base/content/tabbrowser.js
browser/components/urlbar/UrlbarInput.jsm
browser/components/urlbar/UrlbarView.jsm
browser/themes/shared/urlbar-searchbar.inc.css
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -1173,17 +1173,19 @@ window._gBrowser = {
       }
     }
 
     // Focus the location bar if it was previously focused for that tab.
     // In full screen mode, only bother making the location bar visible
     // if the tab is a blank one.
     if (newBrowser._urlbarFocused && gURLBar) {
       // Explicitly close the popup if the URL bar retains focus
-      gURLBar.closePopup();
+      if (!gURLBar.openViewOnFocus) {
+        gURLBar.closePopup();
+      }
 
       // If the user happened to type into the URL bar for this browser
       // by the time we got here, focusing will cause the text to be
       // selected which could cause them to overwrite what they've
       // already typed in.
       if (gURLBar.focused && newBrowser.userTypedValue) {
         return;
       }
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -93,23 +93,25 @@ class UrlbarInput {
     this.lastQueryContextPromise = Promise.resolve();
     this._actionOverrideKeyCount = 0;
     this._autofillPlaceholder = "";
     this._deletedEndOfAutofillPlaceholder = false;
     this._lastSearchString = "";
     this._resultForCurrentValue = null;
     this._suppressStartQuery = false;
     this._untrimmedValue = "";
+    this._openViewOnFocus = false;
 
     // This exists only for tests.
     this._enableAutofillPlaceholder = true;
 
     // Forward textbox methods and properties.
     const METHODS = ["addEventListener", "removeEventListener",
-      "setAttribute", "hasAttribute", "removeAttribute", "getAttribute",
+      "getAttribute", "hasAttribute",
+      "setAttribute", "removeAttribute", "toggleAttribute",
       "select"];
     const READ_ONLY_PROPERTIES = ["inputField", "editor"];
     const READ_WRITE_PROPERTIES = ["placeholder", "readOnly",
       "selectionStart", "selectionEnd"];
 
     for (let method of METHODS) {
       this[method] = (...args) => {
         return this.textbox[method](...args);
@@ -465,17 +467,17 @@ class UrlbarInput {
         return;
       }
       case UrlbarUtils.RESULT_TYPE.SEARCH: {
         if (result.payload.isKeywordOffer) {
           // Picking a keyword offer just fills it in the input and doesn't
           // visit anything.  The user can then type a search string.  Also
           // start a new search so that the offer appears in the view by itself
           // to make it even clearer to the user what's going on.
-          this.startQuery();
+          this.startQuery({ allowEmptyInput: true });
           return;
         }
         const actionDetails = {
           isSuggestion: !!result.payload.suggestion,
           alias: result.payload.keyword,
         };
         const engine = Services.search.getEngineByName(result.payload.engine);
         this._recordSearch(engine, event, actionDetails);
@@ -607,17 +609,17 @@ class UrlbarInput {
    *   If true and the search string is empty, then the input will become empty
    *   when no result is selected.  If false, the input will continue showing
    *   the last non-empty search string when no result is selected.
    */
   startQuery({
     allowAutofill = true,
     searchString = null,
     resetSearchState = true,
-    allowEmptyInput = true,
+    allowEmptyInput = false,
   } = {}) {
     if (this._suppressStartQuery) {
       return;
     }
 
     if (resetSearchState) {
       this._resetSearchState();
     }
@@ -739,16 +741,25 @@ class UrlbarInput {
   get value() {
     return this._untrimmedValue;
   }
 
   set value(val) {
     return this._setValue(val, true);
   }
 
+  get openViewOnFocus() {
+    return this._openViewOnFocus;
+  }
+
+  set openViewOnFocus(val) {
+    this._openViewOnFocus = val;
+    this.toggleAttribute("hidedropmarker", val);
+  }
+
   // Private methods below.
 
   _setValue(val, allowTrim) {
     this._untrimmedValue = val;
 
     let originalUrl = ReaderMode.getOriginalUrlObjectForDisplay(val);
     if (originalUrl) {
       val = originalUrl.displaySpec;
@@ -1296,16 +1307,20 @@ class UrlbarInput {
   _on_focus(event) {
     this._updateUrlTooltip();
     this.formatValue();
 
     // Hide popup notifications, to reduce visual noise.
     if (this.getAttribute("pageproxystate") != "valid") {
       this.window.UpdatePopupNotificationsVisibility();
     }
+
+    if (this.openViewOnFocus) {
+      this.startQuery();
+    }
   }
 
   _on_mouseover(event) {
     this._updateUrlTooltip();
   }
 
   _on_mousedown(event) {
     if (event.originalTarget == this.inputField &&
@@ -1317,17 +1332,17 @@ class UrlbarInput {
       return;
     }
 
     if (event.originalTarget.classList.contains("urlbar-history-dropmarker") &&
         event.button == 0) {
       if (this.view.isOpen) {
         this.view.close();
       } else {
-        this.startQuery({ allowEmptyInput: false });
+        this.startQuery();
       }
     }
   }
 
   _on_input() {
     let value = this.textValue;
     this.valueIsTyped = true;
     let valueIsPasted = this._valueIsPasted;
@@ -1378,16 +1393,17 @@ class UrlbarInput {
     let deletedAutofilledSubstring =
       deletedEndOfAutofillPlaceholder && value == this._lastSearchString;
     let allowAutofill = !valueIsPasted &&
       this._maybeAutofillOnInput(value, deletedAutofilledSubstring);
 
     this.startQuery({
       searchString: value,
       allowAutofill,
+      allowEmptyInput: true,
       resetSearchState: false,
     });
   }
 
   _on_select(event) {
     if (!this.window.windowUtils.isHandlingUserInput) {
       // Register the editor listener we use to detect when the user deletes
       // autofilled substrings.  The editor is destroyed and removes all its
@@ -1471,16 +1487,19 @@ class UrlbarInput {
 
   _on_scrollend(event) {
     this._updateTextOverflow();
   }
 
   _on_TabSelect(event) {
     this._resetSearchState();
     this.controller.viewContextChanged();
+    if (this.focused && this.openViewOnFocus) {
+      this.startQuery();
+    }
   }
 
   _on_keydown(event) {
     // Due to event deferring, it's possible preventDefault() won't be invoked
     // soon enough to actually prevent some of the default behaviors, thus we
     // have to handle the event "twice". This first immediate call passes false
     // as second argument so that handleKeyNavigation will only simulate the
     // event handling, without actually executing actions.
--- a/browser/components/urlbar/UrlbarView.jsm
+++ b/browser/components/urlbar/UrlbarView.jsm
@@ -281,26 +281,16 @@ class UrlbarView {
       newSelectionIndex = this._queryContext.results.length - 1;
     }
     if (newSelectionIndex >= 0) {
       this.selectedIndex = newSelectionIndex;
     }
   }
 
   /**
-   * Notified when the view context changes, for example when switching tabs.
-   * It can be used to reset internal state tracking.
-   */
-  onViewContextChanged() {
-    // Clear rows, so that when reusing results we don't visually leak them
-    // across different contexts.
-    this._rows.textContent = "";
-  }
-
-  /**
    * Passes DOM events for the view to the _on_<event type> methods.
    * @param {Event} event
    *   DOM event from the <view>.
    */
   handleEvent(event) {
     let methodName = "_on_" + event.type;
     if (methodName in this) {
       this[methodName](event);
--- a/browser/themes/shared/urlbar-searchbar.inc.css
+++ b/browser/themes/shared/urlbar-searchbar.inc.css
@@ -343,16 +343,20 @@
 }
 
 .urlbar-history-dropmarker {
   -moz-appearance: none;
   list-style-image: url(chrome://global/skin/icons/arrow-dropdown-16.svg);
   transition: opacity 0.15s ease;
 }
 
+#urlbar[hidedropmarker] > .urlbar-history-dropmarker {
+  display: none;
+}
+
 /* Avoid re-opening the popup when the dropmarker is clicked while the popup is still open. */
 #urlbar[quantumbar="false"] > .urlbar-history-dropmarker[open] {
   pointer-events: none;
 }
 
 #urlbar[switchingtabs] > .urlbar-history-dropmarker {
   transition: none;
 }