Bug 1595641 - Add a focus option to browser.urlbar.search and add a browser.urlbar.focus function. r=mak,mixedpuppy
authorDrew Willcoxon <adw@mozilla.com>
Wed, 13 Nov 2019 14:36:56 +0000
changeset 501765 909f5eb0cc4807fb607a7ec225d3d1389c3bcf49
parent 501764 97a383ec65b7791c289a1ae523cd7181d3ec6093
child 501766 6387f7830d6646e39f8d1102d9d436985c2546c2
push id114172
push userdluca@mozilla.com
push dateTue, 19 Nov 2019 11:31:10 +0000
treeherdermozilla-inbound@b5c5ba07d3db [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak, mixedpuppy
bugs1595641
milestone72.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 1595641 - Add a focus option to browser.urlbar.search and add a browser.urlbar.focus function. r=mak,mixedpuppy Differential Revision: https://phabricator.services.mozilla.com/D52612
browser/components/extensions/parent/ext-urlbar.js
browser/components/extensions/schemas/urlbar.json
browser/components/extensions/test/browser/browser_ext_urlbar.js
browser/components/urlbar/UrlbarInput.jsm
--- a/browser/components/extensions/parent/ext-urlbar.js
+++ b/browser/components/extensions/parent/ext-urlbar.js
@@ -130,19 +130,28 @@ this.urlbar = class extends ExtensionAPI
         UrlbarContextualTip.removeClickListener(type, listener);
       };
     };
   }
 
   getAPI(context) {
     return {
       urlbar: {
-        search(searchString) {
+        focus(select = false) {
           let window = windowTracker.getTopNormalWindow(context);
-          window.gURLBar.search(searchString);
+          if (select) {
+            window.focusAndSelectUrlBar();
+          } else {
+            window.gURLBar.focus();
+          }
+        },
+
+        search(searchString, options = {}) {
+          let window = windowTracker.getTopNormalWindow(context);
+          window.gURLBar.search(searchString, options);
         },
 
         onBehaviorRequested: new EventManager({
           context,
           name: "urlbar.onBehaviorRequested",
           register: (fire, providerName) => {
             let provider = UrlbarProviderExtension.getOrCreate(providerName);
             provider.setEventListener(
--- a/browser/components/extensions/schemas/urlbar.json
+++ b/browser/components/extensions/schemas/urlbar.json
@@ -79,16 +79,29 @@
       },
       {
         "id": "ResultType",
         "type": "string",
         "enum": ["remote_tab", "search", "tab", "tip", "url"],
         "description": "Possible types of results. <code>remote_tab</code>: A synced tab from another device. <code>search</code>: A search suggestion from a search engine. <code>tab</code>: An open tab in the browser. <code>tip</code>: An actionable message to help the user with their query. <code>url</code>: A URL that's not one of the other types."
       },
       {
+        "id": "SearchOptions",
+        "type": "object",
+        "description": "Options to the <code>search</code> function.",
+        "properties": {
+          "focus": {
+            "type": "boolean",
+            "optional": true,
+            "default": true,
+            "description": "Whether to focus the input field and select its contents."
+          }
+        }
+      },
+      {
         "id": "SourceType",
         "type": "string",
         "enum": ["bookmarks", "history", "local", "network", "search", "tabs"],
         "description": "Possible sources of results. <code>bookmarks</code>: The result comes from the user's bookmarks. <code>history</code>: The result comes from the user's history. <code>local</code>: The result comes from some local source not covered by another source type. <code>network</code>: The result comes from some network source not covered by another source type. <code>search</code>: The result comes from a search engine. <code>tabs</code>: The result is an open tab in the browser or a synced tab from another device."
       }
     ],
     "properties": {
       "openViewOnFocus": {
@@ -97,25 +110,47 @@
       },
       "engagementTelemetry": {
         "$ref": "types.Setting",
         "description": "Enables or disables the engagement telemetry."
       }
     },
     "functions": [
       {
+        "name": "focus",
+        "type": "function",
+        "async": true,
+        "description": "Focuses the urlbar in the current window.",
+        "parameters": [
+          {
+            "name": "select",
+            "type": "boolean",
+            "optional": true,
+            "default": false,
+            "description": "If true, the text in the urlbar will also be selected."
+          }
+        ]
+      },
+      {
         "name": "search",
         "type": "function",
         "async": true,
         "description": "Starts a search in the urlbar in the current window.",
         "parameters": [
           {
             "name": "searchString",
             "type": "string",
             "description": "The search string."
+          },
+          {
+            "name": "options",
+            "$ref": "SearchOptions",
+            "optional": true,
+            "default": {},
+            "description": "Options for the search."
           }
         ]
       }
     ],
     "events": [
       {
         "name": "onBehaviorRequested",
         "type": "function",
--- a/browser/components/extensions/test/browser/browser_ext_urlbar.js
+++ b/browser/components/extensions/test/browser/browser_ext_urlbar.js
@@ -1,11 +1,13 @@
 "use strict";
 
 XPCOMUtils.defineLazyModuleGetters(this, {
+  PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
+  PlacesTestUtils: "resource://testing-common/PlacesTestUtils.jsm",
   UrlbarProviderExtension: "resource:///modules/UrlbarProviderExtension.jsm",
   UrlbarProvidersManager: "resource:///modules/UrlbarProvidersManager.jsm",
   UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.jsm",
 });
 
 async function loadTipExtension(options = {}) {
   let ext = ExtensionTestUtils.loadExtension({
     manifest: {
@@ -274,8 +276,151 @@ add_task(async function searchEmpty() {
   Assert.equal(context.results.length, 1);
   Assert.equal(context.results[0].type, UrlbarUtils.RESULT_TYPE.TIP);
   Assert.ok(gURLBar.focused);
   Assert.equal(gURLBar.getAttribute("focused"), "true");
 
   await UrlbarTestUtils.promisePopupClose(window);
   await ext.unload();
 });
+
+// Tests the search function with `focus: false`.
+add_task(async function searchFocusFalse() {
+  await PlacesUtils.history.clear();
+  await PlacesUtils.bookmarks.eraseEverything();
+  await PlacesTestUtils.addVisits([
+    "http://example.com/test1",
+    "http://example.com/test2",
+  ]);
+
+  gURLBar.blur();
+
+  let ext = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["urlbar"],
+    },
+    isPrivileged: true,
+    background: () => {
+      browser.urlbar.search("test", { focus: false });
+    },
+  });
+  await ext.startup();
+
+  let context = await UrlbarTestUtils.promiseSearchComplete(window);
+  Assert.equal(gURLBar.value, "test");
+  Assert.equal(context.searchString, "test");
+  Assert.ok(!gURLBar.focused);
+  Assert.ok(!gURLBar.hasAttribute("focused"));
+
+  let resultCount = UrlbarTestUtils.getResultCount(window);
+  Assert.equal(resultCount, 3);
+
+  let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+  Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.SEARCH);
+  Assert.equal(result.title, "test");
+
+  result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+  Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.URL);
+  Assert.equal(result.url, "http://example.com/test2");
+
+  result = await UrlbarTestUtils.getDetailsOfResultAt(window, 2);
+  Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.URL);
+  Assert.equal(result.url, "http://example.com/test1");
+
+  await UrlbarTestUtils.promisePopupClose(window);
+  await ext.unload();
+});
+
+// Tests the search function with `focus: false` and an empty string.
+add_task(async function searchFocusFalseEmpty() {
+  await PlacesUtils.history.clear();
+  await PlacesUtils.bookmarks.eraseEverything();
+  await PlacesTestUtils.addVisits([
+    "http://example.com/test1",
+    "http://example.com/test2",
+  ]);
+
+  gURLBar.blur();
+
+  let ext = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["urlbar"],
+    },
+    isPrivileged: true,
+    background: () => {
+      browser.urlbar.search("", { focus: false });
+    },
+  });
+  await ext.startup();
+
+  let context = await UrlbarTestUtils.promiseSearchComplete(window);
+  Assert.equal(gURLBar.value, "");
+  Assert.equal(context.searchString, "");
+  Assert.ok(!gURLBar.focused);
+  Assert.ok(!gURLBar.hasAttribute("focused"));
+
+  let resultCount = UrlbarTestUtils.getResultCount(window);
+  Assert.equal(resultCount, 2);
+
+  let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+  Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.URL);
+  Assert.equal(result.url, "http://example.com/test2");
+
+  result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+  Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.URL);
+  Assert.equal(result.url, "http://example.com/test1");
+
+  await UrlbarTestUtils.promisePopupClose(window);
+  await ext.unload();
+});
+
+// Tests the focus function with select = false.
+add_task(async function focusSelectFalse() {
+  gURLBar.blur();
+  gURLBar.value = "test";
+  Assert.ok(!gURLBar.focused);
+  Assert.ok(!gURLBar.hasAttribute("focused"));
+
+  let ext = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["urlbar"],
+    },
+    isPrivileged: true,
+    background: () => {
+      browser.urlbar.focus();
+    },
+  });
+  await ext.startup();
+
+  await TestUtils.waitForCondition(() => gURLBar.focused);
+  Assert.ok(gURLBar.focused);
+  Assert.ok(gURLBar.hasAttribute("focused"));
+  Assert.equal(gURLBar.selectionStart, gURLBar.selectionEnd);
+
+  await ext.unload();
+});
+
+// Tests the focus function with select = true.
+add_task(async function focusSelectTrue() {
+  gURLBar.blur();
+  gURLBar.value = "test";
+  Assert.ok(!gURLBar.focused);
+  Assert.ok(!gURLBar.hasAttribute("focused"));
+
+  let ext = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["urlbar"],
+    },
+    isPrivileged: true,
+    background: () => {
+      browser.urlbar.focus(true);
+    },
+  });
+  await ext.startup();
+
+  await TestUtils.waitForCondition(() => gURLBar.focused);
+  Assert.ok(gURLBar.focused);
+  Assert.ok(gURLBar.hasAttribute("focused"));
+  Assert.equal(gURLBar.selectionStart, 0);
+  Assert.equal(gURLBar.selectionEnd, "test".length);
+
+  await ext.unload();
+});
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -854,19 +854,24 @@ class UrlbarInput {
   }
 
   /**
    * Sets the input's value, starts a search, and opens the view.
    *
    * @param {string} value
    *   The input's value will be set to this value, and the search will
    *   use it as its query.
+   * @param {boolean} [options.focus]
+   *   If true, the urlbar will be focused.  If false, the focus will remain
+   *   unchanged.
    */
-  search(value) {
-    this.window.focusAndSelectUrlBar();
+  search(value, { focus = true } = {}) {
+    if (focus) {
+      this.window.focusAndSelectUrlBar();
+    }
 
     // If the value is a restricted token, append a space.
     if (Object.values(UrlbarTokenizer.RESTRICT).includes(value)) {
       this.inputField.value = value + " ";
       this._revertOnBlurValue = this.value;
     } else {
       this.inputField.value = value;
     }