mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-01-10 05:47:04 +00:00
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:
parent
2c6fe4f014
commit
bdbbf869dd
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -737,7 +737,8 @@ public:
|
||||
ContinueSlowScript = 0,
|
||||
ContinueSlowScriptAndKeepNotifying,
|
||||
AlwaysContinueSlowScript,
|
||||
KillSlowScript
|
||||
KillSlowScript,
|
||||
KillScriptGlobal
|
||||
};
|
||||
SlowScriptResponse ShowSlowScriptDialog(const nsString& aAddonId);
|
||||
|
||||
|
@ -36,7 +36,7 @@ parent:
|
||||
async ClearHang();
|
||||
|
||||
child:
|
||||
async TerminateScript();
|
||||
async TerminateScript(bool aTerminateGlobal);
|
||||
|
||||
async BeginStartingDebugger();
|
||||
async EndStartingDebugger();
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,8 @@ class ProcessHangMonitor final
|
||||
enum SlowScriptAction {
|
||||
Continue,
|
||||
Terminate,
|
||||
StartDebugger
|
||||
StartDebugger,
|
||||
TerminateGlobal,
|
||||
};
|
||||
SlowScriptAction NotifySlowScript(nsITabChild* aTabChild,
|
||||
const char* aFileName,
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user