зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1777198 - Cancel content JS execution on quit-application-granted or on normal content process shutdown. r=smaug
We want to signal content processes to cancel content JS unconditionally on shutdown. In the case of parent shutdown this has to happen as early as "quit-application-granted", given that both extensions and session storage shutdown rely on the possibility to interact with content processes (which is not possible when they are inside long running JS). In addition in the case of a normal child shutdown we cancel content JS execution, too. For now we put this behind the pref "dom.abort_script_on_child_shutdown" which remains default off. Depends on D150539 Differential Revision: https://phabricator.services.mozilla.com/D150598
This commit is contained in:
Родитель
4c88b66171
Коммит
3ca1fb43c7
|
@ -1652,6 +1652,7 @@ const nsACString& ContentParent::GetRemoteType() const { return mRemoteType; }
|
|||
|
||||
static StaticRefPtr<nsIAsyncShutdownClient> sXPCOMShutdownClient;
|
||||
static StaticRefPtr<nsIAsyncShutdownClient> sProfileBeforeChangeClient;
|
||||
static StaticRefPtr<nsIAsyncShutdownClient> sQuitApplicationGrantedClient;
|
||||
|
||||
void ContentParent::Init() {
|
||||
MOZ_ASSERT(sXPCOMShutdownClient);
|
||||
|
@ -1722,6 +1723,7 @@ void ContentParent::MaybeBeginShutDown(uint32_t aExpectedBrowserCount,
|
|||
|
||||
// We're dying now, prevent anything from re-using this process.
|
||||
MarkAsDead();
|
||||
SignalImpendingShutdownToContentJS();
|
||||
StartForceKillTimer();
|
||||
|
||||
if (aSendShutDown) {
|
||||
|
@ -1799,7 +1801,12 @@ void ContentParent::ShutDownProcess(ShutDownMethod aMethod) {
|
|||
__LINE__);
|
||||
mShutdownPending = true;
|
||||
// Start the force-kill timer if we haven't already.
|
||||
StartForceKillTimer();
|
||||
// This can happen if we shutdown a process while launching or
|
||||
// because it is removed from the cached processes pool.
|
||||
if (!mForceKillTimer) {
|
||||
SignalImpendingShutdownToContentJS();
|
||||
StartForceKillTimer();
|
||||
}
|
||||
} else {
|
||||
MaybeLogBlockShutdownDiagnostics(
|
||||
this, "ShutDownProcess: !!! Send shutdown message failed! !!!",
|
||||
|
@ -2367,8 +2374,6 @@ void ContentParent::StartForceKillTimer() {
|
|||
return;
|
||||
}
|
||||
|
||||
NotifyImpendingShutdown();
|
||||
|
||||
int32_t timeoutSecs = StaticPrefs::dom_ipc_tabs_shutdownTimeoutSecs();
|
||||
if (timeoutSecs > 0) {
|
||||
NS_NewTimerWithFuncCallback(getter_AddRefs(mForceKillTimer),
|
||||
|
@ -3578,14 +3583,71 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ContentParent)
|
|||
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMProcessParent)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
class RequestContentJSInterruptRunnable final : public Runnable {
|
||||
public:
|
||||
explicit RequestContentJSInterruptRunnable(PProcessHangMonitorParent* aActor)
|
||||
: Runnable("dom::RequestContentJSInterruptRunnable"),
|
||||
mHangMonitorActor(aActor) {}
|
||||
|
||||
NS_IMETHOD Run() override {
|
||||
MOZ_ASSERT(mHangMonitorActor);
|
||||
Unused << mHangMonitorActor->SendRequestContentJSInterrupt();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
// The end-of-life of ContentParent::mHangMonitorActor is bound to
|
||||
// ContentParent::ActorDestroy and then HangMonitorParent::Shutdown
|
||||
// dispatches a shutdown runnable to this queue and waits for it to be
|
||||
// executed. So the runnable needs not to care about keeping it alive,
|
||||
// as it is surely dispatched earlier than the
|
||||
// HangMonitorParent::ShutdownOnThread.
|
||||
PProcessHangMonitorParent* mHangMonitorActor;
|
||||
};
|
||||
|
||||
void ContentParent::SignalImpendingShutdownToContentJS() {
|
||||
if (!AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdown)) {
|
||||
MaybeLogBlockShutdownDiagnostics(
|
||||
this, "BlockShutdown: NotifyImpendingShutdown.", __FILE__, __LINE__);
|
||||
NotifyImpendingShutdown();
|
||||
if (mHangMonitorActor &&
|
||||
StaticPrefs::dom_abort_script_on_child_shutdown()) {
|
||||
MaybeLogBlockShutdownDiagnostics(
|
||||
this, "BlockShutdown: RequestContentJSInterrupt.", __FILE__,
|
||||
__LINE__);
|
||||
RefPtr<RequestContentJSInterruptRunnable> r =
|
||||
new RequestContentJSInterruptRunnable(mHangMonitorActor);
|
||||
ProcessHangMonitor::Get()->Dispatch(r.forget());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Async shutdown blocker
|
||||
NS_IMETHODIMP
|
||||
ContentParent::BlockShutdown(nsIAsyncShutdownClient* aClient) {
|
||||
if (!AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdown)) {
|
||||
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
|
||||
// We register two shutdown blockers and both would call us, but
|
||||
// if things go well we will unregister both as (delayed) reaction
|
||||
// to the first call we get and thus never receive a second call.
|
||||
// Thus we believe that we will get called only once.
|
||||
mBlockShutdownCalled = true;
|
||||
#endif
|
||||
// Our real shutdown has not yet started. Just notify the
|
||||
// impending shutdown and eventually cancel content JS.
|
||||
SignalImpendingShutdownToContentJS();
|
||||
if (sQuitApplicationGrantedClient) {
|
||||
Unused << sQuitApplicationGrantedClient->RemoveBlocker(this);
|
||||
}
|
||||
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
|
||||
mBlockShutdownCalled = false;
|
||||
#endif
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
|
||||
// We register two final shutdown blockers and both would call us, but if
|
||||
// things go well we will unregister both as (delayed) reaction to the first
|
||||
// call we get and thus never receive a second call. Thus we believe that we
|
||||
// will get called only once except for quit-application-granted, which is
|
||||
// handled above.
|
||||
MOZ_ASSERT(!mBlockShutdownCalled);
|
||||
mBlockShutdownCalled = true;
|
||||
#endif
|
||||
|
@ -3671,6 +3733,15 @@ static void InitShutdownClients() {
|
|||
ClearOnShutdown(&sProfileBeforeChangeClient);
|
||||
}
|
||||
}
|
||||
// TODO: ShutdownPhase::AppShutdownConfirmed is not mapping to
|
||||
// QuitApplicationGranted, see bug 1762840 comment 4.
|
||||
if (!AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
|
||||
rv = svc->GetQuitApplicationGranted(getter_AddRefs(client));
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
sQuitApplicationGrantedClient = client.forget();
|
||||
ClearOnShutdown(&sQuitApplicationGrantedClient);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3687,6 +3758,10 @@ void ContentParent::AddShutdownBlockers() {
|
|||
sProfileBeforeChangeClient->AddBlocker(
|
||||
this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__, u""_ns);
|
||||
}
|
||||
if (sQuitApplicationGrantedClient) {
|
||||
sQuitApplicationGrantedClient->AddBlocker(
|
||||
this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__, u""_ns);
|
||||
}
|
||||
}
|
||||
|
||||
void ContentParent::RemoveShutdownBlockers() {
|
||||
|
@ -3705,6 +3780,9 @@ void ContentParent::RemoveShutdownBlockers() {
|
|||
if (sProfileBeforeChangeClient) {
|
||||
Unused << sProfileBeforeChangeClient->RemoveBlocker(this);
|
||||
}
|
||||
if (sQuitApplicationGrantedClient) {
|
||||
Unused << sQuitApplicationGrantedClient->RemoveBlocker(this);
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
|
|
@ -817,6 +817,13 @@ class ContentParent final : public PContentParent,
|
|||
*/
|
||||
void MarkAsDead();
|
||||
|
||||
/**
|
||||
* Let the process know we are about to send a shutdown through a
|
||||
* non-mainthread side channel in order to bypass mainthread congestion.
|
||||
* This potentially cancels mainthread content JS execution.
|
||||
*/
|
||||
void SignalImpendingShutdownToContentJS();
|
||||
|
||||
/**
|
||||
* Check if this process is ready to be shut down, and if it is, begin the
|
||||
* shutdown process. Should be called whenever a change occurs which could
|
||||
|
|
|
@ -32,6 +32,7 @@ parent:
|
|||
|
||||
child:
|
||||
async TerminateScript();
|
||||
async RequestContentJSInterrupt();
|
||||
|
||||
async BeginStartingDebugger();
|
||||
async EndStartingDebugger();
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "mozilla/dom/BrowserChild.h"
|
||||
#include "mozilla/dom/BrowserParent.h"
|
||||
#include "mozilla/ipc/Endpoint.h"
|
||||
#include "mozilla/ipc/ProcessChild.h"
|
||||
#include "mozilla/ipc/TaskFactory.h"
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
|
@ -110,6 +111,7 @@ class HangMonitorChild : public PProcessHangMonitorChild,
|
|||
void MaybeStartPaintWhileInterruptingJS();
|
||||
|
||||
mozilla::ipc::IPCResult RecvTerminateScript() override;
|
||||
mozilla::ipc::IPCResult RecvRequestContentJSInterrupt() override;
|
||||
mozilla::ipc::IPCResult RecvBeginStartingDebugger() override;
|
||||
mozilla::ipc::IPCResult RecvEndStartingDebugger() override;
|
||||
|
||||
|
@ -344,6 +346,18 @@ HangMonitorChild::~HangMonitorChild() {
|
|||
bool HangMonitorChild::InterruptCallback() {
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (StaticPrefs::dom_abort_script_on_child_shutdown() &&
|
||||
mozilla::ipc::ProcessChild::ExpectingShutdown()) {
|
||||
// We preserve chrome JS from cancel, but not extension content JS.
|
||||
if (!nsContentUtils::IsCallerChrome()) {
|
||||
NS_WARNING(
|
||||
"HangMonitorChild::InterruptCallback: ExpectingShutdown, "
|
||||
"canceling content JS execution.\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Don't start painting if we're not in a good place to run script. We run
|
||||
// chrome script during layout and such, and it wouldn't be good to interrupt
|
||||
// painting code from there.
|
||||
|
@ -495,6 +509,18 @@ mozilla::ipc::IPCResult HangMonitorChild::RecvTerminateScript() {
|
|||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult HangMonitorChild::RecvRequestContentJSInterrupt() {
|
||||
MOZ_RELEASE_ASSERT(IsOnThread());
|
||||
|
||||
CrashReporter::AnnotateCrashReport(
|
||||
CrashReporter::Annotation::IPCShutdownState,
|
||||
"HangMonitorChild::RecvRequestContentJSInterrupt"_ns);
|
||||
// In order to cancel JS execution on shutdown, we expect that
|
||||
// ProcessChild::NotifiedImpendingShutdown has been called before.
|
||||
JS_RequestInterruptCallback(mContext);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult HangMonitorChild::RecvBeginStartingDebugger() {
|
||||
MOZ_RELEASE_ASSERT(IsOnThread());
|
||||
|
||||
|
|
|
@ -70,6 +70,8 @@ add_task(async () => {
|
|||
return;
|
||||
}
|
||||
|
||||
await pushPref("dom.abort_script_on_child_shutdown", true);
|
||||
|
||||
// Ensure the process cache cannot interfere.
|
||||
pushPref("dom.ipc.processPreload.enabled", false);
|
||||
// Ensure we have no cached processes from previous tests.
|
||||
|
|
|
@ -74,7 +74,7 @@ void ProcessChild::NotifiedImpendingShutdown() {
|
|||
sExpectingShutdown = true;
|
||||
CrashReporter::AnnotateCrashReport(
|
||||
CrashReporter::Annotation::IPCShutdownState,
|
||||
"NotifyImpendingShutdown received."_ns);
|
||||
"NotifiedImpendingShutdown"_ns);
|
||||
}
|
||||
|
||||
/* static */
|
||||
|
|
|
@ -4514,6 +4514,12 @@
|
|||
#endif
|
||||
mirror: always
|
||||
|
||||
# Controls if a content script will be aborted on child process shutdown.
|
||||
- name: dom.abort_script_on_child_shutdown
|
||||
type: bool
|
||||
value: false
|
||||
mirror: always
|
||||
|
||||
- name: dom.max_chrome_script_run_time
|
||||
type: int32_t
|
||||
value: 0
|
||||
|
|
Загрузка…
Ссылка в новой задаче