Bug 1211726 - part 1: add results from a hardcoded list of top sites, r?gijs draft
authorSvetlana Orlik <sorlik@mozilla.com>
Mon, 02 Jan 2017 03:28:31 +0300
changeset 483488 c08dd938d988b6163e920a09dceca973d7b96894
parent 467243 07d7ecbf77e3be59797f16234d357a02bb38ed8b
child 486728 c918794093ef6f89fcb5519e409de651c4833296
child 486925 5bf343b08b0e0a98831431c595be54b6f0fa3e49
child 487049 b28a89f970be526f731239e45fa3d6f20e87fd46
push id45331
push userbmo:sveta.orlik.code@gmail.com
push dateTue, 14 Feb 2017 12:39:58 +0000
reviewersgijs
bugs1211726
milestone54.0a1
Bug 1211726 - part 1: add results from a hardcoded list of top sites, r?gijs MozReview-Commit-ID: 21FN4awJaXf
modules/libpref/init/all.js
testing/profiles/prefs_general.js
toolkit/components/places/UnifiedComplete.js
toolkit/components/places/mozIPlacesAutoComplete.idl
toolkit/components/places/tests/head_common.js
toolkit/components/places/tests/unifiedcomplete/test_prefill_sites.js
toolkit/components/places/tests/unifiedcomplete/xpcshell.ini
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1051,16 +1051,22 @@ pref("application.use_ns_plugin_finder",
 pref("browser.fixup.alternate.enabled", true);
 pref("browser.fixup.alternate.prefix", "www.");
 pref("browser.fixup.alternate.suffix", ".com");
 pref("browser.fixup.dns_first_for_single_words", false);
 pref("browser.fixup.hide_user_pass", true);
 
 // Location Bar AutoComplete
 pref("browser.urlbar.autocomplete.enabled", true);
+#ifdef NIGHTLY_BUILD
+pref("browser.urlbar.usepreloadedtopurls.enabled", true);
+#else
+pref("browser.urlbar.usepreloadedtopurls.enabled", false);
+#endif
+pref("browser.urlbar.usepreloadedtopurls.expire_days", 14);
 
 // Print header customization
 // Use the following codes:
 // &T - Title
 // &U - Document URL
 // &D - Date/Time
 // &P - Page Number
 // &PT - Page Number "of" Page total
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -333,16 +333,18 @@ user_pref("media.webspeech.synth.test", 
 // Turn off search suggestions in the location bar so as not to trigger network
 // connections.
 user_pref("browser.urlbar.suggest.searches", false);
 
 // Turn off the location bar search suggestions opt-in.  It interferes with
 // tests that don't expect it to be there.
 user_pref("browser.urlbar.userMadeSearchSuggestionsChoice", true);
 
+user_pref("browser.urlbar.usepreloadedtopurls.enabled", false);
+
 user_pref("dom.audiochannel.mutedByDefault", false);
 
 user_pref("webextensions.tests", true);
 user_pref("startup.homepage_welcome_url", "about:blank");
 user_pref("startup.homepage_welcome_url.additional", "");
 
 // For Firefox 52 only, ESR will support non-Flash plugins while release will
 // not, so we keep testing the non-Flash pathways
--- a/toolkit/components/places/UnifiedComplete.js
+++ b/toolkit/components/places/UnifiedComplete.js
@@ -5,16 +5,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 // Constants
 
 const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
 
+const MS_PER_DAY = 86400000; // 24 * 60 * 60 * 1000
+
 // Match type constants.
 // These indicate what type of search function we should be using.
 const MATCH_ANYWHERE = Ci.mozIPlacesAutoComplete.MATCH_ANYWHERE;
 const MATCH_BOUNDARY_ANYWHERE = Ci.mozIPlacesAutoComplete.MATCH_BOUNDARY_ANYWHERE;
 const MATCH_BOUNDARY = Ci.mozIPlacesAutoComplete.MATCH_BOUNDARY;
 const MATCH_BEGINNING = Ci.mozIPlacesAutoComplete.MATCH_BEGINNING;
 const MATCH_BEGINNING_CASE_SENSITIVE = Ci.mozIPlacesAutoComplete.MATCH_BEGINNING_CASE_SENSITIVE;
 
@@ -42,16 +44,19 @@ const PREF_MATCH_URL =              [ "m
 const PREF_SUGGEST_HISTORY =        [ "suggest.history",        true ];
 const PREF_SUGGEST_BOOKMARK =       [ "suggest.bookmark",       true ];
 const PREF_SUGGEST_OPENPAGE =       [ "suggest.openpage",       true ];
 const PREF_SUGGEST_HISTORY_ONLYTYPED = [ "suggest.history.onlyTyped", false ];
 const PREF_SUGGEST_SEARCHES =       [ "suggest.searches",       false ];
 
 const PREF_MAX_CHARS_FOR_SUGGEST =  [ "maxCharsForSearchSuggestions", 20];
 
+const PREF_PREFILL_SITES_ENABLED =  [ "usepreloadedtopurls.enabled",   true ];
+const PREF_PREFILL_SITES_EXPIRE_DAYS = [ "usepreloadedtopurls.expire_days",  14 ];
+
 // AutoComplete query type constants.
 // Describes the various types of queries that we can process rows for.
 const QUERYTYPE_FILTERED            = 0;
 const QUERYTYPE_AUTOFILL_HOST       = 1;
 const QUERYTYPE_AUTOFILL_URL        = 2;
 
 // This separator is used as an RTL-friendly way to split the title and tags.
 // It can also be used by an nsIAutoCompleteResult consumer to re-split the
@@ -279,16 +284,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesSearchAutocompleteProvider",
                                   "resource://gre/modules/PlacesSearchAutocompleteProvider.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesRemoteTabsAutocompleteProvider",
                                   "resource://gre/modules/PlacesRemoteTabsAutocompleteProvider.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
                                   "resource://gre/modules/BrowserUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ProfileAge",
+                                  "resource://gre/modules/ProfileAge.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "textURIService",
                                    "@mozilla.org/intl/texttosuburi;1",
                                    "nsITextToSubURI");
 
 /**
  * Storage object for switch-to-tab entries.
  * This takes care of caching and registering open pages, that will be reused
@@ -461,16 +468,18 @@ XPCOMUtils.defineLazyGetter(this, "Prefs
     store.matchTitleToken = prefs.get(...PREF_MATCH_TITLE);
     store.matchURLToken = prefs.get(...PREF_MATCH_URL);
     store.suggestHistory = prefs.get(...PREF_SUGGEST_HISTORY);
     store.suggestBookmark = prefs.get(...PREF_SUGGEST_BOOKMARK);
     store.suggestOpenpage = prefs.get(...PREF_SUGGEST_OPENPAGE);
     store.suggestTyped = prefs.get(...PREF_SUGGEST_HISTORY_ONLYTYPED);
     store.suggestSearches = prefs.get(...PREF_SUGGEST_SEARCHES);
     store.maxCharsForSearchSuggestions = prefs.get(...PREF_MAX_CHARS_FOR_SUGGEST);
+    store.prefillSitesEnabled = prefs.get(...PREF_PREFILL_SITES_ENABLED);
+    store.prefillSitesExpireDays = prefs.get(...PREF_PREFILL_SITES_EXPIRE_DAYS);
     store.keywordEnabled = true;
     try {
       store.keywordEnabled = Services.prefs.getBoolPref("keyword.enabled");
     } catch (ex) {}
 
     // If history is not set, onlyTyped value should be ignored.
     if (!store.suggestHistory) {
       store.suggestTyped = false;
@@ -534,16 +543,52 @@ XPCOMUtils.defineLazyGetter(this, "Prefs
 
   loadPrefs();
   prefs.observe("", store);
   Services.prefs.addObserver("keyword.enabled", store, true);
 
   return Object.seal(store);
 });
 
+// Prefill Sites related
+
+function PrefillSite(url, title) {
+  this.uri = NetUtil.newURI(url);
+  this.title = title;
+  this._matchTitle = title.toLowerCase();
+}
+
+/**
+ * Storage object for Prefill Sites.
+ *   add(url, title): adds a site to storage
+ *   populate() : populates the  storage with data (hard-coded for now)
+ *   sites[]: resulting array of sites (PrefillSite objects)
+ */
+XPCOMUtils.defineLazyGetter(this, "PrefillSiteStorage", () => Object.seal({
+  sites: [],
+
+  add(url, title) {
+    let site = new PrefillSite(url, title);
+    this.sites.push(site);
+  },
+
+  populate() {
+    this.add("https://google.com/", "Google");
+    this.add("https://youtube.com/", "YouTube");
+    this.add("https://facebook.com/", "Facebook");
+    this.add("https://baidu.com/", "\u767E\u5EA6\u4E00\u4E0B\uFF0C\u4F60\u5C31\u77E5\u9053");
+    this.add("https://wikipedia.org/", "Wikipedia");
+    this.add("https://yahoo.com/", "Yahoo");
+  },
+}));
+
+XPCOMUtils.defineLazyGetter(this, "ProfileAgeCreatedPromise", () => {
+  return (new ProfileAge(null, null)).created;
+});
+
 // Helper functions
 
 /**
  * Used to unescape encoded URI strings and drop information that we do not
  * care about.
  *
  * @param spec
  *        The text to unescape and modify.
@@ -924,16 +969,19 @@ Search.prototype = {
 
     // "openpage" behavior is supported by the default query.
     // _switchToTabQuery instead returns only pages not supported by history.
     if (this.hasBehavior("openpage")) {
       queries.push(this._switchToTabQuery);
     }
     queries.push(this._searchQuery);
 
+    // Check for Prefill Sites Expiry before Autofill
+    yield this._checkPrefillSitesExpiry();
+
     // Add the first heuristic result, if any.  Set _addingHeuristicFirstMatch
     // to true so that when the result is added, "heuristic" can be included in
     // its style.
     this._addingHeuristicFirstMatch = true;
     let hasHeuristic = yield this._matchFirstHeuristicResult(conn);
     this._addingHeuristicFirstMatch = false;
     if (!this.pending)
       return;
@@ -987,21 +1035,72 @@ Search.prototype = {
         this._originalSearchString.length > this._searchTokens[0].length) {
       yield this._matchExtensionSuggestions();
       if (!this.pending)
         return;
     } else if (ExtensionSearchHandler.hasActiveInputSession()) {
       ExtensionSearchHandler.handleInputCancelled();
     }
 
+    this._matchPrefillSites();
+
     // Ensure to fill any remaining space. Suggestions which come from extensions are
     // inserted at the beginning, so any suggestions
     yield Promise.all(this._remoteMatchesPromises);
   }),
 
+
+  *_checkPrefillSitesExpiry() {
+    if (!Prefs.prefillSitesEnabled)
+      return;
+    let profileCreationDate = yield ProfileAgeCreatedPromise;
+    let daysSinceProfileCreation = (Date.now() - profileCreationDate) / MS_PER_DAY;
+    if (daysSinceProfileCreation > Prefs.prefillSitesExpireDays)
+      Services.prefs.setBoolPref("browser.urlbar.usepreloadedtopurls.enabled", false);
+  },
+
+  // TODO: manage protocol and "www." like _matchSearchEngineUrl() does
+  _matchPrefillSites() {
+    if (!Prefs.prefillSitesEnabled)
+      return;
+    for (let site of PrefillSiteStorage.sites) {
+      if (site.uri.host.includes(this._searchString) ||
+          site._matchTitle.includes(this._searchString)) {
+        let match = {
+          value: site.uri.spec,
+          comment: site.title,
+          style: "prefill-site",
+          finalCompleteValue: site.uri.spec,
+          frecency: FRECENCY_DEFAULT - 1,
+        };
+        this._addMatch(match);
+      }
+    }
+  },
+
+  _matchPrefillSiteForAutofill() {
+    if (!Prefs.prefillSitesEnabled)
+      return false;
+    for (let site of PrefillSiteStorage.sites) {
+      if (site.uri.host.startsWith(this._searchString)) {
+        let match = {
+          value: stripPrefix(site.uri.spec),
+          comment: site.title,
+          style: "autofill",
+          finalCompleteValue: site.uri.spec,
+          frecency: FRECENCY_DEFAULT,
+        };
+        this._result.setDefaultIndex(0);
+        this._addMatch(match);
+        return true;
+      }
+    }
+    return false;
+  },
+
   *_matchFirstHeuristicResult(conn) {
     // We always try to make the first result a special "heuristic" result.  The
     // heuristics below determine what type of result it will be, if any.
 
     let hasSearchTerms = this._searchTokens.length > 0;
 
     if (hasSearchTerms) {
       // It may be a keyword registered by an extension.
@@ -1039,16 +1138,23 @@ Search.prototype = {
     if (this.pending && shouldAutofill) {
       // Or it may look like a URL we know about from search engines.
       let matched = yield this._matchSearchEngineUrl();
       if (matched) {
         return true;
       }
     }
 
+    if (this.pending && shouldAutofill) {
+      let matched = this._matchPrefillSiteForAutofill();
+      if (matched) {
+        return true;
+      }
+    }
+
     if (this.pending && hasSearchTerms && this._enableActions) {
       // If we don't have a result that matches what we know about, then
       // we use a fallback for things we don't know about.
 
       // We may not have auto-filled, but this may still look like a URL.
       // However, even if the input is a valid URL, we may not want to use
       // it as such. This can happen if the host would require whitelisting,
       // but isn't in the whitelist.
@@ -1946,16 +2052,24 @@ Search.prototype = {
 // component @mozilla.org/autocomplete/search;1?name=unifiedcomplete
 
 function UnifiedComplete() {
   // Make sure the preferences are initialized as soon as possible.
   // If the value of browser.urlbar.autocomplete.enabled is set to false,
   // then all the other suggest preferences for history, bookmarks and
   // open pages should be set to false.
   Prefs;
+
+  if (Prefs.prefillSitesEnabled) {
+    // force initializing the profile age check
+    // to ensure the off-main-thread-IO happens ASAP
+    // and we don't have to wait for it when doing an autocomplete lookup
+    ProfileAgeCreatedPromise;
+    PrefillSiteStorage.populate(); // with hard-coded data for now
+  }
 }
 
 UnifiedComplete.prototype = {
   // Database handling
 
   /**
    * Promise resolved when the database initialization has completed, or null
    * if it has never been requested.
@@ -1994,33 +2108,37 @@ UnifiedComplete.prototype = {
         // The value used here is larger than the default Storage value defined
         // as MAX_CACHE_SIZE_BYTES in storage/mozStorageConnection.cpp.
         yield conn.execute("PRAGMA cache_size = -6144"); // 6MiB
 
         yield SwitchToTabStorage.initDatabase(conn);
 
         return conn;
       }).then(null, ex => {
- dump("Couldn't get database handle: " + ex + "\n");
-                                       Cu.reportError(ex);
-});
+        dump("Couldn't get database handle: " + ex + "\n");
+        Cu.reportError(ex);
+      });
     }
     return this._promiseDatabase;
   },
 
   // mozIPlacesAutoComplete
 
   registerOpenPage(uri, userContextId) {
     SwitchToTabStorage.add(uri, userContextId);
   },
 
   unregisterOpenPage(uri, userContextId) {
     SwitchToTabStorage.delete(uri, userContextId);
   },
 
+  addPrefillSite(url, title) {
+    PrefillSiteStorage.add(url, title);
+  },
+
   // nsIAutoCompleteSearch
 
   startSearch(searchString, searchParam, previousResult, listener) {
     // Stop the search in case the controller has not taken care of it.
     if (this._currentSearch) {
       this.stopSearch();
     }
 
--- a/toolkit/components/places/mozIPlacesAutoComplete.idl
+++ b/toolkit/components/places/mozIPlacesAutoComplete.idl
@@ -130,9 +130,19 @@ interface mozIPlacesAutoComplete : nsISu
    *       register themselves.
    *
    * @param aURI
    *        The URI to unregister as an open page.
    * @param aUserContextId
    *        The Container Id of the tab.
    */
   void unregisterOpenPage(in nsIURI aURI, in uint32_t aUserContextId);
+
+  /**
+   * Add a site to list of Prefill Sites.
+   *
+   * @param url
+   *        The URL of added site.
+   * @param title
+   *        The title of added site.
+   */
+  void addPrefillSite(in AUTF8String url, in AUTF8String title);
 };
--- a/toolkit/components/places/tests/head_common.js
+++ b/toolkit/components/places/tests/head_common.js
@@ -72,16 +72,20 @@ XPCOMUtils.defineLazyGetter(this, "SMALL
          "IGhlaWdodD0iNDEuOCIvPg0KPC9zdmc%2BDQo%3D");
 });
 
 var gTestDir = do_get_cwd();
 
 // Initialize profile.
 var gProfD = do_get_profile();
 
