Bug 462674 - URLBar: Autocomplete "about:" URLs r=mak
authorNicolas Ouellet-Payeur <nicolaso@google.com>
Fri, 21 Sep 2018 00:31:02 +0000
changeset 493331 442b0454a61826fe77accb0b73851a4fb21f08c6
parent 493330 2b5b7bd869aca6e972b7d8eef943ebce83720151
child 493332 d5b484dc6344b3b551d595d8f652ae82a32aeb84
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak
bugs462674
milestone64.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 462674 - URLBar: Autocomplete "about:" URLs r=mak Pages that are whitelisted for displaying on about:about can be autocompleted in the URL bar. MozReview-Commit-ID: BYhWUImyiJH Differential Revision: https://phabricator.services.mozilla.com/D3072
toolkit/components/places/UnifiedComplete.js
toolkit/components/places/tests/unifiedcomplete/test_autofill_about_urls.js
toolkit/components/places/tests/unifiedcomplete/test_visit_url.js
toolkit/components/places/tests/unifiedcomplete/xpcshell.ini
toolkit/content/aboutAbout.js
toolkit/modules/AboutPagesUtils.jsm
toolkit/modules/moz.build
tools/lint/eslint/modules.json
--- a/toolkit/components/places/UnifiedComplete.js
+++ b/toolkit/components/places/UnifiedComplete.js
@@ -329,16 +329,17 @@ const SQL_URL_PREFIX_BOOKMARKED_QUERY = 
 // Getters
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]);
 
 XPCOMUtils.defineLazyModuleGetters(this, {
+  AboutPagesUtils: "resource://gre/modules/AboutPagesUtils.jsm",
   BrowserUtils: "resource://gre/modules/BrowserUtils.jsm",
   ExtensionSearchHandler: "resource://gre/modules/ExtensionSearchHandler.jsm",
   OS: "resource://gre/modules/osfile.jsm",
   PlacesRemoteTabsAutocompleteProvider: "resource://gre/modules/PlacesRemoteTabsAutocompleteProvider.jsm",
   PlacesSearchAutocompleteProvider: "resource://gre/modules/PlacesSearchAutocompleteProvider.jsm",
   PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
   ProfileAge: "resource://gre/modules/ProfileAge.jsm",
   Sqlite: "resource://gre/modules/Sqlite.jsm",
@@ -1015,16 +1016,18 @@ Search.prototype = {
           this._currentMatchCount < UrlbarPrefs.get("maxRichResults")) {
       this._addMatch(this._extraRemoteTabRows.shift());
     }
 
     // Ideally we should wait until MATCH_BOUNDARY_ANYWHERE, but that query
     // may be really slow and we may end up showing old results for too long.
     this._cleanUpNonCurrentMatches(UrlbarUtils.MATCHTYPE.GENERAL);
 
+    this._matchAboutPages();
+
     // If we do not have enough results, and our match type is
     // MATCH_BOUNDARY_ANYWHERE, search again with MATCH_ANYWHERE to get more
     // results.
     let count = this._counts[UrlbarUtils.MATCHTYPE.GENERAL] +
                 this._counts[UrlbarUtils.MATCHTYPE.HEURISTIC];
     if (this._matchBehavior == MATCH_BOUNDARY_ANYWHERE &&
         count < UrlbarPrefs.get("maxRichResults")) {
       this._matchBehavior = MATCH_ANYWHERE;
@@ -1038,16 +1041,56 @@ Search.prototype = {
 
     this._matchPreloadedSites();
 
     // Ensure to fill any remaining space.
     await searchSuggestionsCompletePromise;
     await extensionsCompletePromise;
   },
 
+  _shouldMatchAboutPages() {
+    // Only autocomplete input that starts with 'about:' and has at least 1 more
+    // character.
+    return (this._strippedPrefix == "about:" &&
+            this._searchString);
+  },
+
+  _matchAboutPages() {
+    if (!this._shouldMatchAboutPages()) {
+      return;
+    }
+    for (const url of AboutPagesUtils.visibleAboutUrls) {
+      if (url.startsWith(`about:${this._searchString}`)) {
+        this._addMatch({
+          value: url,
+          comment: url,
+          frecency: FRECENCY_DEFAULT,
+        });
+      }
+    }
+  },
+
+  _matchAboutPageForAutofill() {
+    if (!this._shouldMatchAboutPages()) {
+      return false;
+    }
+    for (const url of AboutPagesUtils.visibleAboutUrls) {
+      if (url.startsWith(`about:${this._searchString}`)) {
+        this._addAutofillMatch(
+          url.replace(/^about:/, ""),
+          url,
+          Infinity,
+          []
+        );
+        return true;
+      }
+    }
+    return false;
+  },
+
   async _checkPreloadedSitesExpiry() {
     if (!UrlbarPrefs.get("usepreloadedtopurls.enabled"))
       return;
     let profileCreationDate = await ProfileAgeCreatedPromise;
     let daysSinceProfileCreation = (Date.now() - profileCreationDate) / MS_PER_DAY;
     if (daysSinceProfileCreation > UrlbarPrefs.get("usepreloadedtopurls.expire_days"))
       Services.prefs.setBoolPref("browser.urlbar.usepreloadedtopurls.enabled", false);
   },
@@ -1133,16 +1176,25 @@ Search.prototype = {
       // It may be a Places keyword.
       let matched = await this._matchPlacesKeyword();
       if (matched) {
         return true;
       }
     }
 
     let shouldAutofill = this._shouldAutofill;
+
+    if (this.pending && shouldAutofill) {
+      // It may also look like an about: link.
+      let matched = await this._matchAboutPageForAutofill();
+      if (matched) {
+        return true;
+      }
+    }
+
     if (this.pending && shouldAutofill) {
       // It may also look like a URL we know from the database.
       let matched = await this._matchKnownUrl(conn);
       if (matched) {
         return true;
       }
     }
 
@@ -2149,17 +2201,17 @@ Search.prototype = {
   get _shouldAutofill() {
     // First of all, check for the autoFill pref.
     if (!UrlbarPrefs.get("autoFill"))
       return false;
 
     if (this._searchTokens.length != 1)
       return false;
 
-    // autoFill can only cope with history or bookmarks entries.
+    // autoFill can only cope with history, bookmarks, and about: entries.
     if (!this.hasBehavior("history") &&
         !this.hasBehavior("bookmark"))
       return false;
 
     // autoFill doesn't search titles or tags.
     if (this.hasBehavior("title") || this.hasBehavior("tag"))
       return false;
 
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/unifiedcomplete/test_autofill_about_urls.js
@@ -0,0 +1,60 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// "about:ab" should match "about:about"
+add_task(async function aboutAb() {
+  await check_autocomplete({
+    search: "about:ab",
+    autofilled: "about:ab",
+    completed: "about:ab",
+    matches: [{
+      value: "about:about",
+      comment: "about:about",
+      style: ["autofill", "heuristic"],
+    }],
+  });
+});
+
+// "about:about" should match "about:about"
+add_task(async function aboutAbout() {
+  await check_autocomplete({
+    search: "about:about",
+    autofilled: "about:about",
+    completed: "about:about",
+    matches: [{
+      value: "about:about",
+      comment: "about:about",
+      style: ["autofill", "heuristic"],
+    }],
+  });
+});
+
+// "about:a" should complete to "about:about" and also match "about:addons"
+add_task(async function aboutAboutAndAboutAddons() {
+  await check_autocomplete({
+    search: "about:a",
+    autofilled: "about:a",
+    completed: "about:a",
+    matches: [{
+      value: "about:about",
+      comment: "about:about",
+      style: ["autofill", "heuristic"],
+    }, {
+      value: "about:addons",
+      comment: "about:addons",
+    }],
+  });
+});
+
+// "about:" should *not* match anything
+add_task(async function aboutColonHasNoMatch() {
+  await check_autocomplete({
+    search: "about:",
+    autofilled: "about:",
+    completed: "about:",
+    matches: [],
+  });
+});
--- a/toolkit/components/places/tests/unifiedcomplete/test_visit_url.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_visit_url.js
@@ -44,19 +44,19 @@ add_task(async function() {
   await check_autocomplete({
     search: "https://mozilla.org",
     searchParam: "enable-actions",
     matches: [ { uri: makeActionURI("visiturl", {url: "https://mozilla.org/", input: "https://mozilla.org"}), title: "https://mozilla.org/", style: [ "action", "visiturl", "heuristic" ] } ],
   });
 
   info("visit url, about: protocol (no host)");
   await check_autocomplete({
-    search: "about:config",
+    search: "about:nonexistent",
     searchParam: "enable-actions",
-    matches: [ { uri: makeActionURI("visiturl", {url: "about:config", input: "about:config"}), title: "about:config", style: [ "action", "visiturl", "heuristic" ] } ],
+    matches: [ { uri: makeActionURI("visiturl", {url: "about:nonexistent", input: "about:nonexistent"}), title: "about:nonexistent", style: [ "action", "visiturl", "heuristic" ] } ],
   });
 
   info("visit url, with non-standard whitespace");
   await check_autocomplete({
     search: "https://www.mozilla.org\u2028",
     searchParam: "enable-actions",
     matches: [{
       uri: makeActionURI("visiturl", {url: "https://www.mozilla.org/",
--- a/toolkit/components/places/tests/unifiedcomplete/xpcshell.ini
+++ b/toolkit/components/places/tests/unifiedcomplete/xpcshell.ini
@@ -14,16 +14,17 @@ support-files =
 [test_417798.js]
 [test_418257.js]
 [test_422277.js]
 [test_adaptive.js]
 [test_adaptive_behaviors.js]
 [test_adaptive_limited.js]
 [test_autocomplete_functional.js]
 [test_autocomplete_stopSearch_no_throw.js]
+[test_autofill_about_urls.js]
 [test_autofill_origins.js]
 [test_autofill_search_engines.js]
 [test_autofill_urls.js]
 [test_avoid_middle_complete.js]
 [test_avoid_stripping_to_empty_tokens.js]
 [test_casing.js]
 [test_do_not_trim.js]
 [test_download_embed_bookmarks.js]
--- a/toolkit/content/aboutAbout.js
+++ b/toolkit/content/aboutAbout.js
@@ -1,46 +1,22 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-ChromeUtils.import("resource://gre/modules/Services.jsm");
+ChromeUtils.import("resource://gre/modules/AboutPagesUtils.jsm");
 
-var gProtocols = [];
 var gContainer;
 window.onload = function() {
   gContainer = document.getElementById("abouts");
-  findAbouts();
+  AboutPagesUtils.visibleAboutUrls.forEach(createProtocolListing);
 };
 
-function findAbouts() {
-  for (var cid in Cc) {
-    var result = cid.match(/@mozilla.org\/network\/protocol\/about;1\?what\=(.*)$/);
-    if (result) {
-      var aboutType = result[1];
-      var contract = "@mozilla.org/network/protocol/about;1?what=" + aboutType;
-      try {
-        var am = Cc[contract].getService(Ci.nsIAboutModule);
-        var uri = Services.io.newURI("about:" + aboutType);
-        var flags = am.getURIFlags(uri);
-        if (!(flags & Ci.nsIAboutModule.HIDE_FROM_ABOUTABOUT)) {
-          gProtocols.push(aboutType);
-        }
-      } catch (e) {
-        // getService might have thrown if the component doesn't actually
-        // implement nsIAboutModule
-      }
-    }
-  }
-  gProtocols.sort().forEach(createProtocolListing);
-}
-
-function createProtocolListing(aProtocol) {
-  var uri = "about:" + aProtocol;
+function createProtocolListing(aUrl) {
   var li = document.createElement("li");
   var link = document.createElement("a");
-  var text = document.createTextNode(uri);
+  var text = document.createTextNode(aUrl);
 
-  link.href = uri;
+  link.href = aUrl;
   link.appendChild(text);
   li.appendChild(link);
   gContainer.appendChild(li);
 }
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/AboutPagesUtils.jsm
@@ -0,0 +1,39 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["AboutPagesUtils"];
+
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const AboutPagesUtils = {};
+
+XPCOMUtils.defineLazyGetter(AboutPagesUtils, "visibleAboutUrls", () => {
+  const urls = [];
+  const rx = /@mozilla.org\/network\/protocol\/about;1\?what\=(.*)$/;
+  for (const cid in Cc) {
+    const result = cid.match(rx);
+    if (!result) {
+      continue;
+    }
+    const [, aboutType] = result;
+    try {
+      const am = Cc[cid].getService(Ci.nsIAboutModule);
+      const uri = Services.io.newURI(`about:${aboutType}`);
+      const flags = am.getURIFlags(uri);
+      if (!(flags & Ci.nsIAboutModule.HIDE_FROM_ABOUTABOUT)) {
+        urls.push(`about:${aboutType}`);
+      }
+    } catch (e) {
+      // getService() might have thrown if the component doesn't actually
+      // implement nsIAboutModule
+    }
+  }
+  urls.sort();
+  return urls;
+});
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -161,16 +161,17 @@ TESTING_JS_MODULES += [
 ]
 
 SPHINX_TREES['toolkit_modules'] = 'docs'
 
 with Files('docs/**'):
     SCHEDULES.exclusive = ['docs']
 
 EXTRA_JS_MODULES += [
+    'AboutPagesUtils.jsm',
     'ActorChild.jsm',
     'ActorManagerChild.jsm',
     'ActorManagerParent.jsm',
     'addons/MatchURLFilters.jsm',
     'addons/SecurityInfo.jsm',
     'addons/WebNavigation.jsm',
     'addons/WebNavigationContent.js',
     'addons/WebNavigationFrames.jsm',
--- a/tools/lint/eslint/modules.json
+++ b/tools/lint/eslint/modules.json
@@ -1,9 +1,10 @@
 {
+  "AboutPagesUtils.jsm": ["AboutPagesUtils"],
   "AddonManager.jsm": ["AddonManager", "AddonManagerPrivate"],
   "addons.js": ["AddonsEngine", "AddonValidator"],
   "addons.jsm": ["Addon", "STATE_ENABLED", "STATE_DISABLED"],
   "addonsreconciler.js": ["AddonsReconciler", "CHANGE_INSTALLED", "CHANGE_UNINSTALLED", "CHANGE_ENABLED", "CHANGE_DISABLED"],
   "AddonTestUtils.jsm": ["AddonTestUtils", "MockAsyncShutdown"],
   "addonutils.js": ["AddonUtils"],
   "ajv-4.1.1.js": ["Ajv"],
   "AlertsHelper.jsm": [],