зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1493225, part 1 - Cancel content JS when navigating through history to prevent hangs r=smaug
This patch passes a message through the HangMonitor channel when navigating through history to cancel content JS that could hang the chrome JS in the content process responsible for history navigation. If the content JS is actually canceled, this also disables the BF cache for the current page, since it could end up in an inconsistent state due to the JS cancellation. Differential Revision: https://phabricator.services.mozilla.com/D23089 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
539ae1ed8f
Коммит
04d397e55f
|
@ -394,6 +394,8 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget,
|
|||
already_AddRefed<mozilla::dom::BrowsingContext> GetChildWindow(
|
||||
const nsAString& aName);
|
||||
|
||||
inline nsIBrowserChild* GetBrowserChild() { return mBrowserChild.get(); }
|
||||
|
||||
// These return true if we've reached the state in this top level window
|
||||
// where we ask the user if further dialogs should be blocked.
|
||||
//
|
||||
|
|
|
@ -121,4 +121,11 @@ interface nsIRemoteTab : nsISupports
|
|||
* see nsISecureBrowserUI.contentBlockingLogJSON.
|
||||
*/
|
||||
Promise getContentBlockingLog();
|
||||
|
||||
/**
|
||||
* Interrupt content scripts if possible/needed to allow chrome scripts in the
|
||||
* content process to run (in particular, to allow navigating through browser
|
||||
* history.
|
||||
*/
|
||||
void maybeCancelContentJSExecution();
|
||||
};
|
||||
|
|
|
@ -41,6 +41,8 @@
|
|||
#include "mozilla/net/NeckoChild.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/PresShell.h"
|
||||
#include "mozilla/ProcessHangMonitor.h"
|
||||
#include "mozilla/StaticPrefs.h"
|
||||
#include "mozilla/TextEvents.h"
|
||||
#include "mozilla/TouchEvents.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
@ -3118,6 +3120,14 @@ BrowserParent::GetContentBlockingLog(Promise** aPromise) {
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
BrowserParent::MaybeCancelContentJSExecution() {
|
||||
if (StaticPrefs::dom_ipc_cancel_content_js_when_navigating()) {
|
||||
Manager()->CancelContentJSExecutionIfRunning(this);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void BrowserParent::SuppressDisplayport(bool aEnabled) {
|
||||
if (IsDestroyed()) {
|
||||
return;
|
||||
|
|
|
@ -5295,6 +5295,15 @@ void ContentParent::PaintTabWhileInterruptingJS(
|
|||
mHangMonitorActor, aBrowserParent, aForceRepaint, aEpoch);
|
||||
}
|
||||
|
||||
void ContentParent::CancelContentJSExecutionIfRunning(
|
||||
BrowserParent* aBrowserParent) {
|
||||
if (!mHangMonitorActor) {
|
||||
return;
|
||||
}
|
||||
ProcessHangMonitor::CancelContentJSExecutionIfRunning(mHangMonitorActor,
|
||||
aBrowserParent);
|
||||
}
|
||||
|
||||
void ContentParent::UpdateCookieStatus(nsIChannel* aChannel) {
|
||||
PNeckoParent* neckoParent = LoneManagedOrNullAsserts(ManagedPNeckoParent());
|
||||
PCookieServiceParent* csParent =
|
||||
|
|
|
@ -591,6 +591,8 @@ class ContentParent final : public PContentParent,
|
|||
bool aForceRepaint,
|
||||
const layers::LayersObserverEpoch& aEpoch);
|
||||
|
||||
void CancelContentJSExecutionIfRunning(BrowserParent* aBrowserParent);
|
||||
|
||||
// This function is called when we are about to load a document from an
|
||||
// HTTP(S) or FTP channel for a content process. It is a useful place
|
||||
// to start to kick off work as early as possible in response to such
|
||||
|
|
|
@ -43,6 +43,8 @@ child:
|
|||
async EndStartingDebugger();
|
||||
|
||||
async PaintWhileInterruptingJS(TabId tabId, bool forceRepaint, LayersObserverEpoch aEpoch);
|
||||
|
||||
async CancelContentJSExecutionIfRunning(TabId tabId);
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "mozilla/ProcessHangMonitorIPC.h"
|
||||
|
||||
#include "jsapi.h"
|
||||
#include "xpcprivate.h"
|
||||
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/BackgroundHangMonitor.h"
|
||||
|
@ -112,6 +113,9 @@ class HangMonitorChild : public PProcessHangMonitorChild,
|
|||
const TabId& aTabId, const bool& aForceRepaint,
|
||||
const LayersObserverEpoch& aEpoch) override;
|
||||
|
||||
mozilla::ipc::IPCResult RecvCancelContentJSExecutionIfRunning(
|
||||
const TabId& aTabId) override;
|
||||
|
||||
void ActorDestroy(ActorDestroyReason aWhy) override;
|
||||
|
||||
void InterruptCallback();
|
||||
|
@ -234,6 +238,7 @@ class HangMonitorParent : public PProcessHangMonitorParent,
|
|||
void PaintWhileInterruptingJS(dom::BrowserParent* aBrowserParent,
|
||||
bool aForceRepaint,
|
||||
const LayersObserverEpoch& aEpoch);
|
||||
void CancelContentJSExecutionIfRunning(dom::BrowserParent* aBrowserParent);
|
||||
|
||||
void TerminateScript(bool aTerminateGlobal);
|
||||
void BeginStartingDebugger();
|
||||
|
@ -262,6 +267,7 @@ class HangMonitorParent : public PProcessHangMonitorParent,
|
|||
|
||||
void PaintWhileInterruptingJSOnThread(TabId aTabId, bool aForceRepaint,
|
||||
const LayersObserverEpoch& aEpoch);
|
||||
void CancelContentJSExecutionIfRunningOnThread(TabId aTabId);
|
||||
|
||||
void ShutdownOnThread();
|
||||
|
||||
|
@ -448,6 +454,18 @@ void HangMonitorChild::ClearPaintWhileInterruptingJS(
|
|||
mPaintWhileInterruptingJSActive = false;
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult HangMonitorChild::RecvCancelContentJSExecutionIfRunning(
|
||||
const TabId& aTabId) {
|
||||
MOZ_RELEASE_ASSERT(IsOnThread());
|
||||
|
||||
// Tell xpconnect that we want to cancel the content JS in this tab during the
|
||||
// next interrupt callback.
|
||||
XPCJSContext::SetTabIdToCancelContentJS(aTabId);
|
||||
JS_RequestInterruptCallback(mContext);
|
||||
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
void HangMonitorChild::Bind(Endpoint<PProcessHangMonitorChild>&& aEndpoint) {
|
||||
MOZ_RELEASE_ASSERT(IsOnThread());
|
||||
|
||||
|
@ -656,6 +674,25 @@ void HangMonitorParent::PaintWhileInterruptingJSOnThread(
|
|||
}
|
||||
}
|
||||
|
||||
void HangMonitorParent::CancelContentJSExecutionIfRunning(
|
||||
dom::BrowserParent* aBrowserParent) {
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
|
||||
TabId id = aBrowserParent->GetTabId();
|
||||
Dispatch(NewNonOwningRunnableMethod<TabId>(
|
||||
"HangMonitorParent::CancelContentJSExecutionIfRunningOnThread", this,
|
||||
&HangMonitorParent::CancelContentJSExecutionIfRunningOnThread, id));
|
||||
}
|
||||
|
||||
void HangMonitorParent::CancelContentJSExecutionIfRunningOnThread(
|
||||
TabId aTabId) {
|
||||
MOZ_RELEASE_ASSERT(IsOnThread());
|
||||
|
||||
if (mIPCOpen) {
|
||||
Unused << SendCancelContentJSExecutionIfRunning(aTabId);
|
||||
}
|
||||
}
|
||||
|
||||
void HangMonitorParent::ActorDestroy(ActorDestroyReason aWhy) {
|
||||
MOZ_RELEASE_ASSERT(IsOnThread());
|
||||
mIPCOpen = false;
|
||||
|
@ -1256,3 +1293,11 @@ void ProcessHangMonitor::MaybeStartPaintWhileInterruptingJS() {
|
|||
child->MaybeStartPaintWhileInterruptingJS();
|
||||
}
|
||||
}
|
||||
|
||||
/* static */
|
||||
void ProcessHangMonitor::CancelContentJSExecutionIfRunning(
|
||||
PProcessHangMonitorParent* aParent, dom::BrowserParent* aBrowserParent) {
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
auto parent = static_cast<HangMonitorParent*>(aParent);
|
||||
parent->CancelContentJSExecutionIfRunning(aBrowserParent);
|
||||
}
|
||||
|
|
|
@ -55,6 +55,9 @@ class ProcessHangMonitor final : public nsIObserver {
|
|||
const layers::LayersObserverEpoch& aEpoch);
|
||||
static void MaybeStartPaintWhileInterruptingJS();
|
||||
|
||||
static void CancelContentJSExecutionIfRunning(
|
||||
PProcessHangMonitorParent* aParent, dom::BrowserParent* aTab);
|
||||
|
||||
enum SlowScriptAction {
|
||||
Continue,
|
||||
Terminate,
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "nsIMemoryInfoDumper.h"
|
||||
#include "nsIMemoryReporter.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsIBrowserChild.h"
|
||||
#include "nsIDebug2.h"
|
||||
#include "nsIDocShell.h"
|
||||
#include "nsIRunnable.h"
|
||||
|
@ -638,26 +639,6 @@ bool XPCJSContext::InterruptCallback(JSContext* cx) {
|
|||
limit = Preferences::GetInt(prefName, 10);
|
||||
}
|
||||
|
||||
// If there's no limit, or we're within the limit, let it go.
|
||||
if (limit == 0 || duration.ToSeconds() < limit / 2.0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
self->mSlowScriptActualWait += duration;
|
||||
|
||||
// In order to guard against time changes or laptops going to sleep, we
|
||||
// don't trigger the slow script warning until (limit/2) seconds have
|
||||
// elapsed twice.
|
||||
if (!self->mSlowScriptSecondHalf) {
|
||||
self->mSlowScriptCheckpoint = TimeStamp::NowLoRes();
|
||||
self->mSlowScriptSecondHalf = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// This has gone on long enough! Time to take action. ;-)
|
||||
//
|
||||
|
||||
// Get the DOM window associated with the running script. If the script is
|
||||
// running in a non-DOM scope, we have to just let it keep running.
|
||||
RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
|
||||
|
@ -682,6 +663,40 @@ bool XPCJSContext::InterruptCallback(JSContext* cx) {
|
|||
return true;
|
||||
}
|
||||
|
||||
// Check if we're waiting to cancel JS when going back/forward in a tab.
|
||||
if (sTabIdToCancelContentJS) {
|
||||
if (nsIBrowserChild* browserChild = win->GetBrowserChild()) {
|
||||
uint64_t tabId;
|
||||
browserChild->GetTabId(&tabId);
|
||||
if (sTabIdToCancelContentJS == tabId) {
|
||||
// Don't add this page to the BF cache, since we're cancelling its JS.
|
||||
win->GetExtantDoc()->GetTopLevelContentDocument()->DisallowBFCaching();
|
||||
sTabIdToCancelContentJS = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there's no limit, or we're within the limit, let it go.
|
||||
if (limit == 0 || duration.ToSeconds() < limit / 2.0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
self->mSlowScriptActualWait += duration;
|
||||
|
||||
// In order to guard against time changes or laptops going to sleep, we
|
||||
// don't trigger the slow script warning until (limit/2) seconds have
|
||||
// elapsed twice.
|
||||
if (!self->mSlowScriptSecondHalf) {
|
||||
self->mSlowScriptCheckpoint = TimeStamp::NowLoRes();
|
||||
self->mSlowScriptSecondHalf = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// This has gone on long enough! Time to take action. ;-)
|
||||
//
|
||||
|
||||
if (win->IsDying()) {
|
||||
// The window is being torn down. When that happens we try to prevent
|
||||
// the dispatch of new runnables, so it also makes sense to kill any
|
||||
|
@ -1216,6 +1231,11 @@ nsresult XPCJSContext::Initialize(XPCJSContext* aPrimaryContext) {
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
// static
|
||||
void XPCJSContext::SetTabIdToCancelContentJS(uint64_t aTabId) {
|
||||
sTabIdToCancelContentJS = aTabId;
|
||||
}
|
||||
|
||||
// static
|
||||
uint32_t XPCJSContext::sInstanceCount;
|
||||
|
||||
|
@ -1233,6 +1253,9 @@ WatchdogManager* XPCJSContext::GetWatchdogManager() {
|
|||
return sWatchdogInstance;
|
||||
}
|
||||
|
||||
// static
|
||||
mozilla::Atomic<uint64_t> XPCJSContext::sTabIdToCancelContentJS(0);
|
||||
|
||||
// static
|
||||
void XPCJSContext::InitTLS() { MOZ_RELEASE_ASSERT(gTlsContext.init()); }
|
||||
|
||||
|
|
|
@ -421,6 +421,8 @@ class XPCJSContext final : public mozilla::CycleCollectedJSContext,
|
|||
inline JS::HandleId GetStringID(unsigned index) const;
|
||||
inline const char* GetStringName(unsigned index) const;
|
||||
|
||||
static void SetTabIdToCancelContentJS(uint64_t aTabId);
|
||||
|
||||
private:
|
||||
XPCJSContext();
|
||||
|
||||
|
@ -438,6 +440,8 @@ class XPCJSContext final : public mozilla::CycleCollectedJSContext,
|
|||
static mozilla::StaticAutoPtr<WatchdogManager> sWatchdogInstance;
|
||||
static WatchdogManager* GetWatchdogManager();
|
||||
|
||||
static mozilla::Atomic<uint64_t> sTabIdToCancelContentJS;
|
||||
|
||||
// If we spend too much time running JS code in an event handler, then we
|
||||
// want to show the slow script UI. The timeout T is controlled by prefs. We
|
||||
// invoke the interrupt callback once after T/2 seconds and set
|
||||
|
|
|
@ -1275,6 +1275,17 @@ VARCACHE_PREF(
|
|||
RelaxedAtomicBool, false
|
||||
)
|
||||
|
||||
#ifdef NIGHTLY_BUILD
|
||||
# define PREF_VALUE true
|
||||
#else
|
||||
# define PREF_VALUE false
|
||||
#endif
|
||||
VARCACHE_PREF(
|
||||
"dom.ipc.cancel_content_js_when_navigating",
|
||||
dom_ipc_cancel_content_js_when_navigating,
|
||||
bool, PREF_VALUE
|
||||
)
|
||||
#undef PREF_VALUE
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Media prefs
|
||||
|
|
|
@ -55,12 +55,15 @@ RemoteWebNavigation.prototype = {
|
|||
canGoBack: false,
|
||||
canGoForward: false,
|
||||
goBack() {
|
||||
this._browser.frameLoader.remoteTab.maybeCancelContentJSExecution();
|
||||
this._sendMessage("WebNavigation:GoBack", {});
|
||||
},
|
||||
goForward() {
|
||||
this._browser.frameLoader.remoteTab.maybeCancelContentJSExecution();
|
||||
this._sendMessage("WebNavigation:GoForward", {});
|
||||
},
|
||||
gotoIndex(aIndex) {
|
||||
this._browser.frameLoader.remoteTab.maybeCancelContentJSExecution();
|
||||
this._sendMessage("WebNavigation:GotoIndex", {index: aIndex});
|
||||
},
|
||||
loadURI(aURI, aLoadURIOptions) {
|
||||
|
@ -89,6 +92,7 @@ RemoteWebNavigation.prototype = {
|
|||
}
|
||||
}
|
||||
|
||||
this._browser.frameLoader.remoteTab.maybeCancelContentJSExecution();
|
||||
this._sendMessage("WebNavigation:LoadURI", {
|
||||
uri: aURI,
|
||||
flags: aLoadURIOptions.loadFlags,
|
||||
|
|
Загрузка…
Ссылка в новой задаче