Bug 1067899 - Add autocomplete result type for arbitrary URLs. r=mak

--HG--
extra : transplant_source : %B4%5C%83%26%B0%3EL%F1%B0%0E%B4%822y%E5%8B%0B%A4%01%3D
This commit is contained in:
Blair McBride 2014-09-27 01:17:25 +12:00
parent 5544fa5570
commit 7f485beb93
10 changed files with 181 additions and 8 deletions

View File

@ -151,7 +151,8 @@
if (action) {
switch (action.type) {
case "switchtab": {
case "switchtab": // Fall through.
case "visiturl": {
returnValue = action.params.url;
break;
}
@ -327,6 +328,8 @@
url = submission.uri.spec;
postData = submission.postData;
} else if (action.type == "visiturl") {
url = action.params.url;
}
}
continueOperation.call(this);
@ -1022,8 +1025,9 @@
// TODO (bug 1054816): Centralise the implementation of actions
// into a JS module.
switch (action.type) {
case "switchtab": //Fall through.
case "keyword": {
case "switchtab": // Fall through.
case "keyword": // Fall through.
case "visiturl": {
url = action.params.url;
break;
}

View File

@ -1513,6 +1513,29 @@ this.PlacesUtils = {
return deferred.promise;
},
/**
* Gets the favicon link url (moz-anno:) for a given page url.
*
* @param aPageURL url of the page to lookup the favicon for.
* @resolves to the nsIURL of the favicon link
* @rejects if the given url has no associated favicon.
*/
promiseFaviconLinkUrl: function (aPageUrl) {
let deferred = Promise.defer();
if (!(aPageUrl instanceof Ci.nsIURI))
aPageUrl = NetUtil.newURI(aPageUrl);
PlacesUtils.favicons.getFaviconURLForPage(aPageUrl, uri => {
if (uri) {
uri = PlacesUtils.favicons.getFaviconLinkForIcon(uri);
deferred.resolve(uri);
} else {
deferred.reject();
}
});
return deferred.promise;
},
/**
* Returns the passed URL with a #-moz-resolution fragment
* for the specified dimensions and devicePixelRatio.

View File

@ -711,7 +711,6 @@ Search.prototype = {
// with an alias - which works like a keyword.
hasFirstResult = yield this._matchSearchEngineAlias();
}
let shouldAutofill = this._shouldAutofill;
if (this.pending && !hasFirstResult && shouldAutofill) {
// Or it may look like a URL we know about from search engines.
@ -720,20 +719,35 @@ Search.prototype = {
if (this.pending && !hasFirstResult && shouldAutofill) {
// It may also look like a URL we know from the database.
// Here we can only try to predict whether the URL autofill query is
// likely to return a result. If the prediction ends up being wrong,
// later we will need to make up for the lack of a special first result.
hasFirstResult = yield this._matchKnownUrl(conn, queries);
}
if (this.pending && this._enableActions && !hasFirstResult) {
// When all else fails, we search using the current search engine.
yield this._matchCurrentSearchEngine();
// 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.
yield this._matchHeuristicFallback();
}
// IMPORTANT: No other first result heuristics should run after
// _matchHeuristicFallback().
yield this._sleep(Prefs.delay);
if (!this.pending)
return;
for (let [query, params] of queries) {
yield conn.executeCached(query, params, this._onResultRow.bind(this));
let hasResult = yield conn.executeCached(query, params, this._onResultRow.bind(this));
if (this.pending && params.query_type == QUERYTYPE_AUTOFILL_URL &&
!hasResult) {
// If we predicted that our URL autofill query might have gotten a
// result, but it didn't, then we need to recover.
yield this._matchHeuristicFallback();
}
if (!this.pending)
return;
}
@ -881,6 +895,70 @@ Search.prototype = {
});
},
// These are separated out so we can run them in two distinct cases:
// (1) We didn't match on anything that we know about
// (2) Our predictive query for URL autofill thought we may get a result,
// but we didn't.
_matchHeuristicFallback: function* () {
// We may not have auto-filled, but this may still look like a URL.
let hasFirstResult = yield this._matchUnknownUrl();
// 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.
if (this.pending && !hasFirstResult) {
// When all else fails, we search using the current search engine.
yield this._matchCurrentSearchEngine();
}
},
// TODO (bug 1054814): Use visited URLs to inform which scheme to use, if the
// scheme isn't specificed.
_matchUnknownUrl: function* () {
let flags = Ci.nsIURIFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS |
Ci.nsIURIFixup.FIXUP_FLAG_REQUIRE_WHITELISTED_HOST;
let fixupInfo = null;
try {
fixupInfo = Services.uriFixup.getFixupURIInfo(this._originalSearchString,
flags);
} catch (e) {
return false;
}
let uri = fixupInfo.preferredURI;
// Check the host, as "http:///" is a valid nsIURI, but not useful to us.
// But, some schemes are expected to have no host. So we check just against
// schemes we know should have a host. This allows new schemes to be
// implemented without us accidentally blocking access to them.
let hostExpected = new Set(["http", "https", "ftp", "chrome", "resource"]);
if (!uri || (hostExpected.has(uri.scheme) && !uri.host))
return false;
let value = makeActionURL("visiturl", {
url: uri.spec,
input: this._originalSearchString,
});
let match = {
value: value,
comment: uri.spec,
style: "action visiturl",
finalCompleteValue: this._originalSearchString,
frecency: 0,
};
try {
let favicon = yield PlacesUtils.promiseFaviconLinkUrl(uri);
if (favicon)
match.icon = favicon.spec;
} catch (e) {
// It's possible we don't have a favicon for this - and that's ok.
};
this._addMatch(match);
return true;
},
_onResultRow: function (row) {
TelemetryStopwatch.finish(TELEMETRY_1ST_RESULT);
let queryType = row.getResultByIndex(QUERYINDEX_QUERYTYPE);

View File

@ -27,7 +27,7 @@ add_task(function* test_tab_matches() {
yield check_autocomplete({
search: "abc.com",
searchParam: "enable-actions",
matches: [ { uri: makeActionURI("searchengine", {engineName: "MozSearch", input: "abc.com", searchQuery: "abc.com"}), title: "MozSearch" },
matches: [ { uri: makeActionURI("visiturl", {url: "http://abc.com/", input: "abc.com"}), title: "http://abc.com/" },
{ uri: makeActionURI("switchtab", {url: "http://abc.com/"}), title: "ABC rocks" } ]
});

View File

@ -0,0 +1,53 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
add_task(function*() {
do_log_info("visit url, no protocol");
yield check_autocomplete({
search: "mozilla.org",
searchParam: "enable-actions",
matches: [ { uri: makeActionURI("visiturl", {url: "http://mozilla.org/", input: "mozilla.org"}), title: "http://mozilla.org/" }, ]
});
do_log_info("visit url, with protocol");
yield 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/" }, ]
});
do_log_info("visit url, about: protocol (no host)");
yield check_autocomplete({
search: "about:config",
searchParam: "enable-actions",
matches: [ { uri: makeActionURI("visiturl", {url: "about:config", input: "about:config"}), title: "about:config" }, ]
});
// This is distinct because of how we predict being able to url autofill via
// host lookups.
do_log_info("visit url, host matching visited host but not visited url");
yield promiseAddVisits([
{ uri: NetUtil.newURI("http://mozilla.org/wine/"), title: "Mozilla Wine", transition: TRANSITION_TYPED },
]);
yield check_autocomplete({
search: "mozilla.org/rum",
searchParam: "enable-actions",
matches: [ { uri: makeActionURI("visiturl", {url: "http://mozilla.org/rum", input: "mozilla.org/rum"}), title: "http://mozilla.org/rum" }, ]
});
// And hosts with no dot in them are special, due to requiring whitelisting.
do_log_info("visit url, host matching visited host but not visited url, non-whitelisted host");
Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET",
"http://s.example.com/search");
let engine = Services.search.getEngineByName("MozSearch");
Services.search.currentEngine = engine;
yield promiseAddVisits([
{ uri: NetUtil.newURI("http://mozilla/bourbon/"), title: "Mozilla Bourbon", transition: TRANSITION_TYPED },
]);
yield check_autocomplete({
search: "mozilla/rum",
searchParam: "enable-actions",
matches: [ { uri: makeActionURI("searchengine", {engineName: "MozSearch", input: "mozilla/rum", searchQuery: "mozilla/rum"}), title: "MozSearch" }, ]
});
});

View File

@ -35,5 +35,6 @@ tail =
[test_tabmatches.js]
[test_trimming.js]
[test_typed.js]
[test_visiturl.js]
[test_word_boundary_search.js]
[test_zero_frecency.js]

View File

@ -1588,6 +1588,14 @@ extends="chrome://global/content/bindings/popup.xml#popup">
// Remove the image, so we can style it ourselves with a generic
// search icon.
this.removeAttribute("image");
} else if (action.type == "visiturl") {
emphasiseUrl = false;
url = action.params.url;
let sourceStr = this._stringBundle.GetStringFromName("visitURL");
title = this._generateEmphasisPairs(sourceStr, [
[trimURL(url), true],
]);
}
// Remove the "action" substring so that the correct style, if any,

View File

@ -119,12 +119,14 @@ treechildren.autocomplete-treebody::-moz-tree-cell-text(selected) {
.autocomplete-richlistitem[actiontype="keyword"] .ac-url-box,
.autocomplete-richlistitem[actiontype="searchengine"] .ac-url-box,
.autocomplete-richlistitem[actiontype="visiturl"] .ac-url-box,
.autocomplete-richlistitem[type~="autofill"] .ac-url-box {
display: none;
}
.autocomplete-richlistitem[actiontype="keyword"] .ac-title-box,
.autocomplete-richlistitem[actiontype="searchengine"] .ac-title-box,
.autocomplete-richlistitem[actiontype="visiturl"] .ac-title-box,
.autocomplete-richlistitem[type~="autofill"] .ac-title-box {
margin-top: 12px;
margin-bottom: 12px;

View File

@ -104,12 +104,14 @@ treechildren.autocomplete-treebody::-moz-tree-cell-text(selected) {
.autocomplete-richlistitem[actiontype="keyword"] .ac-url-box,
.autocomplete-richlistitem[actiontype="searchengine"] .ac-url-box,
.autocomplete-richlistitem[actiontype="visiturl"] .ac-url-box,
.autocomplete-richlistitem[type~="autofill"] .ac-url-box {
display: none;
}
.autocomplete-richlistitem[actiontype="keyword"] .ac-title-box,
.autocomplete-richlistitem[actiontype="searchengine"] .ac-title-box,
.autocomplete-richlistitem[actiontype="visiturl"] .ac-title-box,
.autocomplete-richlistitem[type~="autofill"] .ac-title-box {
margin-top: 12px;
margin-bottom: 12px;

View File

@ -132,12 +132,14 @@ treechildren.autocomplete-treebody::-moz-tree-cell-text(selected) {
.autocomplete-richlistitem[actiontype="keyword"] .ac-url-box,
.autocomplete-richlistitem[actiontype="searchengine"] .ac-url-box,
.autocomplete-richlistitem[actiontype="visiturl"] .ac-url-box,
.autocomplete-richlistitem[type~="autofill"] .ac-url-box {
display: none;
}
.autocomplete-richlistitem[actiontype="keyword"] .ac-title-box,
.autocomplete-richlistitem[actiontype="searchengine"] .ac-title-box,
.autocomplete-richlistitem[actiontype="visiturl"] .ac-title-box,
.autocomplete-richlistitem[type~="autofill"] .ac-title-box {
margin-top: 12px;
margin-bottom: 12px;