Bug 1356334: Part 6 - Allow completely terminating a slow content script sandbox. r=billm

MozReview-Commit-ID: 5CDLHrAeuDt

--HG--
extra : rebase_source : 00f75b5be53e38d912b90b8fe777c5aa7ff135bf
This commit is contained in:
Kris Maglione 2017-04-16 14:01:16 -07:00
parent 2c6fe4f014
commit bdbbf869dd
10 changed files with 133 additions and 24 deletions

View File

@ -593,6 +593,8 @@ processHang.add-on.learn-more.text = Learn more
processHang.add-on.learn-more.url = https://support.mozilla.org/en-US/kb/warning-unresponsive-script?cache=no#w_other-causes
processHang.button_stop.label = Stop It
processHang.button_stop.accessKey = S
processHang.button_stop_sandbox.label = Temporarily Disable Extension on Page
processHang.button_stop_sandbox.accessKey = A
processHang.button_wait.label = Wait
processHang.button_wait.accessKey = W
processHang.button_debug.label = Debug Script

View File

@ -64,6 +64,14 @@ var ProcessHangMonitor = {
this.handleUserInput(win, report => report.terminateScript());
},
/**
* Terminate Sandbox globals associated with the hang being reported
* for the selected browser in |win|.
*/
terminateGlobal(win) {
this.handleUserInput(win, report => report.terminateGlobal());
},
/**
* Start devtools debugger for JavaScript associated with the hang
* being reported for the selected browser in |win|.
@ -111,6 +119,23 @@ var ProcessHangMonitor = {
}
},
/**
* Stop all scripts from running in the Sandbox global attached to
* this window.
*/
stopGlobal(win) {
let report = this.findActiveReport(win.gBrowser.selectedBrowser);
if (!report) {
return;
}
switch (report.hangType) {
case report.SLOW_SCRIPT:
this.terminateGlobal(win);
break;
}
},
/**
* Dismiss the notification, clear the report from the active list and set up
* a new timer to track a wait period during which we won't notify.
@ -328,6 +353,14 @@ var ProcessHangMonitor = {
message = doc.createDocumentFragment();
message.appendChild(doc.createTextNode(label + " "));
message.appendChild(link);
buttons.unshift({
label: bundle.getString("processHang.button_stop_sandbox.label"),
accessKey: bundle.getString("processHang.button_stop_sandbox.accessKey"),
callback() {
ProcessHangMonitor.stopGlobal(win);
}
});
}
if (AppConstants.MOZ_DEV_EDITION && report.hangType == report.SLOW_SCRIPT) {

View File

@ -11738,6 +11738,9 @@ nsGlobalWindow::ShowSlowScriptDialog(const nsString& aAddonId)
if (action == ProcessHangMonitor::Terminate) {
return KillSlowScript;
}
if (action == ProcessHangMonitor::TerminateGlobal) {
return KillScriptGlobal;
}
if (action == ProcessHangMonitor::StartDebugger) {
// Spin a nested event loop so that the debugger in the parent can fetch
@ -11791,9 +11794,10 @@ nsGlobalWindow::ShowSlowScriptDialog(const nsString& aAddonId)
// Get localizable strings
nsAutoString title, debugButton, msg;
nsAutoString title, checkboxMsg, debugButton, msg;
if (isAddonScript) {
title = getString("KillAddonScriptTitle");
checkboxMsg = getString("KillAddonScriptGlobalMessage");
auto appName = getString("brandShortName", nsContentUtils::eBRAND_PROPERTIES);
@ -11811,6 +11815,7 @@ nsGlobalWindow::ShowSlowScriptDialog(const nsString& aAddonId)
failed = failed || NS_FAILED(rv);
} else {
title = getString("KillScriptTitle");
checkboxMsg = getString("DontAskAgain");
if (showDebugButton) {
debugButton = getString("DebugScriptButton");
@ -11820,7 +11825,6 @@ nsGlobalWindow::ShowSlowScriptDialog(const nsString& aAddonId)
}
}
auto neverShowDlg = getString("DontAskAgain");
auto stopButton = getString("StopScriptButton");
auto waitButton = getString("WaitForScriptButton");
@ -11872,8 +11876,6 @@ nsGlobalWindow::ShowSlowScriptDialog(const nsString& aAddonId)
}
}
int32_t buttonPressed = 0; // In case the user exits dialog by clicking X.
bool neverShowDlgChk = false;
uint32_t buttonFlags = nsIPrompt::BUTTON_POS_1_DEFAULT +
(nsIPrompt::BUTTON_TITLE_IS_STRING *
(nsIPrompt::BUTTON_POS_0 + nsIPrompt::BUTTON_POS_1));
@ -11882,26 +11884,36 @@ nsGlobalWindow::ShowSlowScriptDialog(const nsString& aAddonId)
if (showDebugButton)
buttonFlags += nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_2;
bool checkboxValue = false;
int32_t buttonPressed = 0; // In case the user exits dialog by clicking X.
{
// Null out the operation callback while we're re-entering JS here.
AutoDisableJSInterruptCallback disabler(cx);
// Open the dialog.
rv = prompt->ConfirmEx(title.get(), msg.get(), buttonFlags,
waitButton.get(), stopButton.get(),
debugButton.get(), neverShowDlg.get(),
&neverShowDlgChk, &buttonPressed);
debugButton.get(), checkboxMsg.get(),
&checkboxValue, &buttonPressed);
}
if (NS_SUCCEEDED(rv) && (buttonPressed == 0)) {
return neverShowDlgChk ? AlwaysContinueSlowScript : ContinueSlowScript;
if (buttonPressed == 0) {
if (checkboxValue && !isAddonScript && NS_SUCCEEDED(rv))
return AlwaysContinueSlowScript;
return ContinueSlowScript;
}
if (buttonPressed == 2) {
if (debugCallback) {
rv = debugCallback->HandleSlowScriptDebug(this);
return NS_SUCCEEDED(rv) ? ContinueSlowScript : KillSlowScript;
}
MOZ_RELEASE_ASSERT(debugCallback);
rv = debugCallback->HandleSlowScriptDebug(this);
return NS_SUCCEEDED(rv) ? ContinueSlowScript : KillSlowScript;
}
JS_ClearPendingException(cx);
if (checkboxValue && isAddonScript)
return KillScriptGlobal;
return KillSlowScript;
}

View File

@ -737,7 +737,8 @@ public:
ContinueSlowScript = 0,
ContinueSlowScriptAndKeepNotifying,
AlwaysContinueSlowScript,
KillSlowScript
KillSlowScript,
KillScriptGlobal
};
SlowScriptResponse ShowSlowScriptDialog(const nsString& aAddonId);

View File

@ -36,7 +36,7 @@ parent:
async ClearHang();
child:
async TerminateScript();
async TerminateScript(bool aTerminateGlobal);
async BeginStartingDebugger();
async EndStartingDebugger();

View File

@ -100,7 +100,7 @@ class HangMonitorChild
void ClearHangAsync();
void ClearForcePaint();
mozilla::ipc::IPCResult RecvTerminateScript() override;
mozilla::ipc::IPCResult RecvTerminateScript(const bool& aTerminateGlobal) override;
mozilla::ipc::IPCResult RecvBeginStartingDebugger() override;
mozilla::ipc::IPCResult RecvEndStartingDebugger() override;
@ -133,6 +133,7 @@ class HangMonitorChild
// These fields must be accessed with mMonitor held.
bool mTerminateScript;
bool mTerminateGlobal;
bool mStartDebugger;
bool mFinishedStartingDebugger;
bool mForcePaint;
@ -169,6 +170,7 @@ public:
NS_IMETHOD GetPluginName(nsACString& aPluginName) override;
NS_IMETHOD TerminateScript() override;
NS_IMETHOD TerminateGlobal() override;
NS_IMETHOD BeginStartingDebugger() override;
NS_IMETHOD EndStartingDebugger() override;
NS_IMETHOD TerminatePlugin() override;
@ -233,7 +235,7 @@ public:
void ForcePaint(dom::TabParent* aTabParent, uint64_t aLayerObserverEpoch);
void TerminateScript();
void TerminateScript(bool aTerminateGlobal);
void BeginStartingDebugger();
void EndStartingDebugger();
void CleanupPluginHang(uint32_t aPluginId, bool aRemoveFiles);
@ -298,6 +300,7 @@ HangMonitorChild::HangMonitorChild(ProcessHangMonitor* aMonitor)
mMonitor("HangMonitorChild lock"),
mSentReport(false),
mTerminateScript(false),
mTerminateGlobal(false),
mStartDebugger(false),
mFinishedStartingDebugger(false),
mForcePaint(false),
@ -384,12 +387,16 @@ HangMonitorChild::ActorDestroy(ActorDestroyReason aWhy)
}
mozilla::ipc::IPCResult
HangMonitorChild::RecvTerminateScript()
HangMonitorChild::RecvTerminateScript(const bool& aTerminateGlobal)
{
MOZ_RELEASE_ASSERT(IsOnThread());
MonitorAutoLock lock(mMonitor);
mTerminateScript = true;
if (aTerminateGlobal) {
mTerminateGlobal = true;
} else {
mTerminateScript = true;
}
return IPC_OK();
}
@ -480,6 +487,11 @@ HangMonitorChild::NotifySlowScript(nsITabChild* aTabChild,
return SlowScriptAction::Terminate;
}
if (mTerminateGlobal) {
mTerminateGlobal = false;
return SlowScriptAction::TerminateGlobal;
}
if (mStartDebugger) {
mStartDebugger = false;
return SlowScriptAction::StartDebugger;
@ -559,6 +571,7 @@ HangMonitorChild::ClearHang()
MonitorAutoLock lock(mMonitor);
mSentReport = false;
mTerminateScript = false;
mTerminateGlobal = false;
mStartDebugger = false;
mFinishedStartingDebugger = false;
}
@ -841,12 +854,12 @@ HangMonitorParent::RecvClearHang()
}
void
HangMonitorParent::TerminateScript()
HangMonitorParent::TerminateScript(bool aTerminateGlobal)
{
MOZ_RELEASE_ASSERT(IsOnThread());
if (mIPCOpen) {
Unused << SendTerminateScript();
Unused << SendTerminateScript(aTerminateGlobal);
}
}
@ -1005,9 +1018,28 @@ HangMonitoredProcess::TerminateScript()
}
ProcessHangMonitor::Get()->Dispatch(
NewNonOwningRunnableMethod("HangMonitorParent::TerminateScript",
mActor,
&HangMonitorParent::TerminateScript));
NewNonOwningRunnableMethod<bool>("HangMonitorParent::TerminateScript",
mActor,
&HangMonitorParent::TerminateScript, false));
return NS_OK;
}
NS_IMETHODIMP
HangMonitoredProcess::TerminateGlobal()
{
MOZ_RELEASE_ASSERT(NS_IsMainThread());
if (mHangData.type() != HangData::TSlowScriptData) {
return NS_ERROR_UNEXPECTED;
}
if (!mActor) {
return NS_ERROR_UNEXPECTED;
}
ProcessHangMonitor::Get()->Dispatch(
NewNonOwningRunnableMethod<bool>("HangMonitorParent::TerminateScript",
mActor,
&HangMonitorParent::TerminateScript, true));
return NS_OK;
}

View File

@ -53,7 +53,8 @@ class ProcessHangMonitor final
enum SlowScriptAction {
Continue,
Terminate,
StartDebugger
StartDebugger,
TerminateGlobal,
};
SlowScriptAction NotifySlowScript(nsITabChild* aTabChild,
const char* aFileName,

View File

@ -46,6 +46,11 @@ interface nsIHangReport : nsISupports
// Only valid for SLOW_SCRIPT reports.
void terminateScript();
// Terminate all scripts on the global that triggered the slow script
// warning.
// Only valid for SLOW_SCRIPT reports.
void terminateGlobal();
// Terminate the plugin if it is still hung.
// Only valid for PLUGIN_HANG reports.
void terminatePlugin();

View File

@ -10,6 +10,7 @@ KillScriptLocation=Script: %S
KillAddonScriptTitle=Warning: Unresponsive add-on script
# LOCALIZATION NOTE (KillAddonScriptMessage): The first %S is the name of an add-on. The second %S is the name of the application (e.g., Firefox).
KillAddonScriptMessage=A script from the add-on “%S” is running on this page, and making %S unresponsive.\n\nIt may be busy, or it may have stopped responsing permanently. You can stop the script now, or you can continue to see if it will complete.
KillAddonScriptGlobalMessage=Prevent the add-on script from running on this page until it next reloads.
StopScriptButton=Stop script
DebugScriptButton=Debug script

View File

@ -616,6 +616,28 @@ XPCJSContext::InterruptCallback(JSContext* cx)
xpc::Scriptability::Get(global).Block();
return false;
}
if (response == nsGlobalWindow::KillScriptGlobal) {
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (!IsSandbox(global) || !obs)
return false;
// Notify the extensions framework that the sandbox should be killed.
nsIXPConnect* xpc = nsContentUtils::XPConnect();
JS::RootedObject wrapper(cx, JS_NewPlainObject(cx));
nsCOMPtr<nsISupports> supports;
// Store the sandbox object on the wrappedJSObject property of the
// subject so that JS recipients can access the JS value directly.
if (!wrapper ||
!JS_DefineProperty(cx, wrapper, "wrappedJSObject", global, JSPROP_ENUMERATE) ||
NS_FAILED(xpc->WrapJS(cx, wrapper, NS_GET_IID(nsISupports), getter_AddRefs(supports)))) {
return false;
}
obs->NotifyObservers(supports, "kill-content-script-sandbox", nullptr);
return false;
}
// The user chose to continue the script. Reset the timer, and disable this
// machinery with a pref of the user opted out of future slow-script dialogs.