Bug 1355451 - Speculative connect to websites on mousedown on awesomebar item. r=mak
We preconnect to an explicit URL or the url in moz-action:remotetab on
the popup.
MozReview-Commit-ID: 3KqlO2Bio3V
--- a/browser/base/content/test/urlbar/browser.ini
+++ b/browser/base/content/test/urlbar/browser.ini
@@ -110,16 +110,17 @@ support-files =
support-files =
file_urlbar_edit_dos.html
[browser_urlbar_searchsettings.js]
[browser_urlbar_search_speculative_connect.js]
[browser_urlbar_search_speculative_connect_engine.js]
support-files =
searchSuggestionEngine2.xml
searchSuggestionEngine.sjs
+[browser_urlbar_search_speculative_connect_mousedown.js]
[browser_urlbar_stop_pending.js]
support-files =
slow-page.sjs
[browser_urlbar_remoteness_switch.js]
run-if = e10s
[browser_urlHighlight.js]
[browser_wyciwyg_urlbarCopying.js]
subsuite = clipboard
--- a/browser/base/content/test/urlbar/browser_urlbar_search_speculative_connect.js
+++ b/browser/base/content/test/urlbar/browser_urlbar_search_speculative_connect.js
@@ -2,40 +2,31 @@
* 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 {HttpServer} = Cu.import("resource://testing-common/httpd.js", {});
let gHttpServer = null;
let gScheme = "http";
let gHost = "localhost"; // 'localhost' by default.
let gPort = -1;
let gPrivateWin = null;
let gIsSpeculativeConnected = false;
add_task(async function setup() {
- if (!gHttpServer) {
- gHttpServer = new HttpServer();
- try {
- gHttpServer.start(gPort);
- gPort = gHttpServer.identity.primaryPort;
- gHttpServer.identity.setPrimary(gScheme, gHost, gPort);
- } catch (ex) {
- info("We can't launch our http server successfully.")
- }
- }
- is(gHttpServer.identity.has(gScheme, gHost, gPort), true, "make sure we have this domain listed");
+ gHttpServer = runHttpServer(gScheme, gHost);
+ // The server will be run on a random port if the port number wasn't given.
+ gPort = gHttpServer.identity.primaryPort;
await SpecialPowers.pushPrefEnv({
set: [["browser.urlbar.autoFill", true],
- // Turn off speculative connect to the search engine.
+ // 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]],
});
@@ -45,17 +36,17 @@ add_task(async function setup() {
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 to add it manually.
+ // this function and add it manually.
let oldSpeculativeConnect = gURLBar.popup.maybeSetupSpeculativeConnect.bind(gURLBar.popup);
let newSpeculativeConnect = (uriString) => {
gIsSpeculativeConnected = true;
info(`Original uri is ${uriString}`);
let newUriString = uriString.substr(0, uriString.length - 1) +
":" + gPort + "/";
info(`New uri is ${newUriString}`);
oldSpeculativeConnect(newUriString);
@@ -83,16 +74,17 @@ const test = {
add_task(async function autofill_tests() {
gIsSpeculativeConnected = false;
info(`Searching for '${test.search}'`);
await promiseAutocompleteResultPopup(test.search, window, true);
is(gURLBar.inputField.value, test.autofilledValue,
`Autofilled value is as expected for search '${test.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 '${test.search}'`);
await promiseAutocompleteResultPopup(test.search, gPrivateWin, true);
is(gPrivateWin.gURLBar.inputField.value, test.autofilledValue,
--- a/browser/base/content/test/urlbar/browser_urlbar_search_speculative_connect_engine.js
+++ b/browser/base/content/test/urlbar/browser_urlbar_search_speculative_connect_engine.js
@@ -2,39 +2,28 @@
* 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 {HttpServer} = Cu.import("resource://testing-common/httpd.js", {});
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() {
- if (!gHttpServer) {
- gHttpServer = new HttpServer();
- try {
- gHttpServer.start(gPort);
- gPort = gHttpServer.identity.primaryPort;
- gHttpServer.identity.setPrimary(gScheme, gHost, gPort);
- } catch (ex) {
- info("We can't launch our http server successfully.")
- }
- }
- is(gHttpServer.identity.has(gScheme, gHost, gPort), true, "make sure we have this domain listed");
+ gHttpServer = runHttpServer(gScheme, gHost, gPort);
await SpecialPowers.pushPrefEnv({
set: [["browser.urlbar.autoFill", true],
- // Turn off speculative connect to the search engine.
+ // 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]],
@@ -49,18 +38,19 @@ add_task(async function setup() {
Services.search.currentEngine = oldCurrentEngine;
gHttpServer.identity.remove(gScheme, gHost, gPort);
gHttpServer.stop(() => {
gHttpServer = null;
});
});
});
-add_task(async function autofill_tests() {
+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.`);
});
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbar_search_speculative_connect_mousedown.js
@@ -0,0 +1,73 @@
+/* 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.childNodes[1] &&
+ is_visible(gURLBar.popup.richlistbox.childNodes[1]);
+ }, "the node is there.");
+
+ let listitem = gURLBar.popup.richlistbox.childNodes[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.`);
+});
--- a/browser/base/content/test/urlbar/head.js
+++ b/browser/base/content/test/urlbar/head.js
@@ -3,16 +3,18 @@
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
"resource://testing-common/PlacesTestUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
"resource://gre/modules/Preferences.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "HttpServer",
+ "resource://testing-common/httpd.js");
/**
* Waits for the next top-level document load in the current browser. The URI
* of the document is compared against aExpectedURL. The load is then stopped
* before it actually starts.
*
* @param aExpectedURL
* The URL of the document that is expected to load.
@@ -124,16 +126,29 @@ function is_element_visible(element, msg
ok(is_visible(element), msg || "Element should be visible");
}
function is_element_hidden(element, msg) {
isnot(element, null, "Element should not be null, when checking visibility");
ok(is_hidden(element), msg || "Element should be hidden");
}
+function runHttpServer(scheme, host, port = -1) {
+ let httpserver = new HttpServer();
+ 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.")
+ }
+ is(httpserver.identity.has(scheme, host, port), true, `${scheme}://${host}:${port} is listening.`);
+ return httpserver;
+}
+
function promisePopupEvent(popup, eventSuffix) {
let endState = {shown: "open", hidden: "closed"}[eventSuffix];
if (popup.state == endState)
return Promise.resolve();
let eventType = "popup" + eventSuffix;
return new Promise(resolve => {
@@ -147,44 +162,52 @@ function promisePopupEvent(popup, eventS
function promisePopupShown(popup) {
return promisePopupEvent(popup, "shown");
}
function promisePopupHidden(popup) {
return promisePopupEvent(popup, "hidden");
}
-function promiseSearchComplete(win = window) {
+function promiseSearchComplete(win = window, dontAnimate = false) {
return promisePopupShown(win.gURLBar.popup).then(() => {
function searchIsComplete() {
- return win.gURLBar.controller.searchStatus >=
- Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH;
+ let isComplete = win.gURLBar.controller.searchStatus >=
+ Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH;
+ if (isComplete) {
+ info(`Restore popup dontAnimate value to ${dontAnimate}`);
+ win.gURLBar.popup.setAttribute("dontanimate", dontAnimate);
+ }
+ return isComplete;
}
// Wait until there are at least two matches.
return BrowserTestUtils.waitForCondition(searchIsComplete, "waiting urlbar search to complete");
});
}
function promiseAutocompleteResultPopup(inputText,
win = window,
fireInputEvent = false) {
+ let dontAnimate = !!win.gURLBar.popup.getAttribute("dontanimate");
waitForFocus(() => {
+ info(`Disable popup animation. Change dontAnimate value from ${dontAnimate} to true.`);
+ 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 = document.createEvent("Events");
event.initEvent("input", true, true);
win.gURLBar.dispatchEvent(event);
}
win.gURLBar.controller.startSearch(inputText);
}, win);
- return promiseSearchComplete(win);
+ return promiseSearchComplete(win, dontAnimate);
}
function promiseNewSearchEngine(basename) {
return new Promise((resolve, reject) => {
info("Waiting for engine to be added: " + basename);
let url = getRootDirectory(gTestPath) + basename;
Services.search.addEngine(url, null, "", false, {
onSuccess(engine) {
@@ -250,15 +273,13 @@ function promisePageActionViewShown() {
}, 5000);
}, { once: true });
});
}
function promiseSpeculativeConnection(httpserver) {
return BrowserTestUtils.waitForCondition(() => {
if (httpserver) {
- is(httpserver.connectionNumber, 1,
- `${httpserver.connectionNumber} speculative connection has been setup.`)
return httpserver.connectionNumber == 1;
}
return false;
}, "Waiting for connection setup");
}
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -2310,16 +2310,62 @@ file, You can obtain one at http://mozil
<handler event="mousedown"><![CDATA[
// Required to make the xul:label.text-link elements in the search
// suggestions notification work correctly when clicked on Linux.
// This is copied from the mousedown handler in
// browser-search-autocomplete-result-popup, which apparently had a
// similar problem.
event.preventDefault();
+
+ if (!this.input.speculativeConnectEnabled) {
+ return;
+ }
+ if (event.button == 2) {
+ // Ignore right-clicks.
+ return;
+ }
+ // Ensure the user is clicking on an url instead of other buttons
+ // on the popup.
+ let elt = event.originalTarget;
+ while (elt && elt.localName != "richlistitem" && elt != this) {
+ elt = elt.parentNode;
+ }
+ if (!elt || elt.localName != "richlistitem") {
+ return;
+ }
+ // The user might click on a ghost entry which was removed because of
+ // the coming new results.
+ if (this.input.controller.matchCount <= this.selectedIndex) {
+ 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.
+ if (url.startsWith("http") && this.selectedIndex > 0) {
+ this.maybeSetupSpeculativeConnect(url);
+ } 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));
+ }
+ }
+
]]></handler>
</handlers>
</binding>
<binding id="addon-progress-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
<implementation>
<constructor><![CDATA[