Bug 1493225, part 3 - Cancel content JS when navigating through history to prevent hangs r=smaug

This patch adds an ID to ensure that we avoid canceling content JS if the next
page already started loading by the time we're ready to try canceling the JS.

Differential Revision: https://phabricator.services.mozilla.com/D25164

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Jim Porter 2019-04-30 23:31:46 +00:00
parent f9d5e48623
commit a480304779
9 changed files with 75 additions and 26 deletions

View File

@ -667,6 +667,16 @@ nsDocShell::GetInterface(const nsIID& aIID, void** aSink) {
return *aSink ? NS_OK : NS_NOINTERFACE;
}
NS_IMETHODIMP
nsDocShell::SetCancelContentJSEpoch(int32_t aEpoch) {
// Note: this gets called fairly early (before a pageload actually starts).
// We could probably defer this even longer.
nsCOMPtr<nsIBrowserChild> browserChild = GetBrowserChild();
static_cast<BrowserChild*>(browserChild.get())
->SetCancelContentJSEpoch(aEpoch);
return NS_OK;
}
NS_IMETHODIMP
nsDocShell::LoadURI(nsDocShellLoadState* aLoadState) {
MOZ_ASSERT(aLoadState, "Must have a valid load state!");

View File

@ -75,6 +75,8 @@ webidl Document;
[scriptable, builtinclass, uuid(049234fe-da10-478b-bc5d-bc6f9a1ba63d)]
interface nsIDocShell : nsIDocShellTreeItem
{
void setCancelContentJSEpoch(in long aEpoch);
/**
* Loads a given URI. This will give priority to loading the requested URI
* in the object implementing this interface. If it can't be loaded here

View File

@ -407,7 +407,8 @@ BrowserChild::BrowserChild(ContentChild* aManager, const TabId& aTabId,
mPendingDocShellIsActive(false), mPendingDocShellReceivedMessage(false),
mPendingRenderLayers(false),
mPendingRenderLayersReceivedMessage(false), mPendingLayersObserverEpoch{0},
mPendingDocShellBlockers(0), mWidgetNativeData(0) {
mPendingDocShellBlockers(0), mCancelContentJSEpoch(0),
mWidgetNativeData(0) {
mozilla::HoldJSObjects(this);
nsWeakPtr weakPtrThis(do_GetWeakReference(
@ -3304,10 +3305,17 @@ void BrowserChild::PaintWhileInterruptingJS(
nsresult BrowserChild::CanCancelContentJS(
nsIRemoteTab::NavigationType aNavigationType, int32_t aNavigationIndex,
nsIURI* aNavigationURI, bool* aCanCancel) {
nsIURI* aNavigationURI, int32_t aEpoch, bool* aCanCancel) {
nsresult rv;
*aCanCancel = false;
if (aEpoch <= mCancelContentJSEpoch) {
// The next page loaded before we got here, so we shouldn't try to cancel
// the content JS.
TABC_LOG("Unable to cancel content JS; the next page is already loaded!\n");
return NS_OK;
}
nsCOMPtr<nsISHistory> history = do_GetInterface(WebNavigation());
if (!history) {
return NS_ERROR_FAILURE;

View File

@ -614,7 +614,7 @@ class BrowserChild final : public BrowserChildBase,
nsresult CanCancelContentJS(nsIRemoteTab::NavigationType aNavigationType,
int32_t aNavigationIndex, nsIURI* aNavigationURI,
bool* aCanCancel);
int32_t aEpoch, bool* aCanCancel);
layers::LayersObserverEpoch LayersObserverEpoch() const {
return mLayersObserverEpoch;
@ -664,6 +664,10 @@ class BrowserChild final : public BrowserChildBase,
const ScrollableLayerGuid& aGuid,
const uint64_t& aInputBlockId);
void SetCancelContentJSEpoch(int32_t aEpoch) {
mCancelContentJSEpoch = aEpoch;
}
static bool HasVisibleTabs() {
return sVisibleTabs && !sVisibleTabs->IsEmpty();
}
@ -920,6 +924,7 @@ class BrowserChild final : public BrowserChildBase,
// When mPendingDocShellBlockers is greater than 0, the DocShell is blocked,
// and once it reaches 0, it is no longer blocked.
uint32_t mPendingDocShellBlockers;
int32_t mCancelContentJSEpoch;
WindowsHandle mWidgetNativeData;

View File

@ -50,7 +50,7 @@ child:
async CancelContentJSExecutionIfRunning(
TabId tabId, NavigationType aNavigationType,
int32_t aNavigationIndex, nsCString? aNavigationURI);
int32_t aNavigationIndex, nsCString? aNavigationURI, int32_t aEpoch);
};
} // namespace mozilla

View File

@ -118,7 +118,8 @@ class HangMonitorChild : public PProcessHangMonitorChild,
mozilla::ipc::IPCResult RecvCancelContentJSExecutionIfRunning(
const TabId& aTabId, const nsIRemoteTab::NavigationType& aNavigationType,
const int32_t& aNavigationIndex,
const mozilla::Maybe<nsCString>& aNavigationURI) override;
const mozilla::Maybe<nsCString>& aNavigationURI,
const int32_t& aEpoch) override;
void ActorDestroy(ActorDestroyReason aWhy) override;
@ -163,6 +164,7 @@ class HangMonitorChild : public PProcessHangMonitorChild,
nsIRemoteTab::NavigationType mCancelContentJSNavigationType;
int32_t mCancelContentJSNavigationIndex;
mozilla::Maybe<nsCString> mCancelContentJSNavigationURI;
int32_t mCancelContentJSEpoch;
JSContext* mContext;
bool mShutdownDone;
@ -281,7 +283,7 @@ class HangMonitorParent : public PProcessHangMonitorParent,
const LayersObserverEpoch& aEpoch);
void CancelContentJSExecutionIfRunningOnThread(
TabId aTabId, nsIRemoteTab::NavigationType aNavigationType,
int32_t aNavigationIndex, nsIURI* aNavigationURI);
int32_t aNavigationIndex, nsIURI* aNavigationURI, int32_t aEpoch);
void ShutdownOnThread();
@ -328,6 +330,7 @@ HangMonitorChild::HangMonitorChild(ProcessHangMonitor* aMonitor)
mCancelContentJS(false),
mCancelContentJSNavigationType(nsIRemoteTab::NAVIGATE_BACK),
mCancelContentJSNavigationIndex(0),
mCancelContentJSEpoch(0),
mShutdownDone(false),
mIPCOpen(true),
mPaintWhileInterruptingJSActive(false) {
@ -356,6 +359,7 @@ void HangMonitorChild::InterruptCallback() {
nsIRemoteTab::NavigationType cancelContentJSNavigationType;
int32_t cancelContentJSNavigationIndex;
mozilla::Maybe<nsCString> cancelContentJSNavigationURI;
int32_t cancelContentJSEpoch;
{
MonitorAutoLock lock(mMonitor);
@ -369,6 +373,7 @@ void HangMonitorChild::InterruptCallback() {
cancelContentJSNavigationType = mCancelContentJSNavigationType;
cancelContentJSNavigationIndex = mCancelContentJSNavigationIndex;
cancelContentJSNavigationURI = std::move(mCancelContentJSNavigationURI);
cancelContentJSEpoch = mCancelContentJSEpoch;
mPaintWhileInterruptingJS = false;
mCancelContentJS = false;
@ -405,7 +410,7 @@ void HangMonitorChild::InterruptCallback() {
bool canCancel;
rv = browserChild->CanCancelContentJS(cancelContentJSNavigationType,
cancelContentJSNavigationIndex, uri,
&canCancel);
cancelContentJSEpoch, &canCancel);
if (NS_SUCCEEDED(rv) && canCancel) {
// Tell xpconnect that we want to cancel the content JS in this tab
// during the next interrupt callback.
@ -516,7 +521,7 @@ void HangMonitorChild::ClearPaintWhileInterruptingJS(
mozilla::ipc::IPCResult HangMonitorChild::RecvCancelContentJSExecutionIfRunning(
const TabId& aTabId, const nsIRemoteTab::NavigationType& aNavigationType,
const int32_t& aNavigationIndex,
const mozilla::Maybe<nsCString>& aNavigationURI) {
const mozilla::Maybe<nsCString>& aNavigationURI, const int32_t& aEpoch) {
MOZ_RELEASE_ASSERT(IsOnThread());
{
@ -526,6 +531,7 @@ mozilla::ipc::IPCResult HangMonitorChild::RecvCancelContentJSExecutionIfRunning(
mCancelContentJSNavigationType = aNavigationType;
mCancelContentJSNavigationIndex = aNavigationIndex;
mCancelContentJSNavigationURI = aNavigationURI;
mCancelContentJSEpoch = aEpoch;
}
JS_RequestInterruptCallback(mContext);
@ -749,16 +755,16 @@ void HangMonitorParent::CancelContentJSExecutionIfRunning(
TabId id = aBrowserParent->GetTabId();
Dispatch(NewNonOwningRunnableMethod<TabId, nsIRemoteTab::NavigationType,
int32_t, nsIURI*>(
int32_t, nsIURI*, int32_t>(
"HangMonitorParent::CancelContentJSExecutionIfRunningOnThread", this,
&HangMonitorParent::CancelContentJSExecutionIfRunningOnThread, id,
aNavigationType, aCancelContentJSOptions.mIndex,
aCancelContentJSOptions.mUri));
aCancelContentJSOptions.mUri, aCancelContentJSOptions.mEpoch));
}
void HangMonitorParent::CancelContentJSExecutionIfRunningOnThread(
TabId aTabId, nsIRemoteTab::NavigationType aNavigationType,
int32_t aNavigationIndex, nsIURI* aNavigationURI) {
int32_t aNavigationIndex, nsIURI* aNavigationURI, int32_t aEpoch) {
MOZ_RELEASE_ASSERT(IsOnThread());
mozilla::Maybe<nsCString> spec;
@ -771,8 +777,8 @@ void HangMonitorParent::CancelContentJSExecutionIfRunningOnThread(
}
if (mIPCOpen) {
Unused << SendCancelContentJSExecutionIfRunning(aTabId, aNavigationType,
aNavigationIndex, spec);
Unused << SendCancelContentJSExecutionIfRunning(
aTabId, aNavigationType, aNavigationIndex, spec, aEpoch);
}
}

View File

@ -5,4 +5,5 @@
dictionary CancelContentJSOptions {
long index = 0;
URI? uri = null;
long epoch = 0;
};

View File

@ -27,13 +27,13 @@ class WebNavigationChild extends ActorChild {
receiveMessage(message) {
switch (message.name) {
case "WebNavigation:GoBack":
this.goBack();
this.goBack(message.data);
break;
case "WebNavigation:GoForward":
this.goForward();
this.goForward(message.data);
break;
case "WebNavigation:GotoIndex":
this.gotoIndex(message.data.index);
this.gotoIndex(message.data);
break;
case "WebNavigation:LoadURI":
let histogram = Services.telemetry.getKeyedHistogramById("FX_TAB_REMOTE_NAVIGATION_DELAY_MS");
@ -65,19 +65,26 @@ class WebNavigationChild extends ActorChild {
}
}
goBack() {
goBack(params) {
if (this.webNavigation.canGoBack) {
this.mm.docShell.setCancelContentJSEpoch(params.cancelContentJSEpoch);
this._wrapURIChangeCall(() => this.webNavigation.goBack());
}
}
goForward() {
goForward(params) {
if (this.webNavigation.canGoForward) {
this.mm.docShell.setCancelContentJSEpoch(params.cancelContentJSEpoch);
this._wrapURIChangeCall(() => this.webNavigation.goForward());
}
}
gotoIndex(index) {
gotoIndex(params) {
let {
index,
cancelContentJSEpoch,
} = params || {};
this.mm.docShell.setCancelContentJSEpoch(cancelContentJSEpoch);
this._wrapURIChangeCall(() => this.webNavigation.gotoIndex(index));
}
@ -91,6 +98,7 @@ class WebNavigationChild extends ActorChild {
baseURI,
triggeringPrincipal,
csp,
cancelContentJSEpoch,
} = params || {};
if (AppConstants.MOZ_CRASHREPORTER && CrashReporter.enabled) {
@ -131,6 +139,7 @@ class WebNavigationChild extends ActorChild {
headers,
baseURI,
};
this.mm.docShell.setCancelContentJSEpoch(cancelContentJSEpoch);
this._wrapURIChangeCall(() => {
return this.webNavigation.loadURI(uri, loadURIOptions);
});

View File

@ -18,6 +18,7 @@ function makeURI(url) {
function RemoteWebNavigation() {
this.wrappedJSObject = this;
this._cancelContentJSEpoch = 1;
}
RemoteWebNavigation.prototype = {
@ -55,19 +56,24 @@ RemoteWebNavigation.prototype = {
canGoBack: false,
canGoForward: false,
goBack() {
let cancelContentJSEpoch = this._cancelContentJSEpoch++;
this._browser.frameLoader.remoteTab.maybeCancelContentJSExecution(
Ci.nsIRemoteTab.NAVIGATE_BACK);
this._sendMessage("WebNavigation:GoBack", {});
Ci.nsIRemoteTab.NAVIGATE_BACK, {epoch: cancelContentJSEpoch});
this._sendMessage("WebNavigation:GoBack", {cancelContentJSEpoch});
},
goForward() {
let cancelContentJSEpoch = this._cancelContentJSEpoch++;
this._browser.frameLoader.remoteTab.maybeCancelContentJSExecution(
Ci.nsIRemoteTab.NAVIGATE_FORWARD);
this._sendMessage("WebNavigation:GoForward", {});
Ci.nsIRemoteTab.NAVIGATE_FORWARD, {epoch: cancelContentJSEpoch});
this._sendMessage("WebNavigation:GoForward", {cancelContentJSEpoch});
},
gotoIndex(aIndex) {
let cancelContentJSEpoch = this._cancelContentJSEpoch++;
this._browser.frameLoader.remoteTab.maybeCancelContentJSExecution(
Ci.nsIRemoteTab.NAVIGATE_INDEX, {index: aIndex});
this._sendMessage("WebNavigation:GotoIndex", {index: aIndex});
Ci.nsIRemoteTab.NAVIGATE_INDEX,
{index: aIndex, epoch: cancelContentJSEpoch});
this._sendMessage("WebNavigation:GotoIndex", {index: aIndex,
cancelContentJSEpoch});
},
loadURI(aURI, aLoadURIOptions) {
let uri;
@ -97,8 +103,9 @@ RemoteWebNavigation.prototype = {
}
}
let cancelContentJSEpoch = this._cancelContentJSEpoch++;
this._browser.frameLoader.remoteTab.maybeCancelContentJSExecution(
Ci.nsIRemoteTab.NAVIGATE_URL, {uri});
Ci.nsIRemoteTab.NAVIGATE_URL, {uri, epoch: cancelContentJSEpoch});
this._sendMessage("WebNavigation:LoadURI", {
uri: aURI,
flags: aLoadURIOptions.loadFlags,
@ -110,6 +117,7 @@ RemoteWebNavigation.prototype = {
aLoadURIOptions.triggeringPrincipal || Services.scriptSecurityManager.createNullPrincipal({})),
csp: aLoadURIOptions.csp ? E10SUtils.serializeCSP(aLoadURIOptions.csp) : null,
requestTime: Services.telemetry.msSystemNow(),
cancelContentJSEpoch,
});
},
setOriginAttributesBeforeLoading(aOriginAttributes) {