Bug 1356334: Part 5 - Add add-on name to slow script messages. r=billm
MozReview-Commit-ID: 2nyDmoiBKp4
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -580,16 +580,21 @@ browser.menu.showCharacterEncoding=false
# Mozilla data reporting notification (Telemetry, Firefox Health Report, etc)
dataReportingNotification.message = %1$S automatically sends some data to %2$S so that we can improve your experience.
dataReportingNotification.button.label = Choose What I Share
dataReportingNotification.button.accessKey = C
# Process hang reporter
processHang.label = A web page is slowing down your browser. What would you like to do?
+# LOCALIZATION NOTE (processHang.add-on.label): The first %S is the name of
+# an extension. The second %S is the name of the product (e.g., Firefox)
+processHang.add-on.label = A script in the extension "%S" is causing %S to slow down.
+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_wait.label = Wait
processHang.button_wait.accessKey = W
processHang.button_debug.label = Debug Script
processHang.button_debug.accessKey = D
# LOCALIZATION NOTE (fullscreenButton.tooltip): %S is the keyboard shortcut for full screen
--- a/browser/modules/ProcessHangMonitor.jsm
+++ b/browser/modules/ProcessHangMonitor.jsm
@@ -299,28 +299,53 @@ var ProcessHangMonitor = {
{
label: bundle.getString("processHang.button_wait.label"),
accessKey: bundle.getString("processHang.button_wait.accessKey"),
callback() {
ProcessHangMonitor.waitLonger(win);
}
}];
+ let message = bundle.getString("processHang.label");
+ if (report.addonId) {
+ let aps = Cc["@mozilla.org/addons/policy-service;1"].getService(Ci.nsIAddonPolicyService);
+
+ let doc = win.document;
+ let brandBundle = doc.getElementById("bundle_brand");
+
+ let addonName = aps.getExtensionName(report.addonId);
+
+ let label = bundle.getFormattedString("processHang.add-on.label",
+ [addonName, brandBundle.getString("brandShortName")]);
+
+ let linkText = bundle.getString("processHang.add-on.learn-more.text");
+ let linkURL = bundle.getString("processHang.add-on.learn-more.url");
+
+ let link = doc.createElement("label");
+ link.setAttribute("class", "text-link");
+ link.setAttribute("role", "link");
+ link.setAttribute("onclick", `openUILinkIn(${JSON.stringify(linkURL)}, "tab")`);
+ link.setAttribute("value", linkText);
+
+ message = doc.createDocumentFragment();
+ message.appendChild(doc.createTextNode(label + " "));
+ message.appendChild(link);
+ }
+
if (AppConstants.MOZ_DEV_EDITION && report.hangType == report.SLOW_SCRIPT) {
buttons.push({
label: bundle.getString("processHang.button_debug.label"),
accessKey: bundle.getString("processHang.button_debug.accessKey"),
callback() {
ProcessHangMonitor.debugScript(win);
}
});
}
- nb.appendNotification(bundle.getString("processHang.label"),
- "process-hang",
+ nb.appendNotification(message, "process-hang",
"chrome://browser/content/aboutRobots-icon.png",
nb.PRIORITY_WARNING_HIGH, buttons);
},
/**
* Ensure that no hang notifications are visible in |win|.
*/
hideNotification(win) {
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -119,16 +119,17 @@
#include "nsIScrollableFrame.h"
#include "nsView.h"
#include "nsViewManager.h"
#include "nsISelectionController.h"
#include "nsISelection.h"
#include "nsIPrompt.h"
#include "nsIPromptService.h"
#include "nsIPromptFactory.h"
+#include "nsIAddonPolicyService.h"
#include "nsIWritablePropertyBag2.h"
#include "nsIWebNavigation.h"
#include "nsIWebBrowserChrome.h"
#include "nsIWebBrowserFind.h" // For window.find()
#include "nsIWindowMediator.h" // For window.find()
#include "nsComputedDOMStyle.h"
#include "nsDOMCID.h"
#include "nsDOMWindowUtils.h"
@@ -11687,17 +11688,17 @@ nsGlobalWindow::HandleIdleActiveEvent()
NotifyIdleObserver(&idleObserver, false);
}
}
return NS_OK;
}
nsGlobalWindow::SlowScriptResponse
-nsGlobalWindow::ShowSlowScriptDialog()
+nsGlobalWindow::ShowSlowScriptDialog(const nsString& aAddonId)
{
MOZ_ASSERT(IsInnerWindow());
nsresult rv;
AutoJSContext cx;
if (Preferences::GetBool("dom.always_stop_slow_scripts")) {
return KillSlowScript;
@@ -11737,17 +11738,18 @@ nsGlobalWindow::ShowSlowScriptDialog()
if (XRE_IsContentProcess() &&
ProcessHangMonitor::Get()) {
ProcessHangMonitor::SlowScriptAction action;
RefPtr<ProcessHangMonitor> monitor = ProcessHangMonitor::Get();
nsIDocShell* docShell = GetDocShell();
nsCOMPtr<nsITabChild> child = docShell ? docShell->GetTabChild() : nullptr;
action = monitor->NotifySlowScript(child,
- filename.get());
+ filename.get(),
+ aAddonId);
if (action == ProcessHangMonitor::Terminate) {
return KillSlowScript;
}
if (action == ProcessHangMonitor::StartDebugger) {
// Spin a nested event loop so that the debugger in the parent can fetch
// any information it needs. Once the debugger has started, return to the
// script.
@@ -11777,71 +11779,67 @@ nsGlobalWindow::ShowSlowScriptDialog()
if (hasFrame) {
const char *debugCID = "@mozilla.org/dom/slow-script-debug;1";
nsCOMPtr<nsISlowScriptDebug> debugService = do_GetService(debugCID, &rv);
if (NS_SUCCEEDED(rv)) {
debugService->GetActivationHandler(getter_AddRefs(debugCallback));
}
}
- bool showDebugButton = !!debugCallback;
+ bool failed = false;
+ auto getString = [&] (const char* name,
+ nsContentUtils::PropertiesFile propFile = nsContentUtils::eDOM_PROPERTIES) {
+ nsAutoString result;
+ nsresult rv = nsContentUtils::GetLocalizedString(
+ propFile, name, result);
+
+ // GetStringFromName can return NS_OK and still give nullptr string
+ failed = failed || NS_FAILED(rv) || result.IsEmpty();
+ return Move(result);
+ };
+
+ bool isAddonScript = !aAddonId.IsEmpty();
+ bool showDebugButton = debugCallback && !isAddonScript;
// Get localizable strings
- nsAutoString 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 (showDebugButton) {
- 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;
- }
- }
-
- if (NS_FAILED(rv)) {
+
+ nsAutoString title, debugButton, msg;
+ if (isAddonScript) {
+ title = getString("KillAddonScriptTitle");
+
+ auto appName = getString("brandShortName", nsContentUtils::eBRAND_PROPERTIES);
+
+ nsCOMPtr<nsIAddonPolicyService> aps = do_GetService("@mozilla.org/addons/policy-service;1");
+ nsString addonName;
+ if (!aps || NS_FAILED(aps->GetExtensionName(aAddonId, addonName))) {
+ addonName = aAddonId;
+ }
+
+ const char16_t* params[] = {addonName.get(), appName.get()};
+ rv = nsContentUtils::FormatLocalizedString(
+ nsContentUtils::eDOM_PROPERTIES, "KillAddonScriptMessage",
+ params, msg);
+
+ failed = failed || NS_FAILED(rv);
+ } else {
+ title = getString("KillScriptTitle");
+
+ if (showDebugButton) {
+ debugButton = getString("DebugScriptButton");
+ msg = getString("KillScriptWithDebugMessage");
+ } else {
+ msg = getString("KillScriptMessage");
+ }
+ }
+
+ auto neverShowDlg = getString("DontAskAgain");
+ auto stopButton = getString("StopScriptButton");
+ auto waitButton = getString("WaitForScriptButton");
+
+ if (failed) {
NS_ERROR("Failed to get localized strings.");
return ContinueSlowScript;
}
// Append file and line number information, if available
if (filename.get()) {
nsAutoString scriptLocation;
// We want to drop the middle part of too-long locations. We'll
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -734,17 +734,17 @@ public:
}
enum SlowScriptResponse {
ContinueSlowScript = 0,
ContinueSlowScriptAndKeepNotifying,
AlwaysContinueSlowScript,
KillSlowScript
};
- SlowScriptResponse ShowSlowScriptDialog();
+ SlowScriptResponse ShowSlowScriptDialog(const nsString& aAddonId);
// Inner windows only.
void AddGamepad(uint32_t aIndex, mozilla::dom::Gamepad* aGamepad);
void RemoveGamepad(uint32_t aIndex);
void GetGamepads(nsTArray<RefPtr<mozilla::dom::Gamepad> >& aGamepads);
already_AddRefed<mozilla::dom::Gamepad> GetGamepad(uint32_t aIndex);
void SetHasSeenGamepadInput(bool aHasSeen);
bool HasSeenGamepadInput();
--- a/dom/ipc/PProcessHangMonitor.ipdl
+++ b/dom/ipc/PProcessHangMonitor.ipdl
@@ -9,16 +9,17 @@ using base::ProcessId from "base/process
using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h";
namespace mozilla {
struct SlowScriptData
{
TabId tabId;
nsCString filename;
+ nsString addonId;
};
struct PluginHangData
{
uint32_t pluginId;
ProcessId contentProcessId;
};
--- a/dom/ipc/ProcessHangMonitor.cpp
+++ b/dom/ipc/ProcessHangMonitor.cpp
@@ -80,19 +80,21 @@ class HangMonitorChild
public:
explicit HangMonitorChild(ProcessHangMonitor* aMonitor);
~HangMonitorChild() override;
void Bind(Endpoint<PProcessHangMonitorChild>&& aEndpoint);
typedef ProcessHangMonitor::SlowScriptAction SlowScriptAction;
SlowScriptAction NotifySlowScript(nsITabChild* aTabChild,
- const char* aFileName);
+ const char* aFileName,
+ const nsString& aAddonId);
void NotifySlowScriptAsync(TabId aTabId,
- const nsCString& aFileName);
+ const nsCString& aFileName,
+ const nsString& aAddonId);
bool IsDebuggerStartupComplete();
void NotifyPluginHang(uint32_t aPluginId);
void NotifyPluginHangAsync(uint32_t aPluginId);
void ClearHang();
void ClearHangAsync();
@@ -157,16 +159,17 @@ public:
HangMonitoredProcess(HangMonitorParent* aActor,
ContentParent* aContentParent)
: mActor(aActor), mContentParent(aContentParent) {}
NS_IMETHOD GetHangType(uint32_t* aHangType) override;
NS_IMETHOD GetScriptBrowser(nsIDOMElement** aBrowser) override;
NS_IMETHOD GetScriptFileName(nsACString& aFileName) override;
+ NS_IMETHOD GetAddonId(nsAString& aAddonId) override;
NS_IMETHOD GetPluginName(nsACString& aPluginName) override;
NS_IMETHOD TerminateScript() override;
NS_IMETHOD BeginStartingDebugger() override;
NS_IMETHOD EndStartingDebugger() override;
NS_IMETHOD TerminatePlugin() override;
NS_IMETHOD UserCanceled() override;
@@ -447,26 +450,28 @@ HangMonitorChild::Bind(Endpoint<PProcess
sInstance = this;
DebugOnly<bool> ok = aEndpoint.Bind(this);
MOZ_ASSERT(ok);
}
void
HangMonitorChild::NotifySlowScriptAsync(TabId aTabId,
- const nsCString& aFileName)
+ const nsCString& aFileName,
+ const nsString& aAddonId)
{
if (mIPCOpen) {
- Unused << SendHangEvidence(SlowScriptData(aTabId, aFileName));
+ Unused << SendHangEvidence(SlowScriptData(aTabId, aFileName, aAddonId));
}
}
HangMonitorChild::SlowScriptAction
HangMonitorChild::NotifySlowScript(nsITabChild* aTabChild,
- const char* aFileName)
+ const char* aFileName,
+ const nsString& aAddonId)
{
MOZ_RELEASE_ASSERT(NS_IsMainThread());
mSentReport = true;
{
MonitorAutoLock lock(mMonitor);
@@ -483,22 +488,22 @@ HangMonitorChild::NotifySlowScript(nsITa
TabId id;
if (aTabChild) {
RefPtr<TabChild> tabChild = static_cast<TabChild*>(aTabChild);
id = tabChild->GetTabId();
}
nsAutoCString filename(aFileName);
- Dispatch(NewNonOwningRunnableMethod<TabId, nsCString>(
+ Dispatch(NewNonOwningRunnableMethod<TabId, nsCString, nsString>(
"HangMonitorChild::NotifySlowScriptAsync",
this,
&HangMonitorChild::NotifySlowScriptAsync,
id,
- filename));
+ filename, aAddonId));
return SlowScriptAction::Continue;
}
bool
HangMonitorChild::IsDebuggerStartupComplete()
{
MOZ_RELEASE_ASSERT(NS_IsMainThread());
@@ -951,16 +956,28 @@ HangMonitoredProcess::GetScriptFileName(
return NS_ERROR_NOT_AVAILABLE;
}
aFileName = mHangData.get_SlowScriptData().filename();
return NS_OK;
}
NS_IMETHODIMP
+HangMonitoredProcess::GetAddonId(nsAString& aAddonId)
+{
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ if (mHangData.type() != HangData::TSlowScriptData) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ aAddonId = mHangData.get_SlowScriptData().addonId();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
HangMonitoredProcess::GetPluginName(nsACString& aPluginName)
{
MOZ_RELEASE_ASSERT(NS_IsMainThread());
if (mHangData.type() != HangData::TPluginHangData) {
return NS_ERROR_NOT_AVAILABLE;
}
uint32_t id = mHangData.get_PluginHangData().pluginId();
@@ -1160,20 +1177,21 @@ ProcessHangMonitor::Observe(nsISupports*
obs->RemoveObserver(this, "xpcom-shutdown");
}
}
return NS_OK;
}
ProcessHangMonitor::SlowScriptAction
ProcessHangMonitor::NotifySlowScript(nsITabChild* aTabChild,
- const char* aFileName)
+ const char* aFileName,
+ const nsString& aAddonId)
{
MOZ_RELEASE_ASSERT(NS_IsMainThread());
- return HangMonitorChild::Get()->NotifySlowScript(aTabChild, aFileName);
+ return HangMonitorChild::Get()->NotifySlowScript(aTabChild, aFileName, aAddonId);
}
bool
ProcessHangMonitor::IsDebuggerStartupComplete()
{
MOZ_RELEASE_ASSERT(NS_IsMainThread());
return HangMonitorChild::Get()->IsDebuggerStartupComplete();
}
--- a/dom/ipc/ProcessHangMonitor.h
+++ b/dom/ipc/ProcessHangMonitor.h
@@ -10,16 +10,17 @@
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/Atomics.h"
#include "nsCOMPtr.h"
#include "nsIObserver.h"
class nsIRunnable;
class nsITabChild;
class nsIThread;
+class nsString;
namespace mozilla {
namespace dom {
class ContentParent;
class TabParent;
} // namespace dom
@@ -50,17 +51,18 @@ class ProcessHangMonitor final
static void ClearForcePaint();
enum SlowScriptAction {
Continue,
Terminate,
StartDebugger
};
SlowScriptAction NotifySlowScript(nsITabChild* aTabChild,
- const char* aFileName);
+ const char* aFileName,
+ const nsString& aAddonId);
void NotifyPluginHang(uint32_t aPluginId);
bool IsDebuggerStartupComplete();
void InitiateCPOWTimeout();
bool ShouldTimeOutCPOWs();
--- a/dom/ipc/nsIHangReport.idl
+++ b/dom/ipc/nsIHangReport.idl
@@ -27,16 +27,17 @@ interface nsIHangReport : nsISupports
// The type of hang being reported: SLOW_SCRIPT or PLUGIN_HANG.
readonly attribute unsigned long hangType;
// For SLOW_SCRIPT reports, these fields contain information about the
// slow script.
// Only valid for SLOW_SCRIPT reports.
readonly attribute nsIDOMElement scriptBrowser;
readonly attribute ACString scriptFileName;
+ readonly attribute AString addonId;
// For PLUGIN_HANGs, this field contains information about the plugin.
// Only valid for PLUGIN_HANG reports.
readonly attribute ACString pluginName;
// Called by front end code when user ignores or cancels
// the notification.
void userCanceled();
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -1,16 +1,21 @@
# 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/.
KillScriptTitle=Warning: Unresponsive script
KillScriptMessage=A script on this page may be busy, or it may have stopped responding. You can stop the script now, or you can continue to see if the script will complete.
KillScriptWithDebugMessage=A script on this page may be busy, or it may have stopped responding. You can stop the script now, open the script in the debugger, or let the script continue.
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.
+
StopScriptButton=Stop script
DebugScriptButton=Debug script
WaitForScriptButton=Continue
DontAskAgain=&Don’t ask me again
JSURLLoadBlockedWarning=Attempt to load a javascript: URL from one host\nin a window displaying content from another host\nwas blocked by the security manager.
WindowCloseBlockedWarning=Scripts may not close windows that were not opened by script.
OnBeforeUnloadTitle=Are you sure?
OnBeforeUnloadMessage=This page is asking you to confirm that you want to leave - data you have entered may not be saved.
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -605,17 +605,17 @@ XPCJSContext::InterruptCallback(JSContex
// Accumulate slow script invokation delay.
if (!chrome && !self->mTimeoutAccumulated) {
uint32_t delay = uint32_t(self->mSlowScriptActualWait.ToMilliseconds() - (limit * 1000.0));
Telemetry::Accumulate(Telemetry::SLOW_SCRIPT_NOTIFY_DELAY, delay);
self->mTimeoutAccumulated = true;
}
// Show the prompt to the user, and kill if requested.
- nsGlobalWindow::SlowScriptResponse response = win->ShowSlowScriptDialog();
+ nsGlobalWindow::SlowScriptResponse response = win->ShowSlowScriptDialog(addonId);
if (response == nsGlobalWindow::KillSlowScript) {
if (Preferences::GetBool("dom.global_stop_script", true))
xpc::Scriptability::Get(global).Block();
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.