Bug 1547301 - Add openViewOnFocus API. r=mak,mixedpuppy
authorDão Gottwald <dao@mozilla.com>
Wed, 19 Jun 2019 00:46:40 +0000
changeset 479164 674aabaec2c45fbec631dde9d1d65456924b2dc1
parent 479162 5f0f3775605323d865539da3a7c4cb3d1fd36ff2
child 479165 a87fb6dc17bfb0046fa6a4fb572f81c3369b8d9b
push id36172
push userrgurzau@mozilla.com
push dateWed, 19 Jun 2019 09:55:37 +0000
treeherdermozilla-central@8bce401b8833 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak, mixedpuppy
bugs1547301
milestone69.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 1547301 - Add openViewOnFocus API. r=mak,mixedpuppy Differential Revision: https://phabricator.services.mozilla.com/D34509
browser/app/profile/firefox.js
browser/components/extensions/parent/ext-urlbar.js
browser/components/extensions/schemas/urlbar.json
browser/components/extensions/test/xpcshell/test_ext_urlbar.js
browser/components/urlbar/UrlbarInput.jsm
browser/components/urlbar/UrlbarPrefs.jsm
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -326,16 +326,18 @@ pref("browser.urlbar.openintab", false);
 
 // This is disabled until Bug 1340663 figures out the remaining requirements.
 pref("browser.urlbar.usepreloadedtopurls.enabled", false);
 pref("browser.urlbar.usepreloadedtopurls.expire_days", 14);
 
 // Enable the new Address Bar code.
 pref("browser.urlbar.quantumbar", true);
 
+pref("browser.urlbar.openViewOnFocus", false);
+
 pref("browser.altClickSave", false);
 
 // Enable logging downloads operations to the Console.
 pref("browser.download.loglevel", "Error");
 
 // Number of milliseconds to wait for the http headers (and thus
 // the Content-Disposition filename) before giving up and falling back to
 // picking a filename without that info in hand so that the user sees some
--- a/browser/components/extensions/parent/ext-urlbar.js
+++ b/browser/components/extensions/parent/ext-urlbar.js
@@ -1,14 +1,27 @@
 "use strict";
 
 XPCOMUtils.defineLazyModuleGetters(this, {
+  UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
   UrlbarProvidersManager: "resource:///modules/UrlbarProvidersManager.jsm",
 });
 
+var {ExtensionPreferencesManager} = ChromeUtils.import("resource://gre/modules/ExtensionPreferencesManager.jsm");
+var {getSettingsAPI} = ExtensionPreferencesManager;
+
+ExtensionPreferencesManager.addSetting("openViewOnFocus", {
+  prefNames: [
+    "browser.urlbar.openViewOnFocus",
+  ],
+  setCallback(value) {
+    return {[this.prefNames[0]]: value};
+  },
+});
+
 this.urlbar = class extends ExtensionAPI {
   getAPI(context) {
     return {
       urlbar: {
         /**
          * Event fired before a search starts, to get the provider behavior.
          */
         onQueryReady: new EventManager(context, "urlbar.onQueryReady", (fire, name) => {
@@ -18,12 +31,18 @@ this.urlbar = class extends ExtensionAPI
                 return Promise.resolve("inactive");
               }
               return fire.async(queryContext);
             });
           return () => {
             UrlbarProvidersManager.removeExtensionListener(name, "queryready");
           };
         }).api(),
+
+        openViewOnFocus: getSettingsAPI(
+          context.extension.id, "openViewOnFocus",
+          () => {
+            return UrlbarPrefs.get("openViewOnFocus");
+          }),
       },
     };
   }
 };
--- a/browser/components/extensions/schemas/urlbar.json
+++ b/browser/components/extensions/schemas/urlbar.json
@@ -50,16 +50,22 @@
             "description": "List of acceptable SourceType to return.",
             "items": {
               "$ref": "SourceType"
             }
           }
         }
       }
     ],
