Bug 1211726 - part 1: add results from a hardcoded list of top sites, r=Gijs,mak
authorSvetlana Orlik <sorlik@mozilla.com>
Mon, 02 Jan 2017 03:28:31 +0300
changeset 343058 1bf558a9bf8749c878bb14d45f5f60f14a8dd0a6
parent 343057 5b4b9186421c101a78c2037f1c7c566b71f832f8
child 343059 a273874c22052691f8cec93a82d374ad0ec52454
push id31368
push userkwierso@gmail.com
push dateWed, 15 Feb 2017 23:26:19 +0000
treeherdermozilla-central@e783bdf2cb50 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGijs, mak
bugs1211726
milestone54.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 1211726 - part 1: add results from a hardcoded list of top sites, r=Gijs,mak 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
@@ -1054,16 +1054,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]