author | Jim Porter <jporter@mozilla.com> |
Tue, 30 Apr 2019 23:56:17 +0000 | |
changeset 472060 | 5c3bff8f45d696710545579b14346a65c07aa2cf |
parent 472059 | 9242458b79d5ae6e9d48c9a2a591680d79c13211 |
child 472061 | a7fa3041bff04a25fa39fb6f714071314f3cc69b |
push id | 84445 |
push user | jporter@mozilla.com |
push date | Wed, 01 May 2019 03:29:15 +0000 |
treeherder | autoland@9a0ce3016f03 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | smaug |
bugs | 1493225 |
milestone | 68.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/dom/base/nsGlobalWindowInner.h +++ b/dom/base/nsGlobalWindowInner.h @@ -389,16 +389,18 @@ class nsGlobalWindowInner final : public nsPIDOMWindowOuter* GetScriptableTop() override; inline nsGlobalWindowOuter* GetTopInternal(); inline nsGlobalWindowOuter* GetScriptableTopInternal(); 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. // // DialogsAreBeingAbused must be called on the scriptable top inner window. // // nsGlobalWindowOuter::ShouldPromptToBlockDialogs is implemented in terms of // nsGlobalWindowInner::DialogsAreBeingAbused, and will get the scriptable top // inner window automatically. Inner windows only.
--- a/dom/interfaces/base/nsIRemoteTab.idl +++ b/dom/interfaces/base/nsIRemoteTab.idl @@ -116,9 +116,16 @@ interface nsIRemoteTab : nsISupports * content blocking events happened so far in the current tab from the * content process. * * This returns a Promise which resolves to a string on success, and is * rejected on failure. For documentation on the string format, please * 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(); };
--- a/dom/ipc/BrowserParent.cpp +++ b/dom/ipc/BrowserParent.cpp @@ -36,16 +36,18 @@ #include "mozilla/layers/InputAPZContext.h" #include "mozilla/layout/RenderFrame.h" #include "mozilla/plugins/PPluginWidgetParent.h" #include "mozilla/LookAndFeel.h" #include "mozilla/MouseEvents.h" #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" #include "mozilla/Unused.h" #include "nsCOMPtr.h" #include "nsContentAreaDragDrop.h" #include "nsContentUtils.h" #include "nsDebug.h" @@ -3113,16 +3115,24 @@ BrowserParent::GetContentBlockingLog(Pro }, [jsPromise](ResponseRejectReason&& aReason) { jsPromise->MaybeRejectWithUndefined(); }); 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; } #ifdef DEBUG if (aEnabled) { mActiveSupressDisplayportCount++;
--- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -5290,16 +5290,25 @@ void ContentParent::PaintTabWhileInterru const layers::LayersObserverEpoch& aEpoch) { if (!mHangMonitorActor) { return; } ProcessHangMonitor::PaintWhileInterruptingJS( 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 = LoneManagedOrNullAsserts(neckoParent->ManagedPCookieServiceParent()); if (csParent) { auto* cs = static_cast<CookieServiceParent*>(csParent); cs->TrackCookieLoad(aChannel); }
--- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -586,16 +586,18 @@ class ContentParent final : public PCont bool DeallocPURLClassifierParent(PURLClassifierParent* aActor); // Use the PHangMonitor channel to ask the child to repaint a tab. void PaintTabWhileInterruptingJS(BrowserParent* aBrowserParent, 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 // document loads. nsresult AboutToLoadHttpFtpDocumentForChild(nsIChannel* aChannel); nsresult TransmitPermissionsForPrincipal(nsIPrincipal* aPrincipal);
--- a/dom/ipc/PProcessHangMonitor.ipdl +++ b/dom/ipc/PProcessHangMonitor.ipdl @@ -38,11 +38,13 @@ parent: child: async TerminateScript(bool aTerminateGlobal); async BeginStartingDebugger(); async EndStartingDebugger(); async PaintWhileInterruptingJS(TabId tabId, bool forceRepaint, LayersObserverEpoch aEpoch); + + async CancelContentJSExecutionIfRunning(TabId tabId); }; } // namespace mozilla
--- a/dom/ipc/ProcessHangMonitor.cpp +++ b/dom/ipc/ProcessHangMonitor.cpp @@ -3,16 +3,17 @@ /* 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/. */ #include "mozilla/ProcessHangMonitor.h" #include "mozilla/ProcessHangMonitorIPC.h" #include "jsapi.h" +#include "xpcprivate.h" #include "mozilla/Atomics.h" #include "mozilla/BackgroundHangMonitor.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/BrowserChild.h" #include "mozilla/dom/BrowserParent.h" @@ -107,16 +108,19 @@ class HangMonitorChild : public PProcess const bool& aTerminateGlobal) override; mozilla::ipc::IPCResult RecvBeginStartingDebugger() override; mozilla::ipc::IPCResult RecvEndStartingDebugger() override; mozilla::ipc::IPCResult RecvPaintWhileInterruptingJS( 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(); void Shutdown(); static HangMonitorChild* Get() { return sInstance; } void Dispatch(already_AddRefed<nsIRunnable> aRunnable) { @@ -229,16 +233,17 @@ class HangMonitorParent : public PProces void SetProcess(HangMonitoredProcess* aProcess) { mProcess = aProcess; } void Shutdown(); void PaintWhileInterruptingJS(dom::BrowserParent* aBrowserParent, bool aForceRepaint, const LayersObserverEpoch& aEpoch); + void CancelContentJSExecutionIfRunning(dom::BrowserParent* aBrowserParent); void TerminateScript(bool aTerminateGlobal); void BeginStartingDebugger(); void EndStartingDebugger(); void CleanupPluginHang(uint32_t aPluginId, bool aRemoveFiles); /** * Update the dump for the specified plugin. This method is thread-safe and @@ -257,16 +262,17 @@ class HangMonitorParent : public PProces void SendHangNotification(const HangData& aHangData, const nsString& aBrowserDumpId, bool aTakeMinidump); void ClearHangNotification(); void PaintWhileInterruptingJSOnThread(TabId aTabId, bool aForceRepaint, const LayersObserverEpoch& aEpoch); + void CancelContentJSExecutionIfRunningOnThread(TabId aTabId); void ShutdownOnThread(); const RefPtr<ProcessHangMonitor> mHangMonitor; // This field is read-only after construction. bool mReportHangs; @@ -443,16 +449,28 @@ void HangMonitorChild::MaybeStartPaintWh void HangMonitorChild::ClearPaintWhileInterruptingJS( const LayersObserverEpoch& aEpoch) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); MOZ_RELEASE_ASSERT(XRE_IsContentProcess()); 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()); MOZ_ASSERT(!sInstance); sInstance = this; DebugOnly<bool> ok = aEndpoint.Bind(this); MOZ_ASSERT(ok); @@ -651,16 +669,35 @@ void HangMonitorParent::PaintWhileInterr TabId aTabId, bool aForceRepaint, const LayersObserverEpoch& aEpoch) { MOZ_RELEASE_ASSERT(IsOnThread()); if (mIPCOpen) { Unused << SendPaintWhileInterruptingJS(aTabId, aForceRepaint, aEpoch); } } +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; } void HangMonitorParent::Bind(Endpoint<PProcessHangMonitorParent>&& aEndpoint) { MOZ_RELEASE_ASSERT(IsOnThread()); @@ -1251,8 +1288,16 @@ void ProcessHangMonitor::ClearPaintWhile void ProcessHangMonitor::MaybeStartPaintWhileInterruptingJS() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); MOZ_RELEASE_ASSERT(XRE_IsContentProcess()); if (HangMonitorChild* child = HangMonitorChild::Get()) { 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); +}
--- a/dom/ipc/ProcessHangMonitor.h +++ b/dom/ipc/ProcessHangMonitor.h @@ -50,16 +50,19 @@ class ProcessHangMonitor final : public static void PaintWhileInterruptingJS( PProcessHangMonitorParent* aParent, dom::BrowserParent* aTab, bool aForceRepaint, const layers::LayersObserverEpoch& aEpoch); static void ClearPaintWhileInterruptingJS( const layers::LayersObserverEpoch& aEpoch); static void MaybeStartPaintWhileInterruptingJS(); + static void CancelContentJSExecutionIfRunning( + PProcessHangMonitorParent* aParent, dom::BrowserParent* aTab); + enum SlowScriptAction { Continue, Terminate, StartDebugger, TerminateGlobal, }; SlowScriptAction NotifySlowScript(nsIBrowserChild* aBrowserChild, const char* aFileName,
--- a/js/xpconnect/src/XPCJSContext.cpp +++ b/js/xpconnect/src/XPCJSContext.cpp @@ -17,16 +17,17 @@ #include "mozJSComponentLoader.h" #include "nsAutoPtr.h" #include "nsNetUtil.h" #include "nsThreadUtils.h" #include "nsIMemoryInfoDumper.h" #include "nsIMemoryReporter.h" #include "nsIObserverService.h" +#include "nsIBrowserChild.h" #include "nsIDebug2.h" #include "nsIDocShell.h" #include "nsIRunnable.h" #include "nsPIDOMWindow.h" #include "nsPrintfCString.h" #include "mozilla/Preferences.h" #include "mozilla/Telemetry.h" #include "mozilla/Services.h" @@ -633,36 +634,16 @@ bool XPCJSContext::InterruptCallback(JSC policy->GetId(addonId); prefName = PREF_MAX_SCRIPT_RUN_TIME_EXT_CONTENT; limit = Preferences::GetInt(prefName, 5); } else { prefName = PREF_MAX_SCRIPT_RUN_TIME_CONTENT; 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)); RefPtr<nsGlobalWindowInner> win = WindowOrNull(global); if (!win && IsSandbox(global)) { // If this is a sandbox associated with a DOMWindow via a // sandboxPrototype, use that DOMWindow. This supports GreaseMonkey // and JetPack content scripts. @@ -677,16 +658,50 @@ bool XPCJSContext::InterruptCallback(JSC } } if (!win) { NS_WARNING("No active window"); 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 // long-running script. The user is primarily interested in this page // going away. return false; } @@ -1212,16 +1227,21 @@ nsresult XPCJSContext::Initialize(XPCJSC #ifdef FUZZING Preferences::RegisterCallback(ReloadPrefsCallback, "fuzzing.enabled", this); #endif return NS_OK; } // static +void XPCJSContext::SetTabIdToCancelContentJS(uint64_t aTabId) { + sTabIdToCancelContentJS = aTabId; +} + +// static uint32_t XPCJSContext::sInstanceCount; // static StaticAutoPtr<WatchdogManager> XPCJSContext::sWatchdogInstance; // static WatchdogManager* XPCJSContext::GetWatchdogManager() { if (sWatchdogInstance) { @@ -1229,16 +1249,19 @@ WatchdogManager* XPCJSContext::GetWatchd } MOZ_ASSERT(sInstanceCount == 0); sWatchdogInstance = new WatchdogManager(); return sWatchdogInstance; } // static +mozilla::Atomic<uint64_t> XPCJSContext::sTabIdToCancelContentJS(0); + +// static void XPCJSContext::InitTLS() { MOZ_RELEASE_ASSERT(gTlsContext.init()); } // static XPCJSContext* XPCJSContext::NewXPCJSContext(XPCJSContext* aPrimaryContext) { XPCJSContext* self = new XPCJSContext(); nsresult rv = self->Initialize(aPrimaryContext); if (NS_FAILED(rv)) { MOZ_CRASH("new XPCJSContext failed to initialize.");
--- a/js/xpconnect/src/xpcprivate.h +++ b/js/xpconnect/src/xpcprivate.h @@ -416,16 +416,18 @@ class XPCJSContext final : public mozill IDX_INTERFACE_ID, IDX_INITIALIZER, IDX_TOTAL_COUNT // just a count of the above }; inline JS::HandleId GetStringID(unsigned index) const; inline const char* GetStringName(unsigned index) const; + static void SetTabIdToCancelContentJS(uint64_t aTabId); + private: XPCJSContext(); MOZ_IS_CLASS_INIT nsresult Initialize(XPCJSContext* aPrimaryContext); XPCCallContext* mCallContext; AutoMarkingPtr* mAutoRoots; @@ -433,16 +435,18 @@ class XPCJSContext final : public mozill XPCWrappedNative* mResolvingWrapper; WatchdogManager* mWatchdogManager; // Number of XPCJSContexts currently alive. static uint32_t sInstanceCount; 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 // mSlowScriptSecondHalf to true. After another T/2 seconds, we invoke the // interrupt callback again. Since mSlowScriptSecondHalf is now true, it // shows the slow script UI. The reason we invoke the callback twice is to // ensure that putting the computer to sleep while running a script doesn't // cause the UI to be shown. If the laptop goes to sleep during one of the
--- a/modules/libpref/init/StaticPrefList.h +++ b/modules/libpref/init/StaticPrefList.h @@ -1270,16 +1270,27 @@ VARCACHE_PREF( ) VARCACHE_PREF( "javascript.options.experimental.await_fix", javascript_options_experimental_await_fix, 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 //--------------------------------------------------------------------------- // These prefs use camel case instead of snake case for the getter because one // reviewer had an unshakeable preference for that.
--- a/toolkit/components/remotebrowserutils/RemoteWebNavigation.jsm +++ b/toolkit/components/remotebrowserutils/RemoteWebNavigation.jsm @@ -50,22 +50,25 @@ RemoteWebNavigation.prototype = { STOP_NETWORK: 1, STOP_CONTENT: 2, STOP_ALL: 3, 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) { // We know the url is going to be loaded, let's start requesting network // connection before the content process asks. // Note that we might have already setup the speculative connection in some // cases, especially when the url is from location bar or its popup menu. if (aURI.startsWith("http:") || aURI.startsWith("https:")) { @@ -84,16 +87,17 @@ RemoteWebNavigation.prototype = { } Services.io.speculativeConnect(uri, principal, null); } catch (ex) { // Can't setup speculative connection for this uri string for some // reason (such as failing to parse the URI), just ignore it. } } + this._browser.frameLoader.remoteTab.maybeCancelContentJSExecution(); this._sendMessage("WebNavigation:LoadURI", { uri: aURI, flags: aLoadURIOptions.loadFlags, referrerInfo: E10SUtils.serializeReferrerInfo(aLoadURIOptions.referrerInfo), postData: aLoadURIOptions.postData ? Utils.serializeInputStream(aLoadURIOptions.postData) : null, headers: aLoadURIOptions.headers ? Utils.serializeInputStream(aLoadURIOptions.headers) : null, baseURI: aLoadURIOptions.baseURI ? aLoadURIOptions.baseURI.spec : null, triggeringPrincipal: E10SUtils.serializePrincipal(