+    "properties": {
+      "openViewOnFocus": {
+        "$ref": "types.Setting",
+        "description": "Enables or disables the open-view-on-focus mode."
+      }
+    },
     "events": [
       {
         "name": "onQueryReady",
         "type": "function",
         "description": "Fired before starting a search to get the provider's behavior.",
         "parameters": [
           {
             "name": "context",
--- a/browser/components/extensions/test/xpcshell/test_ext_urlbar.js
+++ b/browser/components/extensions/test/xpcshell/test_ext_urlbar.js
@@ -1,16 +1,27 @@
 "use strict";
 
+const {AddonTestUtils} = ChromeUtils.import("resource://testing-common/AddonTestUtils.jsm");
+
 XPCOMUtils.defineLazyModuleGetters(this, {
   UrlbarController: "resource:///modules/UrlbarController.jsm",
+  UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
   UrlbarProvidersManager: "resource:///modules/UrlbarProvidersManager.jsm",
   UrlbarQueryContext: "resource:///modules/UrlbarUtils.jsm",
 });
 
+AddonTestUtils.init(this);
+AddonTestUtils.overrideCertDB();
+AddonTestUtils.createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
+
+add_task(async function startup() {
+  await AddonTestUtils.promiseStartupManager();
+});
+
 add_task(async function test_urlbar_without_urlbar_permission() {
   let ext = ExtensionTestUtils.loadExtension({
     isPrivileged: true,
     background() {
       browser.test.assertEq(browser.urlbar, undefined,
                             "'urlbar' permission is required");
     },
   });
@@ -174,8 +185,36 @@ add_task(async function test_registerPro
   }
 
   await ext.unload();
 
   // Sanity check the providers.
   Assert.deepEqual(UrlbarProvidersManager.providers, providers,
                    "Should return to the default providers");
 });
+
+add_task(async function test_setOpenViewOnFocus() {
+  let getPrefValue = () => UrlbarPrefs.get("openViewOnFocus");
+
+  Assert.equal(getPrefValue(), false,
+               "Open-view-on-focus mode should be disabled by default");
+
+  let ext = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["urlbar"],
+    },
+    isPrivileged: true,
+    incognitoOverride: "spanning",
+    useAddonManager: "temporary",
+    background() {
+      browser.urlbar.openViewOnFocus.set({value: true});
+    },
+  });
+  await ext.startup();
+
+  Assert.equal(getPrefValue(), true,
+               "Successfully enabled the open-view-on-focus mode");
+
+  await ext.unload();
+
+  Assert.equal(getPrefValue(), false,
+               "Open-view-on-focus mode should be reset after unloading the add-on");
+});
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -44,16 +44,17 @@ class UrlbarInput {
    *   Intended for use in unit tests only.
    */
   constructor(options = {}) {
     this.textbox = options.textbox;
     this.textbox.clickSelectsAll = UrlbarPrefs.get("clickSelectsAll");
 
     this.window = this.textbox.ownerGlobal;
     this.document = this.window.document;
+    this.window.addEventListener("unload", this);
 
     // 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;
     // TODO Bug 1535659: urlbarView-body-inner possibly doesn't need the
     // role="combobox" once bug 1513337 is fixed.
     this.document.getElementById("mainPopupSet").appendChild(
@@ -92,17 +93,16 @@ class UrlbarInput {
     this.isPrivate = PrivateBrowsingUtils.isWindowPrivate(this.window);
     this.lastQueryContextPromise = Promise.resolve();
     this._actionOverrideKeyCount = 0;
     this._autofillPlaceholder = "";
     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",
       "getAttribute", "hasAttribute",
       "setAttribute", "removeAttribute", "toggleAttribute",
@@ -116,26 +116,26 @@ class UrlbarInput {
         return this.textbox[method](...args);
       };
     }
 
     for (let property of READ_ONLY_PROPERTIES) {
       Object.defineProperty(this, property, {
         enumerable: true,
         get() {
-          return this.textbox[property];
+          return this.textbox && this.textbox[property];
         },
       });
     }
 
     for (let property of READ_WRITE_PROPERTIES) {
       Object.defineProperty(this, property, {
         enumerable: true,
         get() {
-          return this.textbox[property];
+          return this.textbox && this.textbox[property];
         },
         set(val) {
           return this.textbox[property] = val;
         },
       });
     }
 
     XPCOMUtils.defineLazyGetter(this, "valueFormatter", () => {
@@ -174,22 +174,26 @@ class UrlbarInput {
     this._initPasteAndGo();
 
     // Tracks IME composition.
     this._compositionState = UrlbarUtils.COMPOSITION.NONE;
     this._compositionClosedPopup = false;
 
     this.editor.QueryInterface(Ci.nsIPlaintextEditor).newlineHandling =
       Ci.nsIPlaintextEditor.eNewlinesStripSurroundingWhitespace;
+
+    this._setOpenViewOnFocus();
+    Services.prefs.addObserver("browser.urlbar.openViewOnFocus", this);
   }
 
   /**
    * Uninitializes this input object, detaching it from the inputField.
    */
   uninit() {
+    this.window.removeEventListener("unload", this);
     for (let name of this._inputFieldEvents) {
       this.inputField.removeEventListener(name, this);
     }
     this.removeEventListener("mousedown", this);
 
     this.view.panel.remove();
 
     // When uninit is called due to exiting the browser's customize mode,
@@ -206,16 +210,18 @@ class UrlbarInput {
     } catch (ex) {
       Cu.reportError("Leaking UrlbarInput._copyCutController! You should have called removeCopyCutController!");
     }
 
     if (Object.getOwnPropertyDescriptor(this, "valueFormatter").get) {
       this.valueFormatter.uninit();
     }
 
+    Services.prefs.removeObserver("browser.urlbar.openViewOnFocus", this);
+
     delete this.document;
     delete this.window;
     delete this.eventBufferer;
     delete this.valueFormatter;
     delete this.panel;
     delete this.view;
     delete this.controller;
     delete this.textbox;
@@ -292,16 +298,24 @@ class UrlbarInput {
 
     try {
       return Services.uriFixup.createExposableURI(uri);
     } catch (ex) {}
 
     return uri;
   }
 
+  observe(subject, topic, data) {
+    switch (data) {
+      case "browser.urlbar.openViewOnFocus":
+        this._setOpenViewOnFocus();
+        break;
+    }
+  }
+
   /**
    * Passes DOM events for the textbox to the _on_<event type> methods.
    * @param {Event} event
    *   DOM event from the <textbox>.
    */
   handleEvent(event) {
     let methodName = "_on_" + event.type;
     if (methodName in this) {
@@ -716,23 +730,26 @@ class UrlbarInput {
   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.
+
+  _setOpenViewOnFocus() {
+    // FIXME: Not using UrlbarPrefs because its pref observer may run after
+    // this call, so we'd get the previous openViewOnFocus value here. This
+    // can be cleaned up after bug 1560013.
+    this._openViewOnFocus = Services.prefs.getBoolPref("browser.urlbar.openViewOnFocus");
+    this.toggleAttribute("hidedropmarker", this._openViewOnFocus);
   }
 
-  // Private methods below.
-
   _setValue(val, allowTrim) {
     this._untrimmedValue = val;
 
     let originalUrl = ReaderMode.getOriginalUrlObjectForDisplay(val);
     if (originalUrl) {
       val = originalUrl.displaySpec;
     }
 
@@ -1546,16 +1563,22 @@ class UrlbarInput {
       // For safety reasons, in the drop case we don't want to immediately show
       // the the dropped value, instead we want to keep showing the current page
       // url until an onLocationChange happens.
       // See the handling in URLBarSetURI for further details.
       this.window.gBrowser.userTypedValue = null;
       this.window.URLBarSetURI(null, true);
     }
   }
+
+  _on_unload() {
+    // FIXME: This is needed because uninit calls removePrefObserver. We can
+    // remove this once UrlbarPrefs has support for listeners. (bug 1560013)
+    this.uninit();
+  }
 }
 
 /**
  * Tries to extract droppable data from a DND event.
  * @param {Event} event The DND event to examine.
  * @returns {URL|string|null}
  *          null if there's a security reason for which we should do nothing.
  *          A URL object if it's a value we can load.
--- a/browser/components/urlbar/UrlbarPrefs.jsm
+++ b/browser/components/urlbar/UrlbarPrefs.jsm
@@ -95,16 +95,18 @@ const PREF_URLBAR_DEFAULTS = new Map([
 
   // One-off search buttons enabled status.
   ["oneOffSearches", false],
 
   // Whether addresses and search results typed into the address bar
   // should be opened in new tabs by default.
   ["openintab", false],
 
+  ["openViewOnFocus", false],
+
   // Whether the quantum bar is enabled.
   ["quantumbar", false],
 
   // Whether speculative connections should be enabled.
   ["speculativeConnect.enabled", true],
 
   // Results will include the user's bookmarks when this is true.
   ["suggest.bookmark", true],