mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-18 07:45:30 +00:00
Bug 1520494 - Speculative connections for the Quantum Bar. r=Standard8
Differential Revision: https://phabricator.services.mozilla.com/D17519 --HG-- rename : browser/components/urlbar/tests/legacy/browser_urlbar_search_speculative_connect_mousedown.js => browser/components/urlbar/tests/browser/browser_urlbar_speculative_connect.js rename : browser/components/urlbar/tests/legacy/browser_urlbar_search_no_speculative_connect_with_client_cert.js => browser/components/urlbar/tests/browser/browser_urlbar_speculative_connect_not_with_client_cert.js extra : moz-landing-system : lando
This commit is contained in:
parent
007d7ab14c
commit
a50d5dfc41
@ -111,7 +111,6 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
this.clickSelectsAll = this._prefs.getBoolPref("clickSelectsAll");
|
||||
this.doubleClickSelectsAll = this._prefs.getBoolPref("doubleClickSelectsAll");
|
||||
this.completeDefaultIndex = this._prefs.getBoolPref("autoFill");
|
||||
this.speculativeConnectEnabled = this._prefs.getBoolPref("speculativeConnect.enabled");
|
||||
this.urlbarSearchSuggestEnabled = this._prefs.getBoolPref("suggest.searches");
|
||||
this.timeout = this._prefs.getIntPref("delay");
|
||||
this._mayTrimURLs = this._prefs.getBoolPref("trimURLs");
|
||||
@ -1207,9 +1206,6 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
case "ctrlCanonizesURLs":
|
||||
this._ctrlCanonizesURLs = this._prefs.getBoolPref(aData);
|
||||
break;
|
||||
case "speculativeConnect.enabled":
|
||||
this.speculativeConnectEnabled = this._prefs.getBoolPref(aData);
|
||||
break;
|
||||
case "openintab":
|
||||
this.openInTab = this._prefs.getBoolPref(aData);
|
||||
break;
|
||||
@ -2463,19 +2459,6 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="maybeSetupSpeculativeConnect">
|
||||
<parameter name="aUriString"/>
|
||||
<body><![CDATA[
|
||||
try {
|
||||
let uri = makeURI(aUriString);
|
||||
Services.io.speculativeConnect2(uri, gBrowser.contentPrincipal, null);
|
||||
} catch (ex) {
|
||||
// Can't setup speculative connection for this uri string for some
|
||||
// reason, just ignore it.
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="onResultsAdded">
|
||||
<body>
|
||||
<![CDATA[
|
||||
@ -2513,20 +2496,18 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
// search and we are not in the private context, we can speculatively
|
||||
// connect to the intended site as a performance optimization.
|
||||
if (!this.input.gotResultForCurrentQuery &&
|
||||
this.input.speculativeConnectEnabled &&
|
||||
!this.input.inPrivateContext) {
|
||||
let firstStyle = this.input.mController.getStyleAt(0);
|
||||
if (firstStyle.includes("autofill")) {
|
||||
let uri = this.input.mController.getFinalCompleteValueAt(0);
|
||||
this.maybeSetupSpeculativeConnect(uri);
|
||||
UrlbarUtils.setupSpeculativeConnection(uri, window);
|
||||
} else if (firstStyle.includes("searchengine") &&
|
||||
this.input.browserSearchSuggestEnabled &&
|
||||
this.input.urlbarSearchSuggestEnabled) {
|
||||
// Preconnect to the current search engine only if the search
|
||||
// suggestions are enabled.
|
||||
let engine = Services.search.defaultEngine;
|
||||
engine.speculativeConnect({window,
|
||||
originAttributes: gBrowser.contentPrincipal.originAttributes});
|
||||
UrlbarUtils.setupSpeculativeConnection(engine, window);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2704,7 +2685,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.input.speculativeConnectEnabled) {
|
||||
if (!UrlbarPrefs.get("speculativeConnect.enabled")) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2723,29 +2704,21 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
return;
|
||||
}
|
||||
|
||||
let url = this.input.controller.getFinalCompleteValueAt(this.selectedIndex);
|
||||
|
||||
// Whitelist the cases that we want to speculative connect, and ignore
|
||||
// other moz-action uris or fancy protocols.
|
||||
// Note that it's likely we've speculatively connected to the first
|
||||
// url because it is a heuristic "autofill" result (see bug 1348275).
|
||||
// "moz-action:searchengine" is also the same case. (see bug 1355443)
|
||||
// So we won't duplicate the effort here.
|
||||
let url = this.input.controller.getFinalCompleteValueAt(this.selectedIndex);
|
||||
if (url.startsWith("http") && this.selectedIndex > 0) {
|
||||
this.maybeSetupSpeculativeConnect(url);
|
||||
UrlbarUtils.setupSpeculativeConnection(url, window);
|
||||
} else if (url.startsWith("moz-action:remotetab")) {
|
||||
// URL is in the format moz-action:ACTION,PARAMS
|
||||
// Where PARAMS is a JSON encoded object.
|
||||
const MOZ_ACTION_REGEX = /^moz-action:([^,]+),(.*)$/;
|
||||
if (!MOZ_ACTION_REGEX.test(url))
|
||||
return;
|
||||
|
||||
let params = JSON.parse(url.match(MOZ_ACTION_REGEX)[2]);
|
||||
if (params.url) {
|
||||
this.maybeSetupSpeculativeConnect(decodeURIComponent(params.url));
|
||||
let action = PlacesUtils.parseActionUrl(url);
|
||||
if (action && action.params.url) {
|
||||
UrlbarUtils.setupSpeculativeConnection(action.params.url, window);
|
||||
}
|
||||
}
|
||||
|
||||
]]></handler>
|
||||
|
||||
</handlers>
|
||||
|
@ -8,11 +8,13 @@ var EXPORTED_SYMBOLS = [
|
||||
"UrlbarController",
|
||||
];
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
AppConstants: "resource://gre/modules/AppConstants.jsm",
|
||||
// BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.jsm",
|
||||
PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
|
||||
UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
|
||||
UrlbarProvidersManager: "resource:///modules/UrlbarProvidersManager.jsm",
|
||||
UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
|
||||
});
|
||||
@ -100,6 +102,7 @@ class UrlbarController {
|
||||
this._notify("onQueryStarted", queryContext);
|
||||
await this.manager.startQuery(queryContext, this);
|
||||
this._notify("onQueryFinished", queryContext);
|
||||
return queryContext;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -136,9 +139,14 @@ class UrlbarController {
|
||||
this.input.autofill(queryContext.autofillValue);
|
||||
}
|
||||
|
||||
queryContext.lastResultCount = queryContext.results.length;
|
||||
// The first time we receive results try to connect to the heuristic result.
|
||||
if (queryContext.lastResultCount == 0) {
|
||||
this.speculativeConnect(queryContext, 0, "resultsadded");
|
||||
}
|
||||
|
||||
this._notify("onQueryResults", queryContext);
|
||||
// Update lastResultCount after notifying, so the view can use it.
|
||||
queryContext.lastResultCount = queryContext.results.length;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -240,6 +248,58 @@ class UrlbarController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to initialize a speculative connection on a result.
|
||||
* Speculative connections are only supported for a subset of all the results.
|
||||
* @param {UrlbarQueryContext} context The queryContext
|
||||
* @param {number} resultIndex index of the result to speculative connect to.
|
||||
* @param {string} reason Reason for the speculative connect request.
|
||||
* @note speculative connect to:
|
||||
* - Search engine heuristic results
|
||||
* - autofill results
|
||||
* - http/https results
|
||||
*/
|
||||
speculativeConnect(context, resultIndex, reason) {
|
||||
// Never speculative connect in private contexts.
|
||||
if (!this.input || context.isPrivate || context.results.length == 0) {
|
||||
return;
|
||||
}
|
||||
let result = context.results[resultIndex];
|
||||
let {url} = UrlbarUtils.getUrlFromResult(result);
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (reason) {
|
||||
case "resultsadded": {
|
||||
// We should connect to an heuristic result, if it exists.
|
||||
if (resultIndex == 0 && (context.preselected || context.autofillValue)) {
|
||||
if (result.type == UrlbarUtils.RESULT_TYPE.SEARCH) {
|
||||
// Speculative connect only if search suggestions are enabled.
|
||||
if (UrlbarPrefs.get("suggest.searches") &&
|
||||
UrlbarPrefs.get("browser.search.suggest.enabled")) {
|
||||
let engine = Services.search.defaultEngine;
|
||||
UrlbarUtils.setupSpeculativeConnection(engine, this.browserWindow);
|
||||
}
|
||||
} else if (context.autofillValue) {
|
||||
UrlbarUtils.setupSpeculativeConnection(url, this.browserWindow);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
case "mousedown": {
|
||||
// On mousedown, connect only to http/https urls.
|
||||
if (url.startsWith("http")) {
|
||||
UrlbarUtils.setupSpeculativeConnection(url, this.browserWindow);
|
||||
}
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
throw new Error("Invalid speculative connection reason");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function handling deletion of entries. We only support removing
|
||||
* of history entries - other result sources will be ignored.
|
||||
|
@ -57,6 +57,7 @@ class UrlbarInput {
|
||||
this.valueIsTyped = false;
|
||||
this.userInitiatedFocus = false;
|
||||
this.isPrivate = PrivateBrowsingUtils.isWindowPrivate(this.window);
|
||||
this.lastQueryContextPromise = Promise.resolve();
|
||||
this._untrimmedValue = "";
|
||||
this._suppressStartQuery = false;
|
||||
|
||||
@ -299,16 +300,15 @@ class UrlbarInput {
|
||||
// event, this.userSelectionBehavior);
|
||||
|
||||
let where = this._whereToOpen(event);
|
||||
let {url, postData} = UrlbarUtils.getUrlFromResult(result);
|
||||
let openParams = {
|
||||
postData: null,
|
||||
postData,
|
||||
allowInheritPrincipal: false,
|
||||
};
|
||||
|
||||
// TODO bug 1521702: Call _maybeCanonizeURL for autofilled results with the
|
||||
// typed string (not the autofilled one).
|
||||
|
||||
let url = result.payload.url;
|
||||
|
||||
switch (result.type) {
|
||||
case UrlbarUtils.RESULT_TYPE.TAB_SWITCH: {
|
||||
if (this._overrideDefaultAction(event)) {
|
||||
@ -322,7 +322,7 @@ class UrlbarInput {
|
||||
adoptIntoActiveWindow: UrlbarPrefs.get("switchTabs.adoptIntoActiveWindow"),
|
||||
};
|
||||
|
||||
if (this.window.switchToTabHavingURI(Services.io.newURI(result.payload.url), false, loadOpts) &&
|
||||
if (this.window.switchToTabHavingURI(Services.io.newURI(url), false, loadOpts) &&
|
||||
prevTab.isEmpty) {
|
||||
this.window.gBrowser.removeTab(prevTab);
|
||||
}
|
||||
@ -334,26 +334,26 @@ class UrlbarInput {
|
||||
if (url) {
|
||||
break;
|
||||
}
|
||||
|
||||
const actionDetails = {
|
||||
isSuggestion: !!result.payload.suggestion,
|
||||
alias: result.payload.keyword,
|
||||
};
|
||||
const engine = Services.search.getEngineByName(result.payload.engine);
|
||||
|
||||
[url, openParams.postData] = this._getSearchQueryUrl(
|
||||
engine, result.payload.suggestion || result.payload.query);
|
||||
this._recordSearch(engine, event, actionDetails);
|
||||
break;
|
||||
}
|
||||
case UrlbarUtils.RESULT_TYPE.OMNIBOX:
|
||||
case UrlbarUtils.RESULT_TYPE.OMNIBOX: {
|
||||
// Give the extension control of handling the command.
|
||||
ExtensionSearchHandler.handleInputEntered(result.payload.keyword,
|
||||
result.payload.content,
|
||||
where);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!url) {
|
||||
throw new Error(`Invalid url for result ${JSON.stringify(result)}`);
|
||||
}
|
||||
this._loadURL(url, where, openParams);
|
||||
}
|
||||
|
||||
@ -409,8 +409,12 @@ class UrlbarInput {
|
||||
UrlbarPrefs.get("autoFill") &&
|
||||
(!this._lastSearchString ||
|
||||
!this._lastSearchString.startsWith(searchString));
|
||||
this._lastSearchString = searchString;
|
||||
|
||||
this.controller.startQuery(new UrlbarQueryContext({
|
||||
// TODO (Bug 1522902): This promise is necessary for tests, because some
|
||||
// tests are not listening for completion when starting a query through
|
||||
// other methods than startQuery (input events for example).
|
||||
this.lastQueryContextPromise = this.controller.startQuery(new UrlbarQueryContext({
|
||||
enableAutofill,
|
||||
isPrivate: this.isPrivate,
|
||||
lastKey,
|
||||
@ -419,7 +423,6 @@ class UrlbarInput {
|
||||
providers: ["UnifiedComplete"],
|
||||
searchString,
|
||||
}));
|
||||
this._lastSearchString = searchString;
|
||||
}
|
||||
|
||||
typeRestrictToken(char) {
|
||||
@ -620,22 +623,6 @@ class UrlbarInput {
|
||||
event.metaKey : event.ctrlKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url to load for the search query and records in telemetry that it
|
||||
* is being loaded.
|
||||
*
|
||||
* @param {nsISearchEngine} engine
|
||||
* The engine to generate the query for.
|
||||
* @param {string} query
|
||||
* The query string to search for.
|
||||
* @returns {array}
|
||||
* Returns an array containing the query url (string) and the
|
||||
* post data (object).
|
||||
*/
|
||||
_getSearchQueryUrl(engine, query) {
|
||||
let submission = engine.getSubmission(query, null, "keyword");
|
||||
return [submission.uri.spec, submission.postData];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url to load for the search query and records in telemetry that it
|
||||
|
@ -97,6 +97,12 @@ const PREF_URLBAR_DEFAULTS = new Map([
|
||||
// should be opened in new tabs by default.
|
||||
["openintab", false],
|
||||
|
||||
// Whether the quantum bar is enabled.
|
||||
["quantumbar", false],
|
||||
|
||||
// Whether speculative connections should be enabled.
|
||||
["speculativeConnect.enabled", true],
|
||||
|
||||
// Results will include the user's bookmarks when this is true.
|
||||
["suggest.bookmark", true],
|
||||
|
||||
@ -130,6 +136,7 @@ const PREF_URLBAR_DEFAULTS = new Map([
|
||||
]);
|
||||
const PREF_OTHER_DEFAULTS = new Map([
|
||||
["keyword.enabled", true],
|
||||
["browser.search.suggest.enabled", true],
|
||||
]);
|
||||
|
||||
// Maps preferences under browser.urlbar.suggest to behavior names, as defined
|
||||
@ -183,7 +190,9 @@ class Preferences {
|
||||
Ci.nsISupportsWeakReference,
|
||||
]);
|
||||
Services.prefs.addObserver(PREF_URLBAR_BRANCH, this, true);
|
||||
Services.prefs.addObserver("keyword.enabled", this, true);
|
||||
for (let pref of PREF_OTHER_DEFAULTS.keys()) {
|
||||
Services.prefs.addObserver(pref, this, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -197,7 +197,6 @@ function convertResultToMatches(context, result, urls) {
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
matches.push(match);
|
||||
// Manage autofillValue and preselected properties for the first match.
|
||||
if (i == 0) {
|
||||
if (style.includes("autofill") && result.defaultIndex == 0) {
|
||||
@ -205,8 +204,10 @@ function convertResultToMatches(context, result, urls) {
|
||||
}
|
||||
if (style.includes("heuristic")) {
|
||||
context.preselected = true;
|
||||
match.heuristic = true;
|
||||
}
|
||||
}
|
||||
matches.push(match);
|
||||
}
|
||||
return {matches, done};
|
||||
}
|
||||
|
@ -308,7 +308,12 @@ class Query {
|
||||
throw new Error("Invalid provider passed to the add callback");
|
||||
}
|
||||
// Stop returning results as soon as we've been canceled.
|
||||
if (this.canceled || !this.acceptableSources.includes(match.source)) {
|
||||
if (this.canceled) {
|
||||
return;
|
||||
}
|
||||
// Check if the result source should be filtered out. Pay attention to the
|
||||
// heuristic result though, that is supposed to be added regardless.
|
||||
if (!this.acceptableSources.includes(match.source) && !match.heuristic) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -51,6 +51,10 @@ class UrlbarResult {
|
||||
}
|
||||
this.source = matchSource;
|
||||
|
||||
// May be used to indicate an heuristic result. Heuristic results can bypass
|
||||
// source filters in the ProvidersManager, that otherwise may skip them.
|
||||
this.heuristic = false;
|
||||
|
||||
// The payload contains result data. Some of the data is common across
|
||||
// multiple types, but most of it will vary.
|
||||
if (!payload || (typeof payload != "object")) {
|
||||
|
@ -17,7 +17,6 @@ var EXPORTED_SYMBOLS = [
|
||||
];
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
BinarySearch: "resource://gre/modules/BinarySearch.jsm",
|
||||
BrowserUtils: "resource://gre/modules/BrowserUtils.jsm",
|
||||
@ -25,6 +24,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
PlacesUIUtils: "resource:///modules/PlacesUIUtils.jsm",
|
||||
PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
|
||||
Services: "resource://gre/modules/Services.jsm",
|
||||
UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
|
||||
});
|
||||
|
||||
var UrlbarUtils = {
|
||||
@ -80,7 +80,7 @@ var UrlbarUtils = {
|
||||
// A bookmark keyword.
|
||||
// Payload: { icon, url, keyword, postData }
|
||||
KEYWORD: 4,
|
||||
// A WebExtension Omnibox match.
|
||||
// A WebExtension Omnibox result.
|
||||
// Payload: { icon, keyword, title, content }
|
||||
OMNIBOX: 5,
|
||||
// A tab from another synced device.
|
||||
@ -229,8 +229,85 @@ var UrlbarUtils = {
|
||||
return matches;
|
||||
}, []);
|
||||
},
|
||||
|
||||
/**
|
||||
* Extracts an url from a result, if possible.
|
||||
* @param {UrlbarResult} result The result to extract from.
|
||||
* @returns {object} a {url, postData} object, or null if a url can't be built
|
||||
* from this result.
|
||||
*/
|
||||
getUrlFromResult(result) {
|
||||
switch (result.type) {
|
||||
case UrlbarUtils.RESULT_TYPE.URL:
|
||||
case UrlbarUtils.RESULT_TYPE.KEYWORD:
|
||||
case UrlbarUtils.RESULT_TYPE.REMOTE_TAB:
|
||||
case UrlbarUtils.RESULT_TYPE.TAB_SWITCH:
|
||||
return {url: result.payload.url, postData: null};
|
||||
case UrlbarUtils.RESULT_TYPE.SEARCH: {
|
||||
const engine = Services.search.getEngineByName(result.payload.engine);
|
||||
let [url, postData] = getSearchQueryUrl(
|
||||
engine, result.payload.suggestion || result.payload.query);
|
||||
return {url, postData};
|
||||
}
|
||||
}
|
||||
return {url: null, postData: null};
|
||||
},
|
||||
|
||||
/**
|
||||
* Tries to initiate a speculative connection to a given url.
|
||||
* @param {nsISearchEngine|nsIURI|URL|string} urlOrEngine entity to initiate
|
||||
* a speculative connection for.
|
||||
* @param {window} window the window from where the connection is initialized.
|
||||
* @note This is not infallible, if a speculative connection cannot be
|
||||
* initialized, it will be a no-op.
|
||||
*/
|
||||
setupSpeculativeConnection(urlOrEngine, window) {
|
||||
if (!UrlbarPrefs.get("speculativeConnect.enabled")) {
|
||||
return;
|
||||
}
|
||||
if (urlOrEngine instanceof Ci.nsISearchEngine) {
|
||||
try {
|
||||
urlOrEngine.speculativeConnect({
|
||||
window,
|
||||
originAttributes: window.gBrowser.contentPrincipal.originAttributes,
|
||||
});
|
||||
} catch (ex) {
|
||||
// Can't setup speculative connection for this url, just ignore it.
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (urlOrEngine instanceof URL) {
|
||||
urlOrEngine = urlOrEngine.href;
|
||||
}
|
||||
|
||||
try {
|
||||
let uri = urlOrEngine instanceof Ci.nsIURI ? urlOrEngine
|
||||
: Services.io.newURI(urlOrEngine);
|
||||
Services.io.speculativeConnect2(uri, window.gBrowser.contentPrincipal, null);
|
||||
} catch (ex) {
|
||||
// Can't setup speculative connection for this url, just ignore it.
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the url to load for the search query and records in telemetry that it
|
||||
* is being loaded.
|
||||
*
|
||||
* @param {nsISearchEngine} engine
|
||||
* The engine to generate the query for.
|
||||
* @param {string} query
|
||||
* The query string to search for.
|
||||
* @returns {array}
|
||||
* Returns an array containing the query url (string) and the
|
||||
* post data (object).
|
||||
*/
|
||||
function getSearchQueryUrl(engine, query) {
|
||||
let submission = engine.getSubmission(query, null, "keyword");
|
||||
return [submission.uri.spec, submission.postData];
|
||||
}
|
||||
|
||||
/**
|
||||
* UrlbarQueryContext defines a user's autocomplete input from within the urlbar.
|
||||
* It supplements it with details of how the search results should be obtained
|
||||
@ -298,11 +375,11 @@ class UrlbarQueryContext {
|
||||
|
||||
/**
|
||||
* Base class for a muxer.
|
||||
* The muxer scope is to sort a given list of matches.
|
||||
* The muxer scope is to sort a given list of results.
|
||||
*/
|
||||
class UrlbarMuxer {
|
||||
/**
|
||||
* Unique name for the muxer, used by the context to sort matches.
|
||||
* Unique name for the muxer, used by the context to sort results.
|
||||
* Not using a unique name will cause the newest registration to win.
|
||||
* @abstract
|
||||
*/
|
||||
@ -310,8 +387,8 @@ class UrlbarMuxer {
|
||||
return "UrlbarMuxerBase";
|
||||
}
|
||||
/**
|
||||
* Sorts queryContext matches in-place.
|
||||
* @param {UrlbarQueryContext} queryContext the context to sort matches for.
|
||||
* Sorts queryContext results in-place.
|
||||
* @param {UrlbarQueryContext} queryContext the context to sort results for.
|
||||
* @abstract
|
||||
*/
|
||||
sort(queryContext) {
|
||||
@ -321,7 +398,7 @@ class UrlbarMuxer {
|
||||
|
||||
/**
|
||||
* Base class for a provider.
|
||||
* The provider scope is to query a datasource and return matches from it.
|
||||
* The provider scope is to query a datasource and return results from it.
|
||||
*/
|
||||
class UrlbarProvider {
|
||||
/**
|
||||
|
@ -35,6 +35,7 @@ class UrlbarView {
|
||||
this._rows = this.panel.querySelector(".urlbarView-results");
|
||||
|
||||
this._rows.addEventListener("mouseup", this);
|
||||
this._rows.addEventListener("mousedown", this);
|
||||
|
||||
// For the horizontal fade-out effect, set the overflow attribute on result
|
||||
// rows when they overflow.
|
||||
@ -141,6 +142,9 @@ class UrlbarView {
|
||||
if (queryContext.preselected) {
|
||||
this._selected = this._rows.firstElementChild;
|
||||
this._selected.toggleAttribute("selected", true);
|
||||
} else if (queryContext.lastResultCount == 0) {
|
||||
// Clear the selection when we get a new set of results.
|
||||
this._selected = null;
|
||||
}
|
||||
|
||||
this._openPanel();
|
||||
@ -374,6 +378,25 @@ class UrlbarView {
|
||||
}
|
||||
}
|
||||
|
||||
_on_mousedown(event) {
|
||||
if (event.button == 2) {
|
||||
// Ignore right clicks.
|
||||
return;
|
||||
}
|
||||
|
||||
let row = event.target;
|
||||
while (!row.classList.contains("urlbarView-row")) {
|
||||
row = row.parentNode;
|
||||
}
|
||||
let resultIndex = row.getAttribute("resultIndex");
|
||||
if (this._selected) {
|
||||
this._selected.toggleAttribute("selected", false);
|
||||
}
|
||||
this._selected = this._rows.children[resultIndex];
|
||||
this._selected.toggleAttribute("selected", true);
|
||||
this.controller.speculativeConnect(this._queryContext, resultIndex, "mousedown");
|
||||
}
|
||||
|
||||
_on_mouseup(event) {
|
||||
if (event.button == 2) {
|
||||
// Ignore right clicks.
|
||||
|
@ -9,85 +9,312 @@ ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
BrowserTestUtils: "resource://testing-common/BrowserTestUtils.jsm",
|
||||
PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
|
||||
TestUtils: "resource://testing-common/TestUtils.jsm",
|
||||
UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
|
||||
UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
|
||||
});
|
||||
|
||||
var UrlbarTestUtils = {
|
||||
promiseSearchComplete(win, dontAnimate = false) {
|
||||
return BrowserTestUtils.waitForPopupEvent(win.gURLBar.popup, "shown").then(() => {
|
||||
function searchIsComplete() {
|
||||
let isComplete = win.gURLBar.controller.searchStatus >=
|
||||
Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH;
|
||||
if (isComplete) {
|
||||
dump(`Restore popup dontAnimate value to ${dontAnimate}\n`);
|
||||
win.gURLBar.popup.setAttribute("dontanimate", dontAnimate);
|
||||
}
|
||||
return isComplete;
|
||||
/**
|
||||
* Waits to a search to be complete.
|
||||
* @param {object} win The window containing the urlbar
|
||||
* @param {function} restoreAnimationsFn optional Function to be used to
|
||||
* restore animations once done.
|
||||
* @returns {Promise} Resolved when done.
|
||||
*/
|
||||
promiseSearchComplete(win, restoreAnimationsFn = null) {
|
||||
let urlbar = getUrlbarAbstraction(win);
|
||||
return BrowserTestUtils.waitForPopupEvent(urlbar.panel, "shown").then(async () => {
|
||||
await urlbar.promiseSearchComplete();
|
||||
if (typeof restoreAnimations == "function") {
|
||||
restoreAnimationsFn();
|
||||
}
|
||||
|
||||
// Wait until there are at least two matches.
|
||||
return BrowserTestUtils.waitForCondition(searchIsComplete, "waiting urlbar search to complete");
|
||||
});
|
||||
},
|
||||
|
||||
promiseAutocompleteResultPopup(inputText, win, waitForFocus, fireInputEvent = false) {
|
||||
let dontAnimate = !!win.gURLBar.popup.getAttribute("dontanimate");
|
||||
waitForFocus(() => {
|
||||
dump(`Disable popup animation. Change dontAnimate value from ${dontAnimate} to true.\n`);
|
||||
win.gURLBar.popup.setAttribute("dontanimate", "true");
|
||||
win.gURLBar.focus();
|
||||
win.gURLBar.value = inputText;
|
||||
if (fireInputEvent) {
|
||||
// This is necessary to get the urlbar to set gBrowser.userTypedValue.
|
||||
let event = win.document.createEvent("Events");
|
||||
event.initEvent("input", true, true);
|
||||
win.gURLBar.dispatchEvent(event);
|
||||
}
|
||||
win.gURLBar.controller.startSearch(inputText);
|
||||
}, win);
|
||||
|
||||
return this.promiseSearchComplete(win, dontAnimate);
|
||||
},
|
||||
|
||||
async waitForAutocompleteResultAt(win, index) {
|
||||
let searchString = win.gURLBar.controller.searchString;
|
||||
await BrowserTestUtils.waitForCondition(
|
||||
() => win.gURLBar.popup.richlistbox.itemChildren.length > index &&
|
||||
win.gURLBar.popup.richlistbox.itemChildren[index].getAttribute("ac-text") == searchString.trim(),
|
||||
`Waiting for the autocomplete result for "${searchString}" at [${index}] to appear`);
|
||||
// Ensure the addition is complete, for proper mouse events on the entries.
|
||||
await new Promise(resolve => win.requestIdleCallback(resolve, {timeout: 1000}));
|
||||
return win.gURLBar.popup.richlistbox.itemChildren[index];
|
||||
},
|
||||
|
||||
promiseSuggestionsPresent(win, msg = "") {
|
||||
return TestUtils.waitForCondition(this.suggestionsPresent.bind(this, win),
|
||||
msg || "Waiting for suggestions");
|
||||
},
|
||||
|
||||
suggestionsPresent(win) {
|
||||
let controller = win.gURLBar.popup.input.controller;
|
||||
let matchCount = controller.matchCount;
|
||||
for (let i = 0; i < matchCount; i++) {
|
||||
let url = controller.getValueAt(i);
|
||||
let mozActionMatch = url.match(/^moz-action:([^,]+),(.*)$/);
|
||||
if (mozActionMatch) {
|
||||
let [, type, paramStr] = mozActionMatch;
|
||||
let params = JSON.parse(paramStr);
|
||||
if (type == "searchengine" && "searchSuggestion" in params) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Starts a search for a given string and waits for the search to be complete.
|
||||
* @param {string} inputText the search string
|
||||
* @param {object} win The window containing the urlbar
|
||||
* @param {function} waitForFocus The Simpletest function
|
||||
* @param {boolean} fireInputEvent whether an input event should be used when
|
||||
* starting the query (necessary to set userTypedValued)
|
||||
*/
|
||||
async promiseAutocompleteResultPopup(inputText, win, waitForFocus, fireInputEvent = false) {
|
||||
let urlbar = getUrlbarAbstraction(win);
|
||||
let restoreAnimationsFn = urlbar.disableAnimations();
|
||||
await new Promise(resolve => waitForFocus(resolve, win));
|
||||
urlbar.focus();
|
||||
urlbar.value = inputText;
|
||||
if (fireInputEvent) {
|
||||
// This is necessary to get the urlbar to set gBrowser.userTypedValue.
|
||||
urlbar.fireInputEvent();
|
||||
}
|
||||
return false;
|
||||
// In the quantum bar it's enough to fire the input event to start a query,
|
||||
// invoking startSearch would do it twice.
|
||||
if (!urlbar.quantumbar || !fireInputEvent) {
|
||||
urlbar.startSearch(inputText);
|
||||
}
|
||||
return this.promiseSearchComplete(win, restoreAnimationsFn);
|
||||
},
|
||||
|
||||
promiseSpeculativeConnection(httpserver) {
|
||||
return BrowserTestUtils.waitForCondition(() => {
|
||||
if (httpserver) {
|
||||
return httpserver.connectionNumber == 1;
|
||||
}
|
||||
return false;
|
||||
}, "Waiting for connection setup");
|
||||
/**
|
||||
* Waits for a result to be added at a certain index. Since we implement lazy
|
||||
* results replacement, even if we have a result at an index, it may be
|
||||
* related to the previous query, this methods ensures the result is current.
|
||||
* @param {object} win The window containing the urlbar
|
||||
* @param {number} index The index to look for
|
||||
*/
|
||||
async waitForAutocompleteResultAt(win, index) {
|
||||
let urlbar = getUrlbarAbstraction(win);
|
||||
return urlbar.promiseResultAt(index);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets an abstracted rapresentation of the result at an index.
|
||||
* @param {object} win The window containing the urlbar
|
||||
* @param {number} index The index to look for
|
||||
*/
|
||||
async getDetailsOfResultAt(win, index) {
|
||||
let urlbar = getUrlbarAbstraction(win);
|
||||
return urlbar.getDetailsOfResultAt(index);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the currently selected element.
|
||||
* @param {object} win The window containing the urlbar
|
||||
* @returns {HtmlElement|XulElement} the selected element.
|
||||
*/
|
||||
getSelectedElement(win) {
|
||||
let urlbar = getUrlbarAbstraction(win);
|
||||
return urlbar.getSelectedElement();
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the number of results.
|
||||
* You must wait for the query to be complete before using this.
|
||||
* @param {object} win The window containing the urlbar
|
||||
* @returns {number} the number of results.
|
||||
*/
|
||||
getResultCount(win) {
|
||||
let urlbar = getUrlbarAbstraction(win);
|
||||
return urlbar.getResultCount();
|
||||
},
|
||||
|
||||
/**
|
||||
* Ensures at least one search suggestion is present.
|
||||
* @param {object} win The window containing the urlbar
|
||||
* @returns {boolean} whether at least one search suggestion is present.
|
||||
*/
|
||||
promiseSuggestionsPresent(win) {
|
||||
let urlbar = getUrlbarAbstraction(win);
|
||||
return urlbar.promiseSearchSuggestions();
|
||||
},
|
||||
|
||||
/**
|
||||
* Waits for the given number of connections to an http server.
|
||||
* @param {object} httpserver an HTTP Server instance
|
||||
* @param {number} count Number of connections to wait for
|
||||
* @returns {Promise} resolved when all the expected connections were started.
|
||||
*/
|
||||
promiseSpeculativeConnections(httpserver, count) {
|
||||
if (!httpserver) {
|
||||
throw new Error("Must provide an http server");
|
||||
}
|
||||
return BrowserTestUtils.waitForCondition(
|
||||
() => httpserver.connectionNumber == count,
|
||||
"Waiting for speculative connection setup"
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Maps windows to urlbar abstractions.
|
||||
*/
|
||||
var gUrlbarAbstractions = new WeakMap();
|
||||
|
||||
function getUrlbarAbstraction(win) {
|
||||
if (!gUrlbarAbstractions.has(win)) {
|
||||
gUrlbarAbstractions.set(win, new UrlbarAbstraction(win));
|
||||
}
|
||||
return gUrlbarAbstractions.get(win);
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstracts the urlbar implementation, so it can be used regardless of
|
||||
* Quantum Bar being enabled.
|
||||
*/
|
||||
class UrlbarAbstraction {
|
||||
constructor(win) {
|
||||
if (!win) {
|
||||
throw new Error("Must provide a browser window");
|
||||
}
|
||||
this.urlbar = win.gURLBar;
|
||||
this.quantumbar = UrlbarPrefs.get("quantumbar");
|
||||
this.window = win;
|
||||
this.window.addEventListener("unload", () => {
|
||||
this.urlbar = null;
|
||||
this.window = null;
|
||||
}, {once: true});
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable animations.
|
||||
* @returns {function} can be invoked to restore the previous behavior.
|
||||
*/
|
||||
disableAnimations() {
|
||||
if (!this.quantumbar) {
|
||||
let dontAnimate = !!this.urlbar.popup.getAttribute("dontanimate");
|
||||
this.urlbar.popup.setAttribute("dontanimate", "true");
|
||||
return () => {
|
||||
this.urlbar.popup.setAttribute("dontanimate", dontAnimate);
|
||||
};
|
||||
}
|
||||
return () => {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Focus the input field.
|
||||
*/
|
||||
focus() {
|
||||
this.urlbar.inputField.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an input event to the input field.
|
||||
*/
|
||||
fireInputEvent() {
|
||||
let event = this.window.document.createEvent("Events");
|
||||
event.initEvent("input", true, true);
|
||||
this.urlbar.inputField.dispatchEvent(event);
|
||||
}
|
||||
|
||||
set value(val) {
|
||||
this.urlbar.value = val;
|
||||
}
|
||||
get value() {
|
||||
return this.urlbar.value;
|
||||
}
|
||||
|
||||
get panel() {
|
||||
return this.quantumbar ? this.urlbar.panel : this.urlbar.popup;
|
||||
}
|
||||
|
||||
startSearch(text) {
|
||||
if (this.quantumbar) {
|
||||
this.urlbar.value = text;
|
||||
this.urlbar.startQuery();
|
||||
} else {
|
||||
this.urlbar.controller.startSearch(text);
|
||||
}
|
||||
}
|
||||
|
||||
promiseSearchComplete() {
|
||||
if (this.quantumbar) {
|
||||
return this.urlbar.lastQueryContextPromise;
|
||||
}
|
||||
return BrowserTestUtils.waitForCondition(
|
||||
() => this.urlbar.controller.searchStatus >=
|
||||
Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH,
|
||||
"waiting urlbar search to complete");
|
||||
}
|
||||
|
||||
async promiseResultAt(index) {
|
||||
if (!this.quantumbar) {
|
||||
// In the legacy address bar, old results are replaced when new results
|
||||
// arrive, thus it's possible for a result to be present but refer to
|
||||
// a previous query. This ensures the given result refers to the current
|
||||
// query by checking its query string against the string being searched
|
||||
// for.
|
||||
let searchString = this.urlbar.controller.searchString;
|
||||
await BrowserTestUtils.waitForCondition(
|
||||
() => this.panel.richlistbox.itemChildren.length > index &&
|
||||
this.panel.richlistbox.itemChildren[index].getAttribute("ac-text") == searchString.trim(),
|
||||
`Waiting for the autocomplete result for "${searchString}" at [${index}] to appear`);
|
||||
// Ensure the addition is complete, for proper mouse events on the entries.
|
||||
await new Promise(resolve => this.window.requestIdleCallback(resolve, {timeout: 1000}));
|
||||
return this.panel.richlistbox.itemChildren[index];
|
||||
}
|
||||
// TODO: Quantum Bar doesn't yet implement lazy results replacement.
|
||||
await this.promiseSearchComplete();
|
||||
if (index >= this.urlbar.view._rows.length) {
|
||||
throw new Error("Not enough results");
|
||||
}
|
||||
return this.urlbar.view._rows.children[index];
|
||||
}
|
||||
|
||||
getSelectedElement() {
|
||||
if (this.quantumbar) {
|
||||
return this.urlbar.view._selected || null;
|
||||
}
|
||||
return this.panel.selectedIndex >= 0 ?
|
||||
this.panel.richlistbox.itemChildren[this.panel.selectedIndex] : null;
|
||||
}
|
||||
|
||||
getResultCount() {
|
||||
return this.quantumbar ? this.urlbar.view._rows.children.length
|
||||
: this.urlbar.controller.matchCount;
|
||||
}
|
||||
|
||||
async getDetailsOfResultAt(index) {
|
||||
await this.promiseResultAt(index);
|
||||
function getType(style, action) {
|
||||
if (style.includes("searchengine") || style.includes("suggestions")) {
|
||||
return UrlbarUtils.RESULT_TYPE.SEARCH;
|
||||
} else if (style.includes("extension")) {
|
||||
return UrlbarUtils.RESULT_TYPE.OMNIBOX;
|
||||
} else if (action && action.type == "keyword") {
|
||||
return UrlbarUtils.RESULT_TYPE.KEYWORD;
|
||||
} else if (action && action.type == "remotetab") {
|
||||
return UrlbarUtils.RESULT_TYPE.REMOTE_TAB;
|
||||
} else if (action && action.type == "switchtab") {
|
||||
return UrlbarUtils.RESULT_TYPE.TAB_SWITCH;
|
||||
}
|
||||
return UrlbarUtils.RESULT_TYPE.URL;
|
||||
}
|
||||
let details = {};
|
||||
if (this.quantumbar) {
|
||||
let context = await this.urlbar.lastQueryContextPromise;
|
||||
details.url = (UrlbarUtils.getUrlFromResult(context.results[index])).url;
|
||||
details.type = context.results[index].type;
|
||||
details.autofill = index == 0 && context.autofillValue;
|
||||
} else {
|
||||
details.url = this.urlbar.controller.getFinalCompleteValueAt(index);
|
||||
let style = this.urlbar.controller.getStyleAt(index);
|
||||
let action = PlacesUtils.parseActionUrl(this.urlbar.controller.getValueAt(index));
|
||||
details.type = getType(style, action);
|
||||
details.autofill = style.includes("autofill");
|
||||
}
|
||||
return details;
|
||||
}
|
||||
|
||||
async promiseSearchSuggestions() {
|
||||
if (!this.quantumbar) {
|
||||
return TestUtils.waitForCondition(() => {
|
||||
let controller = this.urlbar.controller;
|
||||
let matchCount = controller.matchCount;
|
||||
for (let i = 0; i < matchCount; i++) {
|
||||
let url = controller.getValueAt(i);
|
||||
let action = PlacesUtils.parseActionUrl(url);
|
||||
if (action && action.type == "searchengine" &&
|
||||
action.params.searchSuggestion) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, "Waiting for suggestions");
|
||||
}
|
||||
// TODO: Quantum Bar doesn't yet implement lazy results replacement. When
|
||||
// we do that, we'll have to be sure the suggestions we find are relevant
|
||||
// for the current query. For now let's just wait for the search to be
|
||||
// complete.
|
||||
return this.promiseSearchComplete().then(context => {
|
||||
// Look for search suggestions.
|
||||
if (!context.results.some(r => r.type == UrlbarUtils.RESULT_TYPE.SEARCH &&
|
||||
r.payload.suggestion)) {
|
||||
throw new Error("Cannot find a search suggestion");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,11 @@ support-files =
|
||||
[browser_urlbar_remoteness_switch.js]
|
||||
run-if = e10s
|
||||
[browser_urlbar_searchsettings.js]
|
||||
[browser_urlbar_speculative_connect.js]
|
||||
support-files =
|
||||
searchSuggestionEngine2.xml
|
||||
searchSuggestionEngine.sjs
|
||||
[browser_urlbar_speculative_connect_not_with_client_cert.js]
|
||||
[browser_urlbar_whereToOpen.js]
|
||||
[browser_urlbarCopying.js]
|
||||
subsuite = clipboard
|
||||
|
@ -0,0 +1,152 @@
|
||||
/* 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 test ensures that we setup a speculative network connection to
|
||||
// the site in various cases:
|
||||
// 1. search engine if it's the first result
|
||||
// 2. mousedown event before the http request happens(in mouseup).
|
||||
|
||||
const TEST_ENGINE_BASENAME = "searchSuggestionEngine2.xml";
|
||||
|
||||
const serverInfo = {
|
||||
scheme: "http",
|
||||
host: "localhost",
|
||||
port: 20709, // Must be identical to what is in searchSuggestionEngine2.xml
|
||||
};
|
||||
|
||||
add_task(async function setup() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["browser.urlbar.autoFill", true],
|
||||
["browser.urlbar.suggest.searches", true],
|
||||
["browser.urlbar.speculativeConnect.enabled", true],
|
||||
// In mochitest this number is 0 by default but we have to turn it on.
|
||||
["network.http.speculative-parallel-limit", 6],
|
||||
// The http server is using IPv4, so it's better to disable IPv6 to avoid
|
||||
// weird networking problem.
|
||||
["network.dns.disableIPv6", true],
|
||||
],
|
||||
});
|
||||
|
||||
// Ensure we start from a clean situation.
|
||||
await PlacesUtils.history.clear();
|
||||
|
||||
await PlacesTestUtils.addVisits([{
|
||||
uri: `${serverInfo.scheme}://${serverInfo.host}:${serverInfo.port}`,
|
||||
title: "test visit for speculative connection",
|
||||
transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
|
||||
}]);
|
||||
|
||||
let engine = await SearchTestUtils.promiseNewSearchEngine(
|
||||
getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME);
|
||||
let oldCurrentEngine = Services.search.defaultEngine;
|
||||
Services.search.defaultEngine = engine;
|
||||
|
||||
registerCleanupFunction(async function() {
|
||||
await PlacesUtils.history.clear();
|
||||
Services.search.defaultEngine = oldCurrentEngine;
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function search_test() {
|
||||
// We speculative connect to the search engine only if suggestions are enabled.
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["browser.search.suggest.enabled", true],
|
||||
],
|
||||
});
|
||||
await withHttpServer(serverInfo, async server => {
|
||||
let connectionNumber = server.connectionNumber;
|
||||
info("Searching for 'foo'");
|
||||
await promiseAutocompleteResultPopup("foo", window, true);
|
||||
// Check if the first result is with type "searchengine"
|
||||
let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
|
||||
Assert.equal(details.type, UrlbarUtils.RESULT_TYPE.SEARCH, "The first result is a search");
|
||||
await UrlbarTestUtils.promiseSpeculativeConnections(server, connectionNumber + 1);
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function popup_mousedown_test() {
|
||||
// Disable search suggestions and autofill, to avoid other speculative
|
||||
// connections.
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["browser.search.suggest.enabled", false],
|
||||
["browser.urlbar.autoFill", false],
|
||||
],
|
||||
});
|
||||
await withHttpServer(serverInfo, async server => {
|
||||
let connectionNumber = server.connectionNumber;
|
||||
let searchString = "ocal";
|
||||
let completeValue = `${serverInfo.scheme}://${serverInfo.host}:${serverInfo.port}/`;
|
||||
info(`Searching for '${searchString}'`);
|
||||
|
||||
await promiseAutocompleteResultPopup(searchString, window, true);
|
||||
let listitem = await waitForAutocompleteResultAt(1);
|
||||
let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
|
||||
Assert.equal(details.url, completeValue, "The second item has the url we visited.");
|
||||
|
||||
info("Clicking on the second result");
|
||||
EventUtils.synthesizeMouseAtCenter(listitem, {type: "mousedown"}, window);
|
||||
Assert.equal(UrlbarTestUtils.getSelectedElement(window), listitem, "The second item is selected");
|
||||
await UrlbarTestUtils.promiseSpeculativeConnections(server, connectionNumber + 1);
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_autofill() {
|
||||
// Disable search suggestions but enable autofill.
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["browser.search.suggest.enabled", false],
|
||||
["browser.urlbar.autoFill", true],
|
||||
],
|
||||
});
|
||||
await withHttpServer(serverInfo, async server => {
|
||||
let connectionNumber = server.connectionNumber;
|
||||
let searchString = serverInfo.host;
|
||||
info(`Searching for '${searchString}'`);
|
||||
|
||||
await promiseAutocompleteResultPopup(searchString, window, true);
|
||||
let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
|
||||
let completeValue = `${serverInfo.scheme}://${serverInfo.host}:${serverInfo.port}/`;
|
||||
Assert.equal(details.url, completeValue, `Autofilled value is as expected`);
|
||||
await UrlbarTestUtils.promiseSpeculativeConnections(server, connectionNumber + 1);
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_autofill_privateContext() {
|
||||
info("Autofill in private context.");
|
||||
let privateWin = await BrowserTestUtils.openNewBrowserWindow({private: true});
|
||||
registerCleanupFunction(async () => {
|
||||
let promisePBExit = TestUtils.topicObserved("last-pb-context-exited");
|
||||
await BrowserTestUtils.closeWindow(privateWin);
|
||||
await promisePBExit;
|
||||
});
|
||||
await withHttpServer(serverInfo, async server => {
|
||||
let connectionNumber = server.connectionNumber;
|
||||
let searchString = serverInfo.host;
|
||||
info(`Searching for '${searchString}'`);
|
||||
|
||||
await promiseAutocompleteResultPopup(searchString, privateWin, true);
|
||||
let details = await UrlbarTestUtils.getDetailsOfResultAt(privateWin, 0);
|
||||
let completeValue = `${serverInfo.scheme}://${serverInfo.host}:${serverInfo.port}/`;
|
||||
Assert.equal(details.url, completeValue, `Autofilled value is as expected`);
|
||||
await UrlbarTestUtils.promiseSpeculativeConnections(server, connectionNumber);
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_no_heuristic_result() {
|
||||
info("Don't speculative connect on results addition if there's no heuristic");
|
||||
await withHttpServer(serverInfo, async server => {
|
||||
let connectionNumber = server.connectionNumber;
|
||||
info(`Searching for the empty string`);
|
||||
await promiseAutocompleteResultPopup("", window, true);
|
||||
ok(UrlbarTestUtils.getResultCount(window) > 0, "Has results");
|
||||
let result = await UrlbarTestUtils.getSelectedElement(window);
|
||||
Assert.strictEqual(result, null, `Should have no selection`);
|
||||
await UrlbarTestUtils.promiseSpeculativeConnections(server, connectionNumber);
|
||||
});
|
||||
});
|
@ -142,26 +142,19 @@ add_task(async function setup() {
|
||||
});
|
||||
|
||||
add_task(async function popup_mousedown_no_client_cert_dialog_until_navigate_test() {
|
||||
const test = {
|
||||
// To not trigger autofill, search keyword starts from the second character.
|
||||
search: host.substr(1, 4),
|
||||
completeValue: uri,
|
||||
};
|
||||
info(`Searching for '${test.search}'`);
|
||||
await promiseAutocompleteResultPopup(test.search, window, true);
|
||||
await waitForAutocompleteResultAt(1);
|
||||
let controller = gURLBar.popup.input.controller;
|
||||
// The first item should be 'Search with ...' thus we want the second.
|
||||
let value = controller.getFinalCompleteValueAt(1);
|
||||
info(`The value of the second item is ${value}`);
|
||||
is(value, test.completeValue, "The second item has the url we visited.");
|
||||
|
||||
let listitem = await waitForAutocompleteResultAt(1);
|
||||
Assert.ok(BrowserTestUtils.is_visible(listitem), "The node is there.");
|
||||
// To not trigger autofill, search keyword starts from the second character.
|
||||
let searchString = host.substr(1, 4);
|
||||
let completeValue = uri;
|
||||
info(`Searching for '${searchString}'`);
|
||||
await promiseAutocompleteResultPopup(searchString, window, true);
|
||||
let listitem = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1);
|
||||
let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
|
||||
info(`The url of the second item is ${details.url}`);
|
||||
is(details.url, completeValue, "The second item has the url we visited.");
|
||||
|
||||
expectingChooseCertificate = false;
|
||||
EventUtils.synthesizeMouseAtCenter(listitem, {type: "mousedown"}, window);
|
||||
is(gURLBar.popup.richlistbox.selectedIndex, 1, "The second item is selected");
|
||||
is(UrlbarTestUtils.getSelectedElement(window), listitem, "The second item is selected");
|
||||
|
||||
// We shouldn't have triggered a speculative connection, because a client
|
||||
// certificate is installed.
|
@ -24,17 +24,37 @@ function is_element_hidden(element, msg) {
|
||||
ok(BrowserTestUtils.is_hidden(element), msg || "Element should be hidden");
|
||||
}
|
||||
|
||||
function runHttpServer(scheme, host, port = -1) {
|
||||
let httpserver = new HttpServer();
|
||||
/**
|
||||
* Initializes an HTTP Server, and runs a task with it.
|
||||
* @param {object} details {scheme, host, port}
|
||||
* @param {function} taskFn The task to run, gets the server as argument.
|
||||
*/
|
||||
async function withHttpServer(details = { scheme: "http", host: "localhost", port: -1}, taskFn) {
|
||||
let server = new HttpServer();
|
||||
let url = `${details.scheme}://${details.host}:${details.port}`;
|
||||
try {
|
||||
httpserver.start(port);
|
||||
port = httpserver.identity.primaryPort;
|
||||
httpserver.identity.setPrimary(scheme, host, port);
|
||||
} catch (ex) {
|
||||
info("We can't launch our http server successfully.");
|
||||
info(`starting HTTP Server for ${url}`);
|
||||
try {
|
||||
server.start(details.port);
|
||||
details.port = server.identity.primaryPort;
|
||||
server.identity.setPrimary(details.scheme, details.host, details.port);
|
||||
} catch (ex) {
|
||||
throw ("We can't launch our http server successfully. " + ex);
|
||||
}
|
||||
Assert.ok(server.identity.has(details.scheme, details.host, details.port),
|
||||
`${url} is listening.`);
|
||||
try {
|
||||
await taskFn(server);
|
||||
} catch (ex) {
|
||||
throw new Error("Exception in the task function " + ex);
|
||||
}
|
||||
} finally {
|
||||
server.identity.remove(details.scheme, details.host, details.port);
|
||||
try {
|
||||
server.stop(() => {});
|
||||
} catch (ex) {}
|
||||
server = null;
|
||||
}
|
||||
is(httpserver.identity.has(scheme, host, port), true, `${scheme}://${host}:${port} is listening.`);
|
||||
return httpserver;
|
||||
}
|
||||
|
||||
function promisePopupShown(popup) {
|
||||
@ -56,10 +76,6 @@ function promiseAutocompleteResultPopup(inputText,
|
||||
win, waitForFocus, fireInputEvent);
|
||||
}
|
||||
|
||||
function promiseSpeculativeConnection(httpserver) {
|
||||
return UrlbarTestUtils.promiseSpeculativeConnection(httpserver);
|
||||
}
|
||||
|
||||
async function waitForAutocompleteResultAt(index) {
|
||||
return UrlbarTestUtils.waitForAutocompleteResultAt(window, index);
|
||||
}
|
||||
|
@ -101,13 +101,6 @@ support-files =
|
||||
[browser_urlbar_autoFill_backspaced.js]
|
||||
[browser_urlbar_canonize_on_autofill.js]
|
||||
[browser_urlbar_remove_match.js]
|
||||
[browser_urlbar_search_speculative_connect.js]
|
||||
[browser_urlbar_search_speculative_connect_engine.js]
|
||||
support-files =
|
||||
../browser/searchSuggestionEngine2.xml
|
||||
../browser/searchSuggestionEngine.sjs
|
||||
[browser_urlbar_search_speculative_connect_mousedown.js]
|
||||
[browser_urlbar_search_no_speculative_connect_with_client_cert.js]
|
||||
[browser_urlbar_stop_pending.js]
|
||||
support-files =
|
||||
../browser/slow-page.sjs
|
||||
@ -154,6 +147,11 @@ support-files =
|
||||
[../browser/browser_urlbarSearchSingleWordNotification.js]
|
||||
[../browser/browser_urlbarUpdateForDomainCompletion.js]
|
||||
[../browser/browser_urlbar_searchsettings.js]
|
||||
[../browser/browser_urlbar_speculative_connect.js]
|
||||
support-files =
|
||||
../browser/searchSuggestionEngine2.xml
|
||||
../browser/searchSuggestionEngine.sjs
|
||||
[../browser/browser_urlbar_speculative_connect_not_with_client_cert.js]
|
||||
[../browser/browser_urlbar_remoteness_switch.js]
|
||||
run-if = e10s
|
||||
[../browser/browser_wyciwyg_urlbarCopying.js]
|
||||
|
@ -56,7 +56,7 @@ add_task(async function focus() {
|
||||
EventUtils.sendString("rnd");
|
||||
await promiseSearchComplete();
|
||||
await waitForAutocompleteResultAt(0);
|
||||
Assert.ok(suggestionsPresent());
|
||||
await promiseSuggestionsPresent();
|
||||
assertVisible(true);
|
||||
assertFooterVisible(true);
|
||||
|
||||
|
@ -1,100 +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 test ensures that we setup a speculative network
|
||||
// connection for autoFilled values.
|
||||
|
||||
let gHttpServer = null;
|
||||
let gScheme = "http";
|
||||
let gHost = "localhost"; // 'localhost' by default.
|
||||
let gPort = -1;
|
||||
let gPrivateWin = null;
|
||||
let gIsSpeculativeConnected = false;
|
||||
|
||||
let gTest;
|
||||
|
||||
add_task(async function setup() {
|
||||
gHttpServer = runHttpServer(gScheme, gHost);
|
||||
// The server will be run on a random port if the port number wasn't given.
|
||||
gPort = gHttpServer.identity.primaryPort;
|
||||
|
||||
gTest = {
|
||||
search: gHost.substr(0, 2),
|
||||
autofilledValue: `${gHost}:${gPort}/`,
|
||||
};
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["browser.urlbar.autoFill", true],
|
||||
// Turn off search suggestion so we won't speculative connect to the search engine.
|
||||
["browser.search.suggest.enabled", false],
|
||||
["browser.urlbar.speculativeConnect.enabled", true],
|
||||
// In mochitest this number is 0 by default but we have to turn it on.
|
||||
["network.http.speculative-parallel-limit", 6],
|
||||
// The http server is using IPv4, so it's better to disable IPv6 to avoid weird
|
||||
// networking problem.
|
||||
["network.dns.disableIPv6", true]],
|
||||
});
|
||||
|
||||
await PlacesTestUtils.addVisits([{
|
||||
uri: `${gScheme}://${gHost}:${gPort}`,
|
||||
title: "test visit for speculative connection",
|
||||
transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
|
||||
}]);
|
||||
|
||||
gPrivateWin = await BrowserTestUtils.openNewBrowserWindow({private: true});
|
||||
is(PrivateBrowsingUtils.isWindowPrivate(gPrivateWin), true, "A private window created.");
|
||||
|
||||
// Bug 764062 - we can't get port number from autocomplete result, so we have to mock
|
||||
// this function and add it manually.
|
||||
let oldSpeculativeConnect = gURLBar.popup.maybeSetupSpeculativeConnect.bind(gURLBar.popup);
|
||||
let newSpeculativeConnect = (uriString) => {
|
||||
gIsSpeculativeConnected = true;
|
||||
oldSpeculativeConnect(uriString);
|
||||
};
|
||||
gURLBar.popup.maybeSetupSpeculativeConnect = newSpeculativeConnect;
|
||||
gPrivateWin.gURLBar.popup.maybeSetupSpeculativeConnect = newSpeculativeConnect;
|
||||
|
||||
registerCleanupFunction(async function() {
|
||||
await PlacesUtils.history.clear();
|
||||
gURLBar.popup.maybeSetupSpeculativeConnect = oldSpeculativeConnect;
|
||||
gPrivateWin.gURLBar.popup.maybeSetupSpeculativeConnect = oldSpeculativeConnect;
|
||||
gHttpServer.identity.remove(gScheme, gHost, gPort);
|
||||
await new Promise(resolve => {
|
||||
gHttpServer.stop(() => {
|
||||
gHttpServer = null;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
await BrowserTestUtils.closeWindow(gPrivateWin);
|
||||
gScheme = null;
|
||||
gHost = null;
|
||||
gPort = null;
|
||||
gPrivateWin = null;
|
||||
gIsSpeculativeConnected = null;
|
||||
gTest = null;
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function autofill_tests() {
|
||||
gIsSpeculativeConnected = false;
|
||||
info(`Searching for '${gTest.search}'`);
|
||||
await promiseAutocompleteResultPopup(gTest.search, window, true);
|
||||
is(gURLBar.inputField.value, gTest.autofilledValue,
|
||||
`Autofilled value is as expected for search '${gTest.search}'`);
|
||||
is(gIsSpeculativeConnected, true, "Speculative connection should be called");
|
||||
await promiseSpeculativeConnection(gHttpServer);
|
||||
is(gHttpServer.connectionNumber, 1, `${gHttpServer.connectionNumber} speculative connection has been setup.`);
|
||||
});
|
||||
|
||||
add_task(async function privateContext_test() {
|
||||
info("In private context.");
|
||||
gIsSpeculativeConnected = false;
|
||||
info(`Searching for '${gTest.search}'`);
|
||||
await promiseAutocompleteResultPopup(gTest.search, gPrivateWin, true);
|
||||
is(gPrivateWin.gURLBar.inputField.value, gTest.autofilledValue,
|
||||
`Autofilled value is as expected for search '${gTest.search}'`);
|
||||
is(gIsSpeculativeConnected, false, "Speculative connection shouldn't be called");
|
||||
});
|
@ -1,56 +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 test ensures that we setup a speculative network connection to
|
||||
// current search engine if the first result is 'searchengine'.
|
||||
|
||||
let gHttpServer = null;
|
||||
let gScheme = "http";
|
||||
let gHost = "localhost"; // 'localhost' by default.
|
||||
let gPort = 20709; // the port number must be identical to what we said in searchSuggestionEngine2.xml
|
||||
const TEST_ENGINE_BASENAME = "searchSuggestionEngine2.xml";
|
||||
|
||||
add_task(async function setup() {
|
||||
gHttpServer = runHttpServer(gScheme, gHost, gPort);
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["browser.urlbar.autoFill", true],
|
||||
// Make sure search suggestion for location bar is enabled
|
||||
["browser.search.suggest.enabled", true],
|
||||
["browser.urlbar.suggest.searches", true],
|
||||
["browser.urlbar.speculativeConnect.enabled", true],
|
||||
// In mochitest this number is 0 by default but we have to turn it on.
|
||||
["network.http.speculative-parallel-limit", 6],
|
||||
// The http server is using IPv4, so it's better to disable IPv6 to avoid weird
|
||||
// networking problem.
|
||||
["network.dns.disableIPv6", true]],
|
||||
});
|
||||
|
||||
let engine = await SearchTestUtils.promiseNewSearchEngine(
|
||||
getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME);
|
||||
let oldCurrentEngine = Services.search.defaultEngine;
|
||||
Services.search.defaultEngine = engine;
|
||||
|
||||
registerCleanupFunction(async function() {
|
||||
await PlacesUtils.history.clear();
|
||||
Services.search.defaultEngine = oldCurrentEngine;
|
||||
gHttpServer.identity.remove(gScheme, gHost, gPort);
|
||||
gHttpServer.stop(() => {
|
||||
gHttpServer = null;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function connect_search_engine_tests() {
|
||||
info("Searching for 'foo'");
|
||||
await promiseAutocompleteResultPopup("foo", window, true);
|
||||
// Check if the first result is with type "searchengine"
|
||||
let controller = gURLBar.popup.input.controller;
|
||||
let style = controller.getStyleAt(0);
|
||||
is(style.includes("searchengine"), true, "The first result type is searchengine");
|
||||
await promiseSpeculativeConnection(gHttpServer);
|
||||
is(gHttpServer.connectionNumber, 1, `${gHttpServer.connectionNumber} speculative connection has been setup.`);
|
||||
});
|
@ -1,73 +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 test ensures that we setup a speculative network connection to
|
||||
// the site in mousedown event before the http request happens(in mouseup).
|
||||
|
||||
let gHttpServer = null;
|
||||
let gScheme = "http";
|
||||
let gHost = "localhost"; // 'localhost' by default.
|
||||
let gPort = -1;
|
||||
let gIsSpeculativeConnected = false;
|
||||
|
||||
add_task(async function setup() {
|
||||
gHttpServer = runHttpServer(gScheme, gHost, gPort);
|
||||
// The server will be run on a random port if the port number wasn't given.
|
||||
gPort = gHttpServer.identity.primaryPort;
|
||||
|
||||
await PlacesTestUtils.addVisits([{
|
||||
uri: `${gScheme}://${gHost}:${gPort}`,
|
||||
title: "test visit for speculative connection",
|
||||
transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
|
||||
}]);
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["browser.urlbar.autoFill", true],
|
||||
// Turn off search suggestion so we won't speculative connect to the search engine.
|
||||
["browser.search.suggest.enabled", false],
|
||||
["browser.urlbar.speculativeConnect.enabled", true],
|
||||
// In mochitest this number is 0 by default but we have to turn it on.
|
||||
["network.http.speculative-parallel-limit", 6],
|
||||
// The http server is using IPv4, so it's better to disable IPv6 to avoid weird
|
||||
// networking problem.
|
||||
["network.dns.disableIPv6", true]],
|
||||
});
|
||||
|
||||
registerCleanupFunction(async function() {
|
||||
await PlacesUtils.history.clear();
|
||||
gHttpServer.identity.remove(gScheme, gHost, gPort);
|
||||
gHttpServer.stop(() => {
|
||||
gHttpServer = null;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function popup_mousedown_tests() {
|
||||
const test = {
|
||||
// To not trigger autofill, search keyword starts from the second character.
|
||||
search: gHost.substr(1, 4),
|
||||
completeValue: `${gScheme}://${gHost}:${gPort}/`,
|
||||
};
|
||||
info(`Searching for '${test.search}'`);
|
||||
await promiseAutocompleteResultPopup(test.search, window, true);
|
||||
// Check if the first result is with type "searchengine"
|
||||
let controller = gURLBar.popup.input.controller;
|
||||
// The first item should be 'Search with ...' thus we wan the second.
|
||||
let value = controller.getFinalCompleteValueAt(1);
|
||||
info(`The value of the second item is ${value}`);
|
||||
is(value, test.completeValue, "The second item has the url we visited.");
|
||||
|
||||
await BrowserTestUtils.waitForCondition(() => {
|
||||
return !!gURLBar.popup.richlistbox.children[1] &&
|
||||
BrowserTestUtils.is_visible(gURLBar.popup.richlistbox.children[1]);
|
||||
}, "the node is there.");
|
||||
|
||||
let listitem = gURLBar.popup.richlistbox.children[1];
|
||||
EventUtils.synthesizeMouse(listitem, 10, 10, {type: "mousedown"}, window);
|
||||
is(gURLBar.popup.richlistbox.selectedIndex, 1, "The second item is selected");
|
||||
await promiseSpeculativeConnection(gHttpServer);
|
||||
is(gHttpServer.connectionNumber, 1, `${gHttpServer.connectionNumber} speculative connection has been setup.`);
|
||||
});
|
Loading…
Reference in New Issue
Block a user