Bug 798249 - track when we're 'inside' a loadURI call when dealing with Stop() calls resulting from the same, r?mconley
MozReview-Commit-ID: 48xQn03HtjZ
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -825,20 +825,26 @@ function _loadURIWithFlags(browser, uri,
Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT);
let charset = params.charset;
let postData = params.postData;
if (!(flags & browser.webNavigation.LOAD_FLAGS_FROM_EXTERNAL)) {
browser.userTypedClear++;
}
+ let wasRemote = browser.isRemoteBrowser;
+
let process = browser.isRemoteBrowser ? Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT
: Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
let mustChangeProcess = gMultiProcessBrowser &&
!E10SUtils.canLoadURIInProcess(uri, process);
+ if ((!wasRemote && !mustChangeProcess) ||
+ (wasRemote && mustChangeProcess)) {
+ browser.inLoadURI = true;
+ }
try {
if (!mustChangeProcess) {
browser.webNavigation.loadURIWithOptions(uri, flags,
referrer, referrerPolicy,
postData, null, null);
} else {
if (postData) {
postData = NetUtil.readInputStreamToString(postData, postData.available());
@@ -862,16 +868,20 @@ function _loadURIWithFlags(browser, uri,
Cu.reportError(e);
gBrowser.updateBrowserRemotenessByURL(browser, uri);
browser.webNavigation.loadURIWithOptions(uri, flags, referrer, referrerPolicy,
postData, null, null);
} else {
throw e;
}
} finally {
+ if ((!wasRemote && !mustChangeProcess) ||
+ (wasRemote && mustChangeProcess)) {
+ browser.inLoadURI = false;
+ }
if (browser.userTypedClear) {
browser.userTypedClear--;
}
}
}
// Starts a new load in the browser first switching the browser to the correct
// process
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -679,18 +679,20 @@
if (!Components.isSuccessCode(aStatus) &&
!isTabEmpty(this.mTab)) {
// Restore the current document's location in case the
// request was stopped (possibly from a content script)
// before the location changed.
this.mBrowser.userTypedValue = null;
- if (this.mTab.selected && gURLBar)
+ let inLoadURI = this.mBrowser.inLoadURI;
+ if (this.mTab.selected && gURLBar && !inLoadURI) {
URLBarSetURI();
+ }
} else {
// The document is done loading, we no longer want the
// value cleared.
if (this.mBrowser.userTypedClear > 1)
this.mBrowser.userTypedClear -= 2;
else if (this.mBrowser.userTypedClear > 0)
this.mBrowser.userTypedClear--;
@@ -1315,17 +1317,17 @@
A newly created tab has position Infinity in the cache.
If a tab is closed, it has no effect on the position of other
tabs in the cache since we assume that closing a tab doesn't
cause us to load in any other tabs.
We ignore the effect of dragging tabs between windows.
-->
<method name="_recordTabAccess">
- <parameter name="aTab"/>
+ <parameter name="aTab"/>
<body><![CDATA[
if (!Services.telemetry.canRecordExtended) {
return;
}
let tabs = Array.from(this.visibleTabs);
let pos = aTab.cachePosition;
--- a/browser/base/content/test/general/browser_urlbarStop.js
+++ b/browser/base/content/test/general/browser_urlbarStop.js
@@ -20,12 +20,12 @@ add_task(function* () {
gBrowser.removeCurrentTab();
});
function typeAndSubmitAndStop(url) {
gBrowser.userTypedValue = url;
URLBarSetURI();
is(gURLBar.textValue, gURLBar.trimValue(url), "location bar reflects loading page");
- let promise = waitForDocLoadAndStopIt(url);
+ let promise = waitForDocLoadAndStopIt(url, gBrowser.selectedBrowser, false);
gURLBar.handleCommand();
return promise;
}
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -376,40 +376,55 @@ function promiseHistoryClearedState(aURI
/**
* 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.
+ * @param aStopFromProgressListener
+ * Whether to cancel the load directly from the progress listener. Defaults to true.
+ * If you're using this method to avoid hitting the network, you want the default (true).
+ * However, the browser UI will behave differently for loads stopped directly from
+ * the progress listener (effectively in the middle of a call to loadURI) and so there
+ * are cases where you may want to avoid stopping the load directly from within the
+ * progress listener callback.
* @return promise
*/
-function waitForDocLoadAndStopIt(aExpectedURL, aBrowser=gBrowser.selectedBrowser) {
- function content_script() {
+function waitForDocLoadAndStopIt(aExpectedURL, aBrowser=gBrowser.selectedBrowser, aStopFromProgressListener=true) {
+ function content_script(aStopFromProgressListener) {
let { interfaces: Ci, utils: Cu } = Components;
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
let wp = docShell.QueryInterface(Ci.nsIWebProgress);
+ function stopContent(now, uri) {
+ if (now) {
+ /* Hammer time. */
+ content.stop();
+
+ /* Let the parent know we're done. */
+ sendAsyncMessage("Test:WaitForDocLoadAndStopIt", { uri });
+ } else {
+ setTimeout(stopContent.bind(null, true, uri), 0);
+ }
+ }
+
let progressListener = {
onStateChange: function (webProgress, req, flags, status) {
dump("waitForDocLoadAndStopIt: onStateChange " + flags.toString(16) + ": " + req.name + "\n");
if (webProgress.isTopLevel &&
flags & Ci.nsIWebProgressListener.STATE_START) {
wp.removeProgressListener(progressListener);
let chan = req.QueryInterface(Ci.nsIChannel);
dump(`waitForDocLoadAndStopIt: Document start: ${chan.URI.spec}\n`);
- /* Hammer time. */
- content.stop();
-
- /* Let the parent know we're done. */
- sendAsyncMessage("Test:WaitForDocLoadAndStopIt", { uri: chan.originalURI.spec });
+ stopContent(aStopFromProgressListener, chan.originalURI.spec);
}
},
QueryInterface: XPCOMUtils.generateQI(["nsISupportsWeakReference"])
};
wp.addProgressListener(progressListener, wp.NOTIFY_STATE_WINDOW);
/**
* As |this| is undefined and we can't extend |docShell|, adding an unload
@@ -426,17 +441,17 @@ function waitForDocLoadAndStopIt(aExpect
return new Promise((resolve, reject) => {
function complete({ data }) {
is(data.uri, aExpectedURL, "waitForDocLoadAndStopIt: The expected URL was loaded");
mm.removeMessageListener("Test:WaitForDocLoadAndStopIt", complete);
resolve();
}
let mm = aBrowser.messageManager;
- mm.loadFrameScript("data:,(" + content_script.toString() + ")();", true);
+ mm.loadFrameScript("data:,(" + content_script.toString() + ")(" + aStopFromProgressListener + ");", true);
mm.addMessageListener("Test:WaitForDocLoadAndStopIt", complete);
info("waitForDocLoadAndStopIt: Waiting for URL: " + aExpectedURL);
});
}
/**
* Waits for the next load to complete in any browser or the given browser.
* If a <tabbrowser> is given it waits for a load in any of its browsers.
--- a/browser/base/content/test/urlbar/browser.ini
+++ b/browser/base/content/test/urlbar/browser.ini
@@ -1,4 +1,7 @@
[browser_moz_action_link.js]
[browser_urlbar_blanking.js]
support-files =
file_blank_but_not_blank.html
+[browser_urlbar_stop_pending.js]
+support-files =
+ slow-page.sjs
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbar_stop_pending.js
@@ -0,0 +1,42 @@
+"use strict";
+
+const SLOW_PAGE = "http://www.example.com/browser/browser/base/content/test/urlbar/slow-page.sjs";
+const SLOW_PAGE2 = "http://mochi.test:8888/browser/browser/base/content/test/urlbar/slow-page.sjs?faster";
+
+/**
+ * Check that if we:
+ * 1) have a loaded page
+ * 2) load a separate URL
+ * 3) before the URL for step 2 has finished loading, load a third URL
+ * we don't revert to the URL from (1).
+ */
+add_task(function*() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home", true);
+
+ let expectedURLBarChange = SLOW_PAGE;
+ let sawChange = false;
+ let handler = e => {
+ sawChange = true;
+ is(gURLBar.value, expectedURLBarChange, "Should not change URL bar value!");
+ };
+
+ let obs = new MutationObserver(handler);
+
+ obs.observe(gURLBar, {attributes: true});
+ gURLBar.value = SLOW_PAGE;
+ gURLBar.handleCommand();
+
+ // If this ever starts going intermittent, we've broken this.
+ yield new Promise(resolve => setTimeout(resolve, 200));
+ expectedURLBarChange = SLOW_PAGE2;
+ let pageLoadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ gURLBar.value = expectedURLBarChange;
+ gURLBar.handleCommand();
+ is(gURLBar.value, expectedURLBarChange, "Should not have changed URL bar value synchronously.");
+ yield pageLoadPromise;
+ ok(sawChange, "The URL bar change handler should have been called by the time the page was loaded");
+ obs.disconnect();
+ obs = null;
+ yield BrowserTestUtils.removeTab(tab);
+});
+
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/urlbar/slow-page.sjs
@@ -0,0 +1,22 @@
+"use strict";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+let timer;
+
+const DELAY_MS = 5000;
+function handleRequest(request, response) {
+ if (request.queryString.endsWith("faster")) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.write("<body>Not so slow!</body>");
+ return;
+ }
+ response.processAsync();
+ timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.init(() => {
+ response.setHeader("Content-Type", "text/html", false);
+ response.write("<body>This was the slow load. You should never see this.</body>");
+ response.finish();
+ }, DELAY_MS, Ci.nsITimer.TYPE_ONE_SHOT);
+}
--- a/toolkit/content/browser-child.js
+++ b/toolkit/content/browser-child.js
@@ -127,16 +127,17 @@ var WebProgressListener = {
// will want to know some details, so we'll update it with
// the documentURI.
if (aWebProgress && aWebProgress.isTopLevel) {
json.documentURI = content.document.documentURIObject.spec;
json.charset = content.document.characterSet;
json.mayEnableCharacterEncodingMenu = docShell.mayEnableCharacterEncodingMenu;
}
+ json.inLoadURI = WebNavigation.inLoadURI;
this._send("Content:StateChange", json, objects);
},
onProgressChange: function onProgressChange(aWebProgress, aRequest, aCurSelf, aMaxSelf, aCurTotal, aMaxTotal) {
let json = this._setupJSON(aWebProgress, aRequest);
let objects = this._setupObjects(aWebProgress, aRequest);
json.curSelf = aCurSelf;
@@ -203,16 +204,20 @@ var WebProgressListener = {
this._send("Content:SecurityChange", json, objects);
},
onRefreshAttempted: function onRefreshAttempted(aWebProgress, aURI, aDelay, aSameURI) {
return true;
},
+ sendLoadCallResult(aResult) {
+ this._send("Content:LoadURIResult", aResult);
+ },
+
QueryInterface: function QueryInterface(aIID) {
if (aIID.equals(Ci.nsIWebProgressListener) ||
aIID.equals(Ci.nsIWebProgressListener2) ||
aIID.equals(Ci.nsISupportsWeakReference) ||
aIID.equals(Ci.nsISupports)) {
return this;
}
@@ -234,16 +239,22 @@ var WebNavigation = {
addMessageListener("WebNavigation:Reload", this);
addMessageListener("WebNavigation:Stop", this);
},
get webNavigation() {
return docShell.QueryInterface(Ci.nsIWebNavigation);
},
+ _inLoadURI: false,
+
+ get inLoadURI() {
+ return this._inLoadURI;
+ },
+
receiveMessage: function(message) {
switch (message.name) {
case "WebNavigation:GoBack":
this.goBack();
break;
case "WebNavigation:GoForward":
this.goForward();
break;
@@ -295,18 +306,34 @@ var WebNavigation = {
if (referrer)
referrer = Services.io.newURI(referrer, null, null);
if (postData)
postData = makeInputStream(postData);
if (headers)
headers = makeInputStream(headers);
if (baseURI)
baseURI = Services.io.newURI(baseURI, null, null);
- this.webNavigation.loadURIWithOptions(uri, flags, referrer, referrerPolicy,
- postData, headers, baseURI);
+ this._inLoadURI = true;
+ let exception = null;
+ try {
+ this.webNavigation.loadURIWithOptions(uri, flags, referrer, referrerPolicy,
+ postData, headers, baseURI);
+ } catch (ex) {
+ exception = ex;
+ } finally {
+ this._inLoadURI = false;
+ let loadCallResult = {
+ success: !exception,
+ uri,
+ flags,
+ exception,
+ baseURI
+ };
+ WebProgressListener.sendLoadCallResult(loadCallResult);
+ }
},
reload: function(flags) {
this.webNavigation.reload(flags);
},
stop: function(flags) {
this.webNavigation.stop(flags);
--- a/toolkit/modules/RemoteWebProgress.jsm
+++ b/toolkit/modules/RemoteWebProgress.jsm
@@ -112,25 +112,27 @@ RemoteWebProgressManager.argumentsForAdd
RemoteWebProgressManager.prototype = {
swapBrowser: function(aBrowser) {
if (this._messageManager) {
this._messageManager.removeMessageListener("Content:StateChange", this);
this._messageManager.removeMessageListener("Content:LocationChange", this);
this._messageManager.removeMessageListener("Content:SecurityChange", this);
this._messageManager.removeMessageListener("Content:StatusChange", this);
this._messageManager.removeMessageListener("Content:ProgressChange", this);
+ this._messageManager.removeMessageListener("Content:LoadURIResult", this);
}
this._browser = aBrowser;
this._messageManager = aBrowser.messageManager;
this._messageManager.addMessageListener("Content:StateChange", this);
this._messageManager.addMessageListener("Content:LocationChange", this);
this._messageManager.addMessageListener("Content:SecurityChange", this);
this._messageManager.addMessageListener("Content:StatusChange", this);
this._messageManager.addMessageListener("Content:ProgressChange", this);
+ this._messageManager.addMessageListener("Content:LoadURIResult", this);
},
get topLevelWebProgress() {
return this._topLevelWebProgress;
},
addProgressListener: function (aListener) {
let listener = aListener.QueryInterface(Ci.nsIWebProgressListener);
@@ -206,16 +208,17 @@ RemoteWebProgressManager.prototype = {
request = new RemoteWebProgressRequest(json.requestURI,
json.originalRequestURI,
objects.request);
}
if (isTopLevel) {
this._browser._contentWindow = objects.contentWindow;
this._browser._documentContentType = json.documentContentType;
+ this._browser.inLoadURI = json.inLoadURI;
if (json.charset) {
this._browser._characterSet = json.charset;
this._browser._mayEnableCharacterEncodingMenu = json.mayEnableCharacterEncodingMenu;
}
}
switch (aMessage.name) {
case "Content:StateChange":
@@ -263,11 +266,14 @@ RemoteWebProgressManager.prototype = {
case "Content:StatusChange":
this._callProgressListeners("onStatusChange", webProgress, request, json.status, json.message);
break;
case "Content:ProgressChange":
this._callProgressListeners("onProgressChange", webProgress, request, json.curSelf, json.maxSelf, json.curTotal, json.maxTotal);
break;
+ case "Content:LoadURIResult":
+ this._browser.inLoadURI = false;
+ break;
}
},
};