mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 959582 - Refactor the search URL provider for the location bar. r=mak
--HG-- rename : toolkit/components/places/PriorityUrlProvider.jsm => toolkit/components/places/PlacesSearchAutocompleteProvider.jsm rename : toolkit/components/places/tests/unit/test_priorityUrlProvider.js => toolkit/components/places/tests/unit/test_PlacesSearchAutocompleteProvider.js
This commit is contained in:
parent
a889ea8948
commit
9e803c070a
160
toolkit/components/places/PlacesSearchAutocompleteProvider.jsm
Normal file
160
toolkit/components/places/PlacesSearchAutocompleteProvider.jsm
Normal file
@ -0,0 +1,160 @@
|
||||
/* 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/. */
|
||||
|
||||
/*
|
||||
* Provides functions to handle search engine URLs in the browser history.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = [ "PlacesSearchAutocompleteProvider" ];
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
const SEARCH_ENGINE_TOPIC = "browser-search-engine-modified";
|
||||
|
||||
const SearchAutocompleteProviderInternal = {
|
||||
/**
|
||||
* Array of objects in the format returned by findMatchByToken.
|
||||
*/
|
||||
priorityMatches: null,
|
||||
|
||||
initialize: function () {
|
||||
return new Promise((resolve, reject) => {
|
||||
Services.search.init(status => {
|
||||
if (!Components.isSuccessCode(status)) {
|
||||
reject(new Error("Unable to initialize search service."));
|
||||
}
|
||||
|
||||
try {
|
||||
// The initial loading of the search engines must succeed.
|
||||
this._refresh();
|
||||
|
||||
Services.obs.addObserver(this, SEARCH_ENGINE_TOPIC, true);
|
||||
|
||||
this.initialized = true;
|
||||
resolve();
|
||||
} catch (ex) {
|
||||
reject(ex);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
initialized: false,
|
||||
|
||||
observe: function (subject, topic, data) {
|
||||
switch (data) {
|
||||
case "engine-added":
|
||||
case "engine-changed":
|
||||
case "engine-removed":
|
||||
this._refresh();
|
||||
}
|
||||
},
|
||||
|
||||
_refresh: function () {
|
||||
this.priorityMatches = [];
|
||||
|
||||
// The search engines will always be processed in the order returned by the
|
||||
// search service, which can be defined by the user.
|
||||
Services.search.getVisibleEngines().forEach(e => this._addEngine(e));
|
||||
},
|
||||
|
||||
_addEngine: function (engine) {
|
||||
let token = engine.getResultDomain();
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.priorityMatches.push({
|
||||
token: token,
|
||||
// The searchForm property returns a simple URL for the search engine, but
|
||||
// we may need an URL which includes an affiliate code (bug 990799).
|
||||
url: engine.searchForm,
|
||||
engineName: engine.name,
|
||||
iconUrl: engine.iconURI ? engine.iconURI.spec : null,
|
||||
});
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference]),
|
||||
}
|
||||
|
||||
let gInitializationPromise = null;
|
||||
|
||||
this.PlacesSearchAutocompleteProvider = Object.freeze({
|
||||
/**
|
||||
* Starts initializing the component and returns a promise that is resolved or
|
||||
* rejected when initialization finished. The same promise is returned if
|
||||
* this function is called multiple times.
|
||||
*/
|
||||
ensureInitialized: function () {
|
||||
if (!gInitializationPromise) {
|
||||
gInitializationPromise = SearchAutocompleteProviderInternal.initialize();
|
||||
}
|
||||
return gInitializationPromise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Matches a given string to an item that should be included by URL search
|
||||
* components, like autocomplete in the address bar.
|
||||
*
|
||||
* @param searchToken
|
||||
* String containing the first part of the matching domain name.
|
||||
*
|
||||
* @return An object with the following properties, or undefined if the token
|
||||
* does not match any relevant URL:
|
||||
* {
|
||||
* token: The full string used to match the search term to the URL.
|
||||
* url: The URL to navigate to if the match is selected.
|
||||
* engineName: The display name of the search engine.
|
||||
* iconUrl: Icon associated to the match, or null if not available.
|
||||
* }
|
||||
*/
|
||||
findMatchByToken: Task.async(function* (searchToken) {
|
||||
yield this.ensureInitialized();
|
||||
|
||||
// Match at the beginning for now. In the future, an "options" argument may
|
||||
// allow the matching behavior to be tuned.
|
||||
return SearchAutocompleteProviderInternal.priorityMatches
|
||||
.find(m => m.token.startsWith(searchToken));
|
||||
}),
|
||||
|
||||
/**
|
||||
* Synchronously determines if the provided URL represents results from a
|
||||
* search engine, and provides details about the match.
|
||||
*
|
||||
* @param url
|
||||
* String containing the URL to parse.
|
||||
*
|
||||
* @return An object with the following properties, or null if the URL does
|
||||
* not represent a search result:
|
||||
* {
|
||||
* engineName: The display name of the search engine.
|
||||
* terms: The originally sought terms extracted from the URI.
|
||||
* }
|
||||
*
|
||||
* @remarks The asynchronous ensureInitialized function must be called before
|
||||
* this synchronous method can be used.
|
||||
*
|
||||
* @note This API function needs to be synchronous because it is called inside
|
||||
* a row processing callback of Sqlite.jsm, in UnifiedComplete.js.
|
||||
*/
|
||||
parseSubmissionURL: function (url) {
|
||||
if (!SearchAutocompleteProviderInternal.initialized) {
|
||||
throw new Error("The component has not been initialized.");
|
||||
}
|
||||
|
||||
let parseUrlResult = Services.search.parseSubmissionURL(url);
|
||||
return parseUrlResult.engine && {
|
||||
engineName: parseUrlResult.engine.name,
|
||||
terms: parseUrlResult.terms,
|
||||
};
|
||||
},
|
||||
});
|
@ -1,142 +0,0 @@
|
||||
/* 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";
|
||||
|
||||
this.EXPORTED_SYMBOLS = [ "PriorityUrlProvider" ];
|
||||
|
||||
const Ci = Components.interfaces;
|
||||
const Cc = Components.classes;
|
||||
const Cu = Components.utils;
|
||||
const Cr = Components.results;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
|
||||
/**
|
||||
* Provides search engines matches to the PriorityUrlProvider through the
|
||||
* search engines definitions handled by the Search Service.
|
||||
*/
|
||||
const SEARCH_ENGINE_TOPIC = "browser-search-engine-modified";
|
||||
|
||||
let SearchEnginesProvider = {
|
||||
init: function () {
|
||||
this._engines = new Map();
|
||||
let deferred = Promise.defer();
|
||||
Services.search.init(rv => {
|
||||
if (Components.isSuccessCode(rv)) {
|
||||
Services.search.getVisibleEngines().forEach(this._addEngine, this);
|
||||
deferred.resolve();
|
||||
} else {
|
||||
deferred.reject(new Error("Unable to initialize search service."));
|
||||
}
|
||||
});
|
||||
Services.obs.addObserver(this, SEARCH_ENGINE_TOPIC, true);
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
observe: function (engine, topic, verb) {
|
||||
let engine = engine.QueryInterface(Ci.nsISearchEngine);
|
||||
switch (verb) {
|
||||
case "engine-added":
|
||||
this._addEngine(engine);
|
||||
break;
|
||||
case "engine-changed":
|
||||
if (engine.hidden) {
|
||||
this._removeEngine(engine);
|
||||
} else {
|
||||
this._addEngine(engine);
|
||||
}
|
||||
break;
|
||||
case "engine-removed":
|
||||
this._removeEngine(engine);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_addEngine: function (engine) {
|
||||
if (this._engines.has(engine.name)) {
|
||||
return;
|
||||
}
|
||||
let token = engine.getResultDomain();
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
let match = { token: token,
|
||||
// TODO (bug 990799): searchForm should provide an usable
|
||||
// url with affiliate code, if available.
|
||||
url: engine.searchForm,
|
||||
title: engine.name,
|
||||
iconUrl: engine.iconURI ? engine.iconURI.spec : null,
|
||||
reason: "search" }
|
||||
this._engines.set(engine.name, match);
|
||||
PriorityUrlProvider.addMatch(match);
|
||||
},
|
||||
|
||||
_removeEngine: function (engine) {
|
||||
if (!this._engines.has(engine.name)) {
|
||||
return;
|
||||
}
|
||||
this._engines.delete(engine.name);
|
||||
PriorityUrlProvider.removeMatchByToken(engine.getResultDomain());
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference])
|
||||
}
|
||||
|
||||
/**
|
||||
* The PriorityUrlProvider allows to match a given string to a list of
|
||||
* urls that should have priority in url search components, like autocomplete.
|
||||
* Each returned match is an object with the following properties:
|
||||
* - token: string used to match the search term to the url
|
||||
* - url: url string represented by the match
|
||||
* - title: title describing the match, or an empty string if not available
|
||||
* - iconUrl: url of the icon associated to the match, or null if not available
|
||||
* - reason: a string describing the origin of the match, for example if it
|
||||
* represents a search engine, it will be "search".
|
||||
*/
|
||||
let matches = new Map();
|
||||
|
||||
let initialized = false;
|
||||
function promiseInitialized() {
|
||||
if (initialized) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Task.spawn(function* () {
|
||||
try {
|
||||
yield SearchEnginesProvider.init();
|
||||
} catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
initialized = true;
|
||||
});
|
||||
}
|
||||
|
||||
this.PriorityUrlProvider = Object.freeze({
|
||||
addMatch: function (match) {
|
||||
matches.set(match.token, match);
|
||||
},
|
||||
|
||||
removeMatchByToken: function (token) {
|
||||
matches.delete(token);
|
||||
},
|
||||
|
||||
getMatch: function (searchToken) {
|
||||
return Task.spawn(function* () {
|
||||
yield promiseInitialized();
|
||||
for (let [token, match] of matches.entries()) {
|
||||
// Match at the beginning for now. In future an aOptions argument may
|
||||
// allow to control the matching behavior.
|
||||
if (token.startsWith(searchToken)) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}.bind(this));
|
||||
}
|
||||
});
|
@ -264,8 +264,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
"resource://gre/modules/Promise.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
"resource://gre/modules/Task.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PriorityUrlProvider",
|
||||
"resource://gre/modules/PriorityUrlProvider.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesSearchAutocompleteProvider",
|
||||
"resource://gre/modules/PlacesSearchAutocompleteProvider.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "textURIService",
|
||||
"@mozilla.org/intl/texttosuburi;1",
|
||||
@ -703,14 +703,17 @@ Search.prototype = {
|
||||
_matchPriorityUrl: function* () {
|
||||
if (!Prefs.autofillPriority)
|
||||
return;
|
||||
let priorityMatch = yield PriorityUrlProvider.getMatch(this._searchString);
|
||||
|
||||
// Handle priority matches for search engine domains.
|
||||
let priorityMatch =
|
||||
yield PlacesSearchAutocompleteProvider.findMatchByToken(this._searchString);
|
||||
if (priorityMatch) {
|
||||
this._result.setDefaultIndex(0);
|
||||
this._addFrecencyMatch({
|
||||
value: priorityMatch.token,
|
||||
comment: priorityMatch.title,
|
||||
comment: priorityMatch.engineName,
|
||||
icon: priorityMatch.iconUrl,
|
||||
style: "priority-" + priorityMatch.reason,
|
||||
style: "priority-search",
|
||||
finalCompleteValue: priorityMatch.url,
|
||||
frecency: FRECENCY_PRIORITY_DEFAULT
|
||||
});
|
||||
|
@ -67,8 +67,8 @@ if CONFIG['MOZ_PLACES']:
|
||||
'ColorConversion.js',
|
||||
'PlacesBackups.jsm',
|
||||
'PlacesDBUtils.jsm',
|
||||
'PlacesSearchAutocompleteProvider.jsm',
|
||||
'PlacesTransactions.jsm',
|
||||
'PriorityUrlProvider.jsm'
|
||||
]
|
||||
|
||||
EXTRA_PP_JS_MODULES += [
|
||||
|
@ -2,7 +2,7 @@
|
||||
* 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/. */
|
||||
|
||||
Cu.import("resource://gre/modules/PriorityUrlProvider.jsm");
|
||||
Cu.import("resource://gre/modules/PlacesSearchAutocompleteProvider.jsm");
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
@ -11,15 +11,14 @@ function run_test() {
|
||||
add_task(function* search_engine_match() {
|
||||
let engine = yield promiseDefaultSearchEngine();
|
||||
let token = engine.getResultDomain();
|
||||
let match = yield PriorityUrlProvider.getMatch(token.substr(0, 1));
|
||||
let match = yield PlacesSearchAutocompleteProvider.findMatchByToken(token.substr(0, 1));
|
||||
do_check_eq(match.url, engine.searchForm);
|
||||
do_check_eq(match.title, engine.name);
|
||||
do_check_eq(match.engineName, engine.name);
|
||||
do_check_eq(match.iconUrl, engine.iconURI ? engine.iconURI.spec : null);
|
||||
do_check_eq(match.reason, "search");
|
||||
});
|
||||
|
||||
add_task(function* no_match() {
|
||||
do_check_eq(null, yield PriorityUrlProvider.getMatch("test"));
|
||||
do_check_eq(null, yield PlacesSearchAutocompleteProvider.findMatchByToken("test"));
|
||||
});
|
||||
|
||||
add_task(function* hide_search_engine_nomatch() {
|
||||
@ -29,20 +28,19 @@ add_task(function* hide_search_engine_nomatch() {
|
||||
Services.search.removeEngine(engine);
|
||||
yield promiseTopic;
|
||||
do_check_true(engine.hidden);
|
||||
do_check_eq(null, yield PriorityUrlProvider.getMatch(token.substr(0, 1)));
|
||||
do_check_eq(null, yield PlacesSearchAutocompleteProvider.findMatchByToken(token.substr(0, 1)));
|
||||
});
|
||||
|
||||
add_task(function* add_search_engine_match() {
|
||||
let promiseTopic = promiseSearchTopic("engine-added");
|
||||
do_check_eq(null, yield PriorityUrlProvider.getMatch("bacon"));
|
||||
do_check_eq(null, yield PlacesSearchAutocompleteProvider.findMatchByToken("bacon"));
|
||||
Services.search.addEngineWithDetails("bacon", "", "bacon", "Search Bacon",
|
||||
"GET", "http://www.bacon.moz/?search={searchTerms}");
|
||||
yield promiseSearchTopic;
|
||||
let match = yield PriorityUrlProvider.getMatch("bacon");
|
||||
let match = yield PlacesSearchAutocompleteProvider.findMatchByToken("bacon");
|
||||
do_check_eq(match.url, "http://www.bacon.moz");
|
||||
do_check_eq(match.title, "bacon");
|
||||
do_check_eq(match.engineName, "bacon");
|
||||
do_check_eq(match.iconUrl, null);
|
||||
do_check_eq(match.reason, "search");
|
||||
});
|
||||
|
||||
add_task(function* remove_search_engine_nomatch() {
|
||||
@ -50,7 +48,21 @@ add_task(function* remove_search_engine_nomatch() {
|
||||
let promiseTopic = promiseSearchTopic("engine-removed");
|
||||
Services.search.removeEngine(engine);
|
||||
yield promiseTopic;
|
||||
do_check_eq(null, yield PriorityUrlProvider.getMatch("bacon"));
|
||||
do_check_eq(null, yield PlacesSearchAutocompleteProvider.findMatchByToken("bacon"));
|
||||
});
|
||||
|
||||
add_task(function* test_parseSubmissionURL_basic() {
|
||||
// Most of the logic of parseSubmissionURL is tested in the search service
|
||||
// itself, thus we only do a sanity check of the wrapper here.
|
||||
let engine = yield promiseDefaultSearchEngine();
|
||||
let submissionURL = engine.getSubmission("terms").uri.spec;
|
||||
|
||||
let result = PlacesSearchAutocompleteProvider.parseSubmissionURL(submissionURL);
|
||||
do_check_eq(result.engineName, engine.name);
|
||||
do_check_eq(result.terms, "terms");
|
||||
|
||||
let result = PlacesSearchAutocompleteProvider.parseSubmissionURL("http://example.org/");
|
||||
do_check_eq(result, null);
|
||||
});
|
||||
|
||||
function promiseDefaultSearchEngine() {
|
@ -115,6 +115,7 @@ skip-if = true
|
||||
[test_pageGuid_bookmarkGuid.js]
|
||||
[test_frecency_observers.js]
|
||||
[test_placeURIs.js]
|
||||
[test_PlacesSearchAutocompleteProvider.js]
|
||||
[test_PlacesUtils_asyncGetBookmarkIds.js]
|
||||
[test_PlacesUtils_lazyobservers.js]
|
||||
[test_placesTxn.js]
|
||||
@ -125,7 +126,6 @@ skip-if = os == "android"
|
||||
# Bug 676989: test hangs consistently on Android
|
||||
skip-if = os == "android"
|
||||
[test_preventive_maintenance_runTasks.js]
|
||||
[test_priorityUrlProvider.js]
|
||||
[test_promiseBookmarksTree.js]
|
||||
[test_removeVisitsByTimeframe.js]
|
||||
# Bug 676989: test hangs consistently on Android
|
||||
|
Loading…
Reference in New Issue
Block a user