+Services.prefs.setBoolPref("browser.urlbar.usepreloadedtopurls.enabled", false);
+do_register_cleanup(() =>
+  Services.prefs.clearUserPref("browser.urlbar.usepreloadedtopurls.enabled"));
+
 // Remove any old database.
 clearDB();
 
 /**
  * Shortcut to create a nsIURI.
  *
  * @param aSpec
  *        URLString of the uri.
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/unifiedcomplete/test_prefill_sites.js
@@ -0,0 +1,116 @@
+/**
+ * Test for bug 1211726 - prefill list of top web sites for better
+ * autocompletion on empty profiles.
+ */
+
+const PREF_FEATURE_ENABLED = "browser.urlbar.usepreloadedtopurls.enabled";
+const PREF_FEATURE_EXPIRE_DAYS = "browser.urlbar.usepreloadedtopurls.expire_days";
+
+const autocompleteObject = Cc["@mozilla.org/autocomplete/search;1?name=unifiedcomplete"]
+                             .getService(Ci.mozIPlacesAutoComplete);
+
+// With or without trailing slash - no matter. URI.spec does have it always.
+// Then, check_autocomplete() doesn't cut it off (uses stripPrefix()).
+let yahoooURI = NetUtil.newURI("https://yahooo.com/");
+let gooogleURI = NetUtil.newURI("https://gooogle.com/");
+
+autocompleteObject.addPrefillSite(yahoooURI.spec, "Yahooo");
+autocompleteObject.addPrefillSite(gooogleURI.spec, "Gooogle");
+
+function *assert_feature_works(condition) {
+  do_print("List Results do appear " + condition);
+  yield check_autocomplete({
+    search: "ooo",
+    matches: [
+      { uri: yahoooURI, title: "Yahooo",  style: ["prefill-site"] },
+      { uri: gooogleURI, title: "Gooogle", style: ["prefill-site"] },
+    ],
+  });
+
+  do_print("Autofill does appear " + condition);
+  yield check_autocomplete({
+    search: "gooo",
+    autofilled: "gooogle.com/", // Will fail without trailing slash
+    completed: "https://gooogle.com/",
+  });
+}
+
+function *assert_feature_does_not_appear(condition) {
+  do_print("List Results don't appear " + condition);
+  yield check_autocomplete({
+    search: "ooo",
+    matches: [],
+  });
+
+  do_print("Autofill doesn't appear " + condition);
+  // "search" is what you type,
+  // "autofilled" is what you get in response in the url bar,
+  // "completed" is what you get there when you hit enter.
+  // So if they are all equal - it's the proof there was no autofill
+  // (knowing we have a suitable prefill site).
+  yield check_autocomplete({
+    search: "gooo",
+    autofilled: "gooo",
+    completed: "gooo",
+  });
+}
+
+add_task(function* test_it_works() {
+  // Not expired but OFF
+  Services.prefs.setIntPref(PREF_FEATURE_EXPIRE_DAYS, 14);
+  Services.prefs.setBoolPref(PREF_FEATURE_ENABLED, false);
+  yield assert_feature_does_not_appear("when OFF by prefs");
+
+  // Now turn it ON
+  Services.prefs.setBoolPref(PREF_FEATURE_ENABLED, true);
+  yield assert_feature_works("when ON by prefs");
+
+  // And expire
+  Services.prefs.setIntPref(PREF_FEATURE_EXPIRE_DAYS, 0);
+  yield assert_feature_does_not_appear("when expired");
+
+  yield cleanup();
+});
+
+add_task(function* test_sorting_against_bookmark() {
+  let boookmarkURI = NetUtil.newURI("https://boookmark.com");
+  yield addBookmark( { uri: boookmarkURI, title: "Boookmark" } );
+
+  Services.prefs.setBoolPref(PREF_FEATURE_ENABLED, true);
+  Services.prefs.setIntPref(PREF_FEATURE_EXPIRE_DAYS, 14);
+
+  do_print("Prefill Sites are placed lower than Bookmarks");
+  yield check_autocomplete({
+    checkSorting: true,
+    search: "ooo",
+    matches: [
+      { uri: boookmarkURI, title: "Boookmark",  style: ["bookmark"] },
+      { uri: yahoooURI, title: "Yahooo",  style: ["prefill-site"] },
+      { uri: gooogleURI, title: "Gooogle", style: ["prefill-site"] },
+    ],
+  });
+
+  yield cleanup();
+});
+
+add_task(function* test_sorting_against_history() {
+  let histoooryURI = NetUtil.newURI("https://histooory.com");
+  yield PlacesTestUtils.addVisits( { uri: histoooryURI, title: "Histooory" } );
+
+  Services.prefs.setBoolPref(PREF_FEATURE_ENABLED, true);
+  Services.prefs.setIntPref(PREF_FEATURE_EXPIRE_DAYS, 14);
+
+  do_print("Prefill Sites are placed lower than History entries");
+  yield check_autocomplete({
+    checkSorting: true,
+    search: "ooo",
+    matches: [
+      { uri: histoooryURI, title: "Histooory" },
+      { uri: yahoooURI, title: "Yahooo",  style: ["prefill-site"] },
+      { uri: gooogleURI, title: "Gooogle", style: ["prefill-site"] },
+    ],
+  });
+
+  yield cleanup();
+});
+
--- a/toolkit/components/places/tests/unifiedcomplete/xpcshell.ini
+++ b/toolkit/components/places/tests/unifiedcomplete/xpcshell.ini
@@ -25,16 +25,17 @@ support-files =
 [test_escape_self.js]
 [test_extension_matches.js]
 [test_ignore_protocol.js]
 [test_keyword_search.js]
 [test_keyword_search_actions.js]
 [test_keywords.js]
 [test_match_beginning.js]
 [test_multi_word_search.js]
+[test_prefill_sites.js]
 [test_query_url.js]
 [test_remote_tab_matches.js]
 skip-if = !sync
 [test_search_engine_alias.js]
 [test_search_engine_current.js]
 [test_search_engine_host.js]
 [test_search_engine_restyle.js]
 [test_search_suggestions.js]