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:
Marco Bonardo 2019-01-28 13:45:48 +00:00
parent 007d7ab14c
commit a50d5dfc41
19 changed files with 710 additions and 409 deletions

View File

@ -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>

View File

@ -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.

View File

@ -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

View File

@ -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);
}
}
/**

View File

@ -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};
}

View File

@ -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;
}

View File

@ -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")) {

View File

@ -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 {
/**

View File

@ -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.

View File

@ -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");
}
});
}
}

View File

@ -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

View File

@ -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);
});
});

View File

@ -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.

View File

@ -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);
}

View File

@ -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]

View File

@ -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);

View File

@ -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");
});

View File

@ -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.`);
});

View File

@ -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.`);
});