mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-16 14:55:47 +00:00
Bug 901364 - Hoist slow script prompt machinery into nsGlobalWindow. r=mrbkap
This commit is contained in:
parent
8bc140769d
commit
3b168e2acb
@ -239,6 +239,10 @@
|
||||
#include "mozilla/dom/SpeechSynthesis.h"
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_JSDEBUGGER
|
||||
#include "jsdIDebuggerService.h"
|
||||
#endif
|
||||
|
||||
// Apple system headers seem to have a check() macro. <sigh>
|
||||
#ifdef check
|
||||
#undef check
|
||||
@ -9374,6 +9378,164 @@ nsGlobalWindow::HandleIdleActiveEvent()
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsGlobalWindow::SlowScriptResponse
|
||||
nsGlobalWindow::ShowSlowScriptDialog()
|
||||
{
|
||||
nsresult rv;
|
||||
AutoJSContext cx;
|
||||
|
||||
// If it isn't safe to run script, then it isn't safe to bring up the prompt
|
||||
// (since that spins the event loop). In that (rare) case, we just kill the
|
||||
// script and report a warning.
|
||||
if (!nsContentUtils::IsSafeToRunScript()) {
|
||||
JS_ReportWarning(cx, "A long running script was terminated");
|
||||
return KillSlowScript;
|
||||
}
|
||||
|
||||
// Get the nsIPrompt interface from the docshell
|
||||
nsCOMPtr<nsIDocShell> ds = GetDocShell();
|
||||
NS_ENSURE_TRUE(ds, KillSlowScript);
|
||||
nsCOMPtr<nsIPrompt> prompt = do_GetInterface(ds);
|
||||
NS_ENSURE_TRUE(prompt, KillSlowScript);
|
||||
|
||||
// Check if we should offer the option to debug
|
||||
JS::RootedScript script(cx);
|
||||
unsigned lineno;
|
||||
bool hasFrame = JS_DescribeScriptedCaller(cx, script.address(), &lineno);
|
||||
|
||||
bool debugPossible = hasFrame && js::CanCallContextDebugHandler(cx);
|
||||
#ifdef MOZ_JSDEBUGGER
|
||||
// Get the debugger service if necessary.
|
||||
if (debugPossible) {
|
||||
bool jsds_IsOn = false;
|
||||
const char jsdServiceCtrID[] = "@mozilla.org/js/jsd/debugger-service;1";
|
||||
nsCOMPtr<jsdIExecutionHook> jsdHook;
|
||||
nsCOMPtr<jsdIDebuggerService> jsds = do_GetService(jsdServiceCtrID, &rv);
|
||||
|
||||
// Check if there's a user for the debugger service that's 'on' for us
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
jsds->GetDebuggerHook(getter_AddRefs(jsdHook));
|
||||
jsds->GetIsOn(&jsds_IsOn);
|
||||
}
|
||||
|
||||
// If there is a debug handler registered for this runtime AND
|
||||
// ((jsd is on AND has a hook) OR (jsd isn't on (something else debugs)))
|
||||
// then something useful will be done with our request to debug.
|
||||
debugPossible = ((jsds_IsOn && (jsdHook != nullptr)) || !jsds_IsOn);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Get localizable strings
|
||||
nsXPIDLString title, msg, stopButton, waitButton, debugButton, neverShowDlg;
|
||||
|
||||
rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
|
||||
"KillScriptTitle",
|
||||
title);
|
||||
|
||||
nsresult tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
|
||||
"StopScriptButton",
|
||||
stopButton);
|
||||
if (NS_FAILED(tmp)) {
|
||||
rv = tmp;
|
||||
}
|
||||
|
||||
tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
|
||||
"WaitForScriptButton",
|
||||
waitButton);
|
||||
if (NS_FAILED(tmp)) {
|
||||
rv = tmp;
|
||||
}
|
||||
|
||||
tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
|
||||
"DontAskAgain",
|
||||
neverShowDlg);
|
||||
if (NS_FAILED(tmp)) {
|
||||
rv = tmp;
|
||||
}
|
||||
|
||||
|
||||
if (debugPossible) {
|
||||
tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
|
||||
"DebugScriptButton",
|
||||
debugButton);
|
||||
if (NS_FAILED(tmp)) {
|
||||
rv = tmp;
|
||||
}
|
||||
|
||||
tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
|
||||
"KillScriptWithDebugMessage",
|
||||
msg);
|
||||
if (NS_FAILED(tmp)) {
|
||||
rv = tmp;
|
||||
}
|
||||
}
|
||||
else {
|
||||
tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
|
||||
"KillScriptMessage",
|
||||
msg);
|
||||
if (NS_FAILED(tmp)) {
|
||||
rv = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
// GetStringFromName can return NS_OK and still give NULL string
|
||||
if (NS_FAILED(rv) || !title || !msg || !stopButton || !waitButton ||
|
||||
(!debugButton && debugPossible) || !neverShowDlg) {
|
||||
NS_ERROR("Failed to get localized strings.");
|
||||
return ContinueSlowScript;
|
||||
}
|
||||
|
||||
// Append file and line number information, if available
|
||||
if (script) {
|
||||
const char *filename = JS_GetScriptFilename(cx, script);
|
||||
if (filename) {
|
||||
nsXPIDLString scriptLocation;
|
||||
NS_ConvertUTF8toUTF16 filenameUTF16(filename);
|
||||
const PRUnichar *formatParams[] = { filenameUTF16.get() };
|
||||
rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
|
||||
"KillScriptLocation",
|
||||
formatParams,
|
||||
scriptLocation);
|
||||
|
||||
if (NS_SUCCEEDED(rv) && scriptLocation) {
|
||||
msg.AppendLiteral("\n\n");
|
||||
msg.Append(scriptLocation);
|
||||
msg.Append(':');
|
||||
msg.AppendInt(lineno);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
// Add a third button if necessary.
|
||||
if (debugPossible)
|
||||
buttonFlags += nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_2;
|
||||
|
||||
// Null out the operation callback while we're re-entering JS here.
|
||||
JSOperationCallback old = JS_SetOperationCallback(cx, nullptr);
|
||||
|
||||
// Open the dialog.
|
||||
rv = prompt->ConfirmEx(title, msg, buttonFlags, waitButton, stopButton,
|
||||
debugButton, neverShowDlg, &neverShowDlgChk,
|
||||
&buttonPressed);
|
||||
|
||||
JS_SetOperationCallback(cx, old);
|
||||
|
||||
if (NS_SUCCEEDED(rv) && (buttonPressed == 0)) {
|
||||
return neverShowDlgChk ? AlwaysContinueSlowScript : ContinueSlowScript;
|
||||
}
|
||||
if ((buttonPressed == 2) && debugPossible) {
|
||||
return js_CallContextDebugHandler(cx) ? ContinueSlowScript : KillSlowScript;
|
||||
}
|
||||
JS_ClearPendingException(cx);
|
||||
return KillSlowScript;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
nsGlobalWindow::FindInsertionIndex(IdleObserverHolder* aIdleObserver)
|
||||
{
|
||||
|
@ -692,6 +692,13 @@ public:
|
||||
mAllowScriptsToClose = true;
|
||||
}
|
||||
|
||||
enum SlowScriptResponse {
|
||||
ContinueSlowScript = 0,
|
||||
AlwaysContinueSlowScript,
|
||||
KillSlowScript
|
||||
};
|
||||
SlowScriptResponse ShowSlowScriptDialog();
|
||||
|
||||
#ifdef MOZ_GAMEPAD
|
||||
void AddGamepad(uint32_t aIndex, mozilla::dom::Gamepad* aGamepad);
|
||||
void RemoveGamepad(uint32_t aIndex);
|
||||
|
@ -679,25 +679,9 @@ DumpString(const nsAString &str)
|
||||
}
|
||||
#endif
|
||||
|
||||
static already_AddRefed<nsIPrompt>
|
||||
GetPromptFromContext(nsJSContext* ctx)
|
||||
{
|
||||
nsCOMPtr<nsPIDOMWindow> win(do_QueryInterface(ctx->GetGlobalObject()));
|
||||
NS_ENSURE_TRUE(win, nullptr);
|
||||
|
||||
nsIDocShell *docShell = win->GetDocShell();
|
||||
NS_ENSURE_TRUE(docShell, nullptr);
|
||||
|
||||
// Get the nsIPrompt interface from the docshell
|
||||
nsCOMPtr<nsIPrompt> prompt = do_GetInterface(docShell);
|
||||
return prompt.forget();
|
||||
}
|
||||
|
||||
bool
|
||||
nsJSContext::DOMOperationCallback(JSContext *cx)
|
||||
{
|
||||
nsresult rv;
|
||||
|
||||
// Get the native context
|
||||
nsJSContext *ctx = static_cast<nsJSContext *>(::JS_GetContextPrivate(cx));
|
||||
|
||||
@ -743,172 +727,25 @@ nsJSContext::DOMOperationCallback(JSContext *cx)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!nsContentUtils::IsSafeToRunScript()) {
|
||||
// If it isn't safe to run script, then it isn't safe to bring up the
|
||||
// prompt (since that will cause the event loop to spin). In this case
|
||||
// (which is rare), we just stop the script... But report a warning so
|
||||
// that developers have some idea of what went wrong.
|
||||
nsCOMPtr<nsPIDOMWindow> domWin = do_QueryInterface(ctx->GetGlobalObject());
|
||||
NS_ENSURE_TRUE(domWin, false);
|
||||
nsGlobalWindow::SlowScriptResponse response =
|
||||
static_cast<nsGlobalWindow*>(domWin.get())->ShowSlowScriptDialog();
|
||||
|
||||
JS_ReportWarning(cx, "A long running script was terminated");
|
||||
if (response == nsGlobalWindow::KillSlowScript) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we get here we're most likely executing an infinite loop in JS,
|
||||
// we'll tell the user about this and we'll give the user the option
|
||||
// of stopping the execution of the script.
|
||||
nsCOMPtr<nsIPrompt> prompt = GetPromptFromContext(ctx);
|
||||
NS_ENSURE_TRUE(prompt, false);
|
||||
|
||||
// Check if we should offer the option to debug
|
||||
JS::RootedScript script(cx);
|
||||
unsigned lineno;
|
||||
bool hasFrame = ::JS_DescribeScriptedCaller(cx, script.address(), &lineno);
|
||||
|
||||
bool debugPossible = hasFrame && js::CanCallContextDebugHandler(cx);
|
||||
#ifdef MOZ_JSDEBUGGER
|
||||
// Get the debugger service if necessary.
|
||||
if (debugPossible) {
|
||||
bool jsds_IsOn = false;
|
||||
const char jsdServiceCtrID[] = "@mozilla.org/js/jsd/debugger-service;1";
|
||||
nsCOMPtr<jsdIExecutionHook> jsdHook;
|
||||
nsCOMPtr<jsdIDebuggerService> jsds = do_GetService(jsdServiceCtrID, &rv);
|
||||
|
||||
// Check if there's a user for the debugger service that's 'on' for us
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
jsds->GetDebuggerHook(getter_AddRefs(jsdHook));
|
||||
jsds->GetIsOn(&jsds_IsOn);
|
||||
}
|
||||
|
||||
// If there is a debug handler registered for this runtime AND
|
||||
// ((jsd is on AND has a hook) OR (jsd isn't on (something else debugs)))
|
||||
// then something useful will be done with our request to debug.
|
||||
debugPossible = ((jsds_IsOn && (jsdHook != nullptr)) || !jsds_IsOn);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Get localizable strings
|
||||
nsXPIDLString title, msg, stopButton, waitButton, debugButton, neverShowDlg;
|
||||
|
||||
rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
|
||||
"KillScriptTitle",
|
||||
title);
|
||||
|
||||
nsresult tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
|
||||
"StopScriptButton",
|
||||
stopButton);
|
||||
if (NS_FAILED(tmp)) {
|
||||
rv = tmp;
|
||||
}
|
||||
|
||||
tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
|
||||
"WaitForScriptButton",
|
||||
waitButton);
|
||||
if (NS_FAILED(tmp)) {
|
||||
rv = tmp;
|
||||
}
|
||||
|
||||
tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
|
||||
"DontAskAgain",
|
||||
neverShowDlg);
|
||||
if (NS_FAILED(tmp)) {
|
||||
rv = tmp;
|
||||
}
|
||||
|
||||
|
||||
if (debugPossible) {
|
||||
tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
|
||||
"DebugScriptButton",
|
||||
debugButton);
|
||||
if (NS_FAILED(tmp)) {
|
||||
rv = tmp;
|
||||
}
|
||||
|
||||
tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
|
||||
"KillScriptWithDebugMessage",
|
||||
msg);
|
||||
if (NS_FAILED(tmp)) {
|
||||
rv = tmp;
|
||||
ctx->mOperationCallbackTime = PR_Now();
|
||||
if (response == nsGlobalWindow::AlwaysContinueSlowScript) {
|
||||
if (isTrackingChromeCodeTime) {
|
||||
Preferences::SetInt("dom.max_chrome_script_run_time", 0);
|
||||
sMaxChromeScriptRunTime = NS_UNLIMITED_SCRIPT_RUNTIME;
|
||||
} else {
|
||||
Preferences::SetInt("dom.max_script_run_time", 0);
|
||||
sMaxScriptRunTime = NS_UNLIMITED_SCRIPT_RUNTIME;
|
||||
}
|
||||
}
|
||||
else {
|
||||
tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
|
||||
"KillScriptMessage",
|
||||
msg);
|
||||
if (NS_FAILED(tmp)) {
|
||||
rv = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
//GetStringFromName can return NS_OK and still give NULL string
|
||||
if (NS_FAILED(rv) || !title || !msg || !stopButton || !waitButton ||
|
||||
(!debugButton && debugPossible) || !neverShowDlg) {
|
||||
NS_ERROR("Failed to get localized strings.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Append file and line number information, if available
|
||||
if (script) {
|
||||
const char *filename = ::JS_GetScriptFilename(cx, script);
|
||||
if (filename) {
|
||||
nsXPIDLString scriptLocation;
|
||||
NS_ConvertUTF8toUTF16 filenameUTF16(filename);
|
||||
const PRUnichar *formatParams[] = { filenameUTF16.get() };
|
||||
rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
|
||||
"KillScriptLocation",
|
||||
formatParams,
|
||||
scriptLocation);
|
||||
|
||||
if (NS_SUCCEEDED(rv) && scriptLocation) {
|
||||
msg.AppendLiteral("\n\n");
|
||||
msg.Append(scriptLocation);
|
||||
msg.Append(':');
|
||||
msg.AppendInt(lineno);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32_t buttonPressed = 0; //In case 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));
|
||||
|
||||
// Add a third button if necessary:
|
||||
if (debugPossible)
|
||||
buttonFlags += nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_2;
|
||||
|
||||
// Null out the operation callback while we're re-entering JS here.
|
||||
::JS_SetOperationCallback(cx, nullptr);
|
||||
|
||||
// Open the dialog.
|
||||
rv = prompt->ConfirmEx(title, msg, buttonFlags, waitButton, stopButton,
|
||||
debugButton, neverShowDlg, &neverShowDlgChk,
|
||||
&buttonPressed);
|
||||
|
||||
::JS_SetOperationCallback(cx, DOMOperationCallback);
|
||||
|
||||
if (NS_SUCCEEDED(rv) && (buttonPressed == 0)) {
|
||||
// Allow the script to continue running
|
||||
|
||||
if (neverShowDlgChk) {
|
||||
if (isTrackingChromeCodeTime) {
|
||||
Preferences::SetInt("dom.max_chrome_script_run_time", 0);
|
||||
sMaxChromeScriptRunTime = NS_UNLIMITED_SCRIPT_RUNTIME;
|
||||
} else {
|
||||
Preferences::SetInt("dom.max_script_run_time", 0);
|
||||
sMaxScriptRunTime = NS_UNLIMITED_SCRIPT_RUNTIME;
|
||||
}
|
||||
}
|
||||
|
||||
ctx->mOperationCallbackTime = PR_Now();
|
||||
return true;
|
||||
}
|
||||
else if ((buttonPressed == 2) && debugPossible) {
|
||||
return js_CallContextDebugHandler(cx);
|
||||
}
|
||||
|
||||
JS_ClearPendingException(cx);
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
|
Loading…
Reference in New Issue
Block a user