Bug 1211726 - part 1: add results from a hardcoded list of top sites, r?gijs
MozReview-Commit-ID: 21FN4awJaXf
--- 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]