Bug 1110887 - Make the plugin crash reporter work with e10s. r=felipe.

This patch does the following:
* Has the gPluginHandler tell content processes when a NPAPI plugin crashes
* Introduces PluginCrashReporter, and renames TabCrashReporter.jsm to ContentCrashReporters.jsm.
* Makes gPluginHandler use PluginCrashReporter to submit plugin crashes.
* If a plugin crash report is submitted, puts all visible instances into the submitted state.
* Makes the plugin crashed notification bar work with run IDs.
* Removes event and message listeners in PluginContent when uninitting.
This commit is contained in:
Mike Conley 2015-03-27 23:34:15 -04:00
parent 0c226a877b
commit 8ce112b8d8
7 changed files with 541 additions and 299 deletions

View File

@ -13,7 +13,8 @@ var gPluginHandler = {
"PluginContent:HideNotificationBar",
"PluginContent:ShowInstallNotification",
"PluginContent:InstallSinglePlugin",
"PluginContent:ShowPluginCrashedNotification",
"PluginContent:ShowNPAPIPluginCrashedNotification",
"PluginContent:ShowGMPCrashedNotification",
"PluginContent:SubmitReport",
"PluginContent:LinkClickCallback",
],
@ -61,12 +62,20 @@ var gPluginHandler = {
case "PluginContent:InstallSinglePlugin":
this.installSinglePlugin(msg.data.pluginInfo);
break;
case "PluginContent:ShowPluginCrashedNotification":
this.showPluginCrashedNotification(msg.target, msg.data.messageString,
msg.data.pluginDumpID, msg.data.browserDumpID);
case "PluginContent:ShowNPAPIPluginCrashedNotification":
this.showNPAPIPluginCrashedNotification(msg.target, msg.data.message,
msg.data.runID);
break;
case "PluginContent:ShowGMPCrashedNotification":
this.showGMPCrashedNotification(msg.target,
msg.data.messageString,
msg.data.pluginDumpID,
msg.data.browserDumpID);
break;
case "PluginContent:SubmitReport":
this.submitReport(msg.data.pluginDumpID, msg.data.browserDumpID, msg.data.keyVals);
if (AppConstants.MOZ_CRASHREPORTER) {
this.submitReport(msg.data.runID, msg.data.keyVals, msg.data.submitURLOptIn);
}
break;
case "PluginContent:LinkClickCallback":
switch (msg.data.name) {
@ -102,15 +111,13 @@ var gPluginHandler = {
openUILinkIn(Services.urlFormatter.formatURLPref("plugins.update.url"), "tab");
},
#ifdef MOZ_CRASHREPORTER
submitReport: function submitReport(pluginDumpID, browserDumpID, keyVals) {
keyVals = keyVals || {};
this.CrashSubmit.submit(pluginDumpID, { recordSubmission: true,
extraExtraKeyVals: keyVals });
if (browserDumpID)
this.CrashSubmit.submit(browserDumpID);
submitReport: function submitReport(runID, keyVals, submitURLOptIn) {
if (!AppConstants.MOZ_CRASHREPORTER) {
return;
}
Services.prefs.setBoolPref("dom.ipc.plugins.reportCrashURL", submitURLOptIn);
PluginCrashReporter.submitCrashReport(runID, keyVals);
},
#endif
// Callback for user clicking a "reload page" link
reloadPage: function (browser) {
@ -455,28 +462,86 @@ var gPluginHandler = {
// Crashed-plugin observer. Notified once per plugin crash, before events
// are dispatched to individual plugin instances.
pluginCrashed : function(subject, topic, data) {
NPAPIPluginCrashed : function(subject, topic, data) {
let propertyBag = subject;
if (!(propertyBag instanceof Ci.nsIPropertyBag2) ||
!(propertyBag instanceof Ci.nsIWritablePropertyBag2))
return;
#ifdef MOZ_CRASHREPORTER
let pluginDumpID = propertyBag.getPropertyAsAString("pluginDumpID");
let browserDumpID= propertyBag.getPropertyAsAString("browserDumpID");
let shouldSubmit = gCrashReporter.submitReports;
let doPrompt = true; // XXX followup to get via gCrashReporter
// Submit automatically when appropriate.
if (pluginDumpID && shouldSubmit && !doPrompt) {
this.submitReport(pluginDumpID, browserDumpID);
// Submission is async, so we can't easily show failure UI.
propertyBag.setPropertyAsBool("submittedCrashReport", true);
!(propertyBag instanceof Ci.nsIWritablePropertyBag2) ||
!propertyBag.hasKey("runID") ||
!propertyBag.hasKey("pluginName")) {
Cu.reportError("A NPAPI plugin crashed, but the properties of this plugin " +
"cannot be read.");
return;
}
#endif
let runID = propertyBag.getPropertyAsUint32("runID");
let uglyPluginName = propertyBag.getPropertyAsAString("pluginName");
let pluginName = BrowserUtils.makeNicePluginName(uglyPluginName);
let pluginDumpID = propertyBag.getPropertyAsAString("pluginDumpID");
// If we don't have a minidumpID, we can't (or didn't) submit anything.
// This can happen if the plugin is killed from the task manager.
let state;
if (!AppConstants.MOZ_CRASHREPORTER || !gCrashReporter.enabled) {
// This state tells the user that crash reporting is disabled, so we
// cannot send a report.
state = "noSubmit";
} else if (!pluginDumpID) {
// This state tells the user that there is no crash report available.
state = "noReport";
} else {
// This state asks the user to submit a crash report.
state = "please";
}
let mm = window.getGroupMessageManager("browsers");
mm.broadcastAsyncMessage("BrowserPlugins:NPAPIPluginProcessCrashed",
{ pluginName, runID, state });
},
showPluginCrashedNotification: function (browser, messageString, pluginDumpID, browserDumpID) {
showNPAPIPluginCrashedNotification: function (browser, messageString, runID) {
let crashReportCallback;
if (AppConstants.MOZ_CRASHREPORTER &&
PluginCrashReporter.hasCrashReport(runID)) {
crashReportCallback = () => {
PluginCrashReporter.submitGMPCrashReport(pluginDumpID, browserDumpID);
};
}
this._showPluginCrashedNotification(browser, messageString, crashReportCallback);
},
/**
* For now, GMP crashes are handled separately from NPAPI plugin crashes,
* since the latter are not yet working for e10s. These will be unified
* once e10s support is added for GMP crash reporting in bug 1146955.
*/
showGMPCrashedNotification: function (browser, messageString,
pluginDumpID, browserDumpID) {
let crashReportCallback;
if (AppConstants.MOZ_CRASHREPORTER && pluginDumpID) {
crashReportCallback = () => {
PluginCrashReporter.submitGMPCrashReport(pluginDumpID, browserDumpID);
};
}
this._showPluginCrashedNotification(browser, messageString, crashReportCallback);
},
/**
* A helper function for showing the plugin crashed notification bar.
*
* @param browser
* The browser that contains the crashing plugin.
* @param messageString
* The message to display in the notification.
* @param crashReportCallback
* Optional. Pass a function to submit a crash report for this plugin
* crash if a report exists. If no function is passed, the Submit Report
* button will not be added.
*/
_showPluginCrashedNotification: function (browser, messageString, crashReportCallback) {
// If there's already an existing notification bar, don't do anything.
let notificationBox = gBrowser.getNotificationBox(browser);
let notification = notificationBox.getNotificationWithValue("plugin-crashed");
@ -498,16 +563,16 @@ var gPluginHandler = {
callback: function() { browser.reload(); },
}];
#ifdef MOZ_CRASHREPORTER
let submitButton = {
label: submitLabel,
accessKey: submitKey,
popup: null,
callback: function() { gPluginHandler.submitReport(pluginDumpID, browserDumpID); },
};
if (pluginDumpID)
if (AppConstants.MOZ_CRASHREPORTER && crashReportCallback) {
let submitButton = {
label: submitLabel,
accessKey: submitKey,
popup: null,
callback: crashReportCallback,
};
buttons.push(submitButton);
#endif
}
notification = notificationBox.appendNotification(messageString, "plugin-crashed",
iconURL, priority, buttons);

View File

@ -39,6 +39,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "AboutHome",
"resource:///modules/AboutHome.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Log",
"resource://gre/modules/Log.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
"resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "Favicons",
"@mozilla.org/browser/favicon-service;1",
"mozIAsyncFavicons");
@ -195,7 +197,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "gWebRTCUI",
#ifdef MOZ_CRASHREPORTER
XPCOMUtils.defineLazyModuleGetter(this, "TabCrashReporter",
"resource:///modules/TabCrashReporter.jsm");
"resource:///modules/ContentCrashReporters.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PluginCrashReporter",
"resource:///modules/ContentCrashReporters.jsm");
#endif
XPCOMUtils.defineLazyModuleGetter(this, "FormValidationHandler",
@ -925,7 +929,7 @@ var gBrowserInit = {
onLoad: function() {
gBrowser.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver, false);
Services.obs.addObserver(gPluginHandler.pluginCrashed, "plugin-crashed", false);
Services.obs.addObserver(gPluginHandler.NPAPIPluginCrashed, "plugin-crashed", false);
window.addEventListener("AppCommand", HandleAppCommandEvent, true);
@ -1243,11 +1247,6 @@ var gBrowserInit = {
PanelUI.init();
LightweightThemeListener.init();
#ifdef MOZ_CRASHREPORTER
if (gMultiProcessBrowser)
TabCrashReporter.init();
#endif
Services.telemetry.getHistogramById("E10S_WINDOW").add(gMultiProcessBrowser);
SidebarUI.startDelayedLoad();
@ -1490,7 +1489,7 @@ var gBrowserInit = {
gFxAccounts.uninit();
#endif
Services.obs.removeObserver(gPluginHandler.pluginCrashed, "plugin-crashed");
Services.obs.removeObserver(gPluginHandler.NPAPIPluginCrashed, "plugin-crashed");
try {
gBrowser.removeProgressListener(window.XULBrowserWindow);

View File

@ -122,6 +122,13 @@ XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
"resource://gre/modules/UpdateChannel.jsm");
#endif
#ifdef MOZ_CRASHREPORTER
XPCOMUtils.defineLazyModuleGetter(this, "TabCrashReporter",
"resource:///modules/ContentCrashReporters.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PluginCrashReporter",
"resource:///modules/ContentCrashReporters.jsm");
#endif
XPCOMUtils.defineLazyGetter(this, "ShellService", function() {
try {
return Cc["@mozilla.org/browser/shell-service;1"].
@ -733,6 +740,11 @@ BrowserGlue.prototype = {
});
#endif
#ifdef MOZ_CRASHREPORTER
TabCrashReporter.init();
PluginCrashReporter.init();
#endif
Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
AddonWatcher.init(this._notifySlowAddon);

View File

@ -0,0 +1,207 @@
/* 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/. */
"use strict";
let Cc = Components.classes;
let Ci = Components.interfaces;
let Cu = Components.utils;
this.EXPORTED_SYMBOLS = [ "TabCrashReporter", "PluginCrashReporter" ];
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CrashSubmit",
"resource://gre/modules/CrashSubmit.jsm");
this.TabCrashReporter = {
init: function () {
if (this.initialized)
return;
this.initialized = true;
Services.obs.addObserver(this, "ipc:content-shutdown", false);
Services.obs.addObserver(this, "oop-frameloader-crashed", false);
this.childMap = new Map();
this.browserMap = new WeakMap();
},
observe: function (aSubject, aTopic, aData) {
switch (aTopic) {
case "ipc:content-shutdown":
aSubject.QueryInterface(Ci.nsIPropertyBag2);
if (!aSubject.get("abnormal"))
return;
this.childMap.set(aSubject.get("childID"), aSubject.get("dumpID"));
break;
case "oop-frameloader-crashed":
aSubject.QueryInterface(Ci.nsIFrameLoader);
let browser = aSubject.ownerElement;
if (!browser)
return;
this.browserMap.set(browser, aSubject.childID);
break;
}
},
submitCrashReport: function (aBrowser) {
let childID = this.browserMap.get(aBrowser);
let dumpID = this.childMap.get(childID);
if (!dumpID)
return
if (CrashSubmit.submit(dumpID, { recordSubmission: true })) {
this.childMap.set(childID, null); // Avoid resubmission.
this.removeSubmitCheckboxesForSameCrash(childID);
}
},
removeSubmitCheckboxesForSameCrash: function(childID) {
let enumerator = Services.wm.getEnumerator("navigator:browser");
while (enumerator.hasMoreElements()) {
let window = enumerator.getNext();
if (!window.gMultiProcessBrowser)
continue;
for (let browser of window.gBrowser.browsers) {
if (browser.isRemoteBrowser)
continue;
let doc = browser.contentDocument;
if (!doc.documentURI.startsWith("about:tabcrashed"))
continue;
if (this.browserMap.get(browser) == childID) {
this.browserMap.delete(browser);
browser.contentDocument.documentElement.classList.remove("crashDumpAvailable");
browser.contentDocument.documentElement.classList.add("crashDumpSubmitted");
}
}
}
},
onAboutTabCrashedLoad: function (aBrowser) {
if (!this.childMap)
return;
let dumpID = this.childMap.get(this.browserMap.get(aBrowser));
if (!dumpID)
return;
aBrowser.contentDocument.documentElement.classList.add("crashDumpAvailable");
}
}
this.PluginCrashReporter = {
/**
* Makes the PluginCrashReporter ready to hear about and
* submit crash reports.
*/
init() {
if (this.initialized) {
return;
}
this.initialized = true;
this.crashReports = new Map();
Services.obs.addObserver(this, "plugin-crashed", false);
},
observe(subject, topic, data) {
if (topic != "plugin-crashed") {
return;
}
let propertyBag = subject;
if (!(propertyBag instanceof Ci.nsIPropertyBag2) ||
!(propertyBag instanceof Ci.nsIWritablePropertyBag2) ||
!propertyBag.hasKey("runID") ||
!propertyBag.hasKey("pluginName")) {
Cu.reportError("PluginCrashReporter can not read plugin information.");
return;
}
let runID = propertyBag.getPropertyAsUint32("runID");
let pluginDumpID = propertyBag.getPropertyAsAString("pluginDumpID");
let browserDumpID = propertyBag.getPropertyAsAString("browserDumpID");
if (pluginDumpID) {
this.crashReports.set(runID, { pluginDumpID, browserDumpID });
}
},
/**
* Submit a crash report for a crashed NPAPI plugin.
*
* @param runID
* The runID of the plugin that crashed. A run ID is a unique
* identifier for a particular run of a plugin process - and is
* analogous to a process ID (though it is managed by Gecko instead
* of the operating system).
* @param keyVals
* An object whose key-value pairs will be merged
* with the ".extra" file submitted with the report.
* The properties of htis object will override properties
* of the same name in the .extra file.
*/
submitCrashReport(runID, keyVals) {
if (!this.crashReports.has(runID)) {
Cu.reportError(`Could not find plugin dump IDs for run ID ${runID}.` +
`It is possible that a report was already submitted.`);
return;
}
keyVals = keyVals || {};
let { pluginDumpID, browserDumpID } = this.crashReports.get(runID);
let submissionPromise = CrashSubmit.submit(pluginDumpID, {
recordSubmission: true,
extraExtraKeyVals: keyVals,
});
if (browserDumpID)
CrashSubmit.submit(browserDumpID);
this.broadcastState(runID, "submitting");
submissionPromise.then(() => {
this.broadcastState(runID, "success");
}, () => {
this.broadcastState(runID, "failed");
});
this.crashReports.delete(runID);
},
broadcastState(runID, state) {
let enumerator = Services.wm.getEnumerator("navigator:browser");
while (enumerator.hasMoreElements()) {
let window = enumerator.getNext();
let mm = window.messageManager;
mm.broadcastAsyncMessage("BrowserPlugins:CrashReportSubmitted",
{ runID, state });
}
},
hasCrashReport(runID) {
return this.crashReports.has(runID);
},
/**
* Deprecated mechanism for sending crash reports for GMPs. This
* should be removed when bug 1146955 is fixed.
*/
submitGMPCrashReport(pluginDumpID, browserDumpID) {
CrashSubmit.submit(pluginDumpID, { recordSubmission: true });
if (browserDumpID)
CrashSubmit.submit(browserDumpID);
},
};

View File

@ -34,6 +34,8 @@ PluginContent.prototype = {
this.content = this.global.content;
// Cache of plugin actions for the current page.
this.pluginData = new Map();
// Cache of plugin crash information sent from the parent
this.pluginCrashData = new Map();
// Note that the XBL binding is untrusted
global.addEventListener("PluginBindingAttached", this, true, true);
@ -48,9 +50,28 @@ PluginContent.prototype = {
global.addMessageListener("BrowserPlugins:ActivatePlugins", this);
global.addMessageListener("BrowserPlugins:NotificationShown", this);
global.addMessageListener("BrowserPlugins:ContextMenuCommand", this);
global.addMessageListener("BrowserPlugins:NPAPIPluginProcessCrashed", this);
global.addMessageListener("BrowserPlugins:CrashReportSubmitted", this);
},
uninit: function() {
let global = this.global;
global.removeEventListener("PluginBindingAttached", this, true);
global.removeEventListener("PluginCrashed", this, true);
global.removeEventListener("PluginOutdated", this, true);
global.removeEventListener("PluginInstantiated", this, true);
global.removeEventListener("PluginRemoved", this, true);
global.removeEventListener("pagehide", this, true);
global.removeEventListener("pageshow", this, true);
global.removeEventListener("unload", this);
global.removeMessageListener("BrowserPlugins:ActivatePlugins", this);
global.removeMessageListener("BrowserPlugins:NotificationShown", this);
global.removeMessageListener("BrowserPlugins:ContextMenuCommand", this);
global.removeMessageListener("BrowserPlugins:NPAPIPluginProcessCrashed", this);
global.removeMessageListener("BrowserPlugins:CrashReportSubmitted", this);
global.removeMessageListener("BrowserPlugins:Test:ClearCrashData", this);
delete this.global;
delete this.content;
},
@ -73,6 +94,19 @@ PluginContent.prototype = {
break;
}
break;
case "BrowserPlugins:NPAPIPluginProcessCrashed":
this.NPAPIPluginProcessCrashed({
pluginName: msg.data.pluginName,
runID: msg.data.runID,
state: msg.data.state,
});
break;
case "BrowserPlugins:CrashReportSubmitted":
this.NPAPIPluginCrashReportSubmitted({
runID: msg.data.runID,
state: msg.data.state,
})
break;
}
},
@ -97,7 +131,7 @@ PluginContent.prototype = {
}
this._finishRecordingFlashPluginTelemetry();
this.clearPluginDataCache();
this.clearPluginCaches();
},
getPluginUI: function (plugin, anonid) {
@ -308,7 +342,7 @@ PluginContent.prototype = {
!(event.target instanceof Ci.nsIObjectLoadingContent)) {
// If the event target is not a plugin object (i.e., an <object> or
// <embed> element), this call is for a window-global plugin.
this.pluginInstanceCrashed(event.target, event);
this.onPluginCrashed(event.target, event);
return;
}
@ -339,7 +373,7 @@ PluginContent.prototype = {
let shouldShowNotification = false;
switch (eventType) {
case "PluginCrashed":
this.pluginInstanceCrashed(plugin, event);
this.onPluginCrashed(plugin, event);
break;
case "PluginNotFound": {
@ -520,24 +554,31 @@ PluginContent.prototype = {
this.global.sendAsyncMessage("PluginContent:LinkClickCallback", { name: name });
},
submitReport: function submitReport(pluginDumpID, browserDumpID, plugin) {
submitReport: function submitReport(plugin) {
if (!AppConstants.MOZ_CRASHREPORTER) {
return;
}
let keyVals = {};
if (plugin) {
let userComment = this.getPluginUI(plugin, "submitComment").value.trim();
if (userComment)
keyVals.PluginUserComment = userComment;
if (this.getPluginUI(plugin, "submitURLOptIn").checked)
keyVals.PluginContentURL = plugin.ownerDocument.URL;
if (!plugin) {
Cu.reportError("Attempted to submit crash report without an associated plugin.");
return;
}
if (!(plugin instanceof Ci.nsIObjectLoadingContent)) {
Cu.reportError("Attempted to submit crash report on plugin that does not" +
"implement nsIObjectLoadingContent.");
return;
}
this.global.sendAsyncMessage("PluginContent:SubmitReport", {
pluginDumpID: pluginDumpID,
browserDumpID: browserDumpID,
keyVals: keyVals,
});
let runID = plugin.runID;
let submitURLOptIn = this.getPluginUI(plugin, "submitURLOptIn");
let keyVals = {};
let userComment = this.getPluginUI(plugin, "submitComment").value.trim();
if (userComment)
keyVals.PluginUserComment = userComment;
if (this.getPluginUI(plugin, "submitURLOptIn").checked)
keyVals.PluginContentURL = plugin.ownerDocument.URL;
this.global.sendAsyncMessage("PluginContent:SubmitReport",
{ runID, keyVals, submitURLOptIn });
},
reloadPage: function () {
@ -845,112 +886,134 @@ PluginContent.prototype = {
this.global.sendAsyncMessage("PluginContent:RemoveNotification", { name: name });
},
clearPluginDataCache: function () {
clearPluginCaches: function () {
this.pluginData.clear();
this.pluginCrashData.clear();
},
hideNotificationBar: function (name) {
this.global.sendAsyncMessage("PluginContent:HideNotificationBar", { name: name });
},
// Crashed-plugin event listener. Called for every instance of a
// plugin in content.
pluginInstanceCrashed: function (target, aEvent) {
/**
* The PluginCrashed event handler. Note that the PluginCrashed event is
* fired for both NPAPI and Gecko Media plugins. In the latter case, the
* target of the event is the document that the GMP is being used in.
*/
onPluginCrashed: function (target, aEvent) {
if (!(aEvent instanceof this.content.PluginCrashedEvent))
return;
let submittedReport = aEvent.submittedCrashReport;
let doPrompt = true; // XXX followup for aEvent.doPrompt;
let submitReports = true; // XXX followup for aEvent.submitReports;
let pluginName = aEvent.pluginName;
let pluginDumpID = aEvent.pluginDumpID;
let browserDumpID = aEvent.browserDumpID;
let gmpPlugin = aEvent.gmpPlugin;
// For non-GMP plugins, remap the plugin name to a more user-presentable form.
if (!gmpPlugin) {
pluginName = BrowserUtils.makeNicePluginName(pluginName);
if (aEvent.gmpPlugin) {
this.GMPCrashed(aEvent);
return;
}
let messageString = gNavigatorBundle.formatStringFromName("crashedpluginsMessage.title", [pluginName], 1);
if (!(target instanceof Ci.nsIObjectLoadingContent))
return;
let plugin = null, doc;
if (target instanceof Ci.nsIObjectLoadingContent) {
plugin = target;
doc = plugin.ownerDocument;
} else {
doc = target.document;
if (!doc) {
return;
}
// doPrompt is specific to the crashed plugin overlay, and
// therefore is not applicable for window-global plugins.
doPrompt = false;
let crashData = this.pluginCrashData.get(target.runID);
if (!crashData) {
// We haven't received information from the parent yet about
// this crash, so we should hold off showing the crash report
// UI.
return;
}
let status;
// Determine which message to show regarding crash reports.
if (submittedReport) { // submitReports && !doPrompt, handled in observer
status = "submitted";
}
else if (!submitReports && !doPrompt) {
status = "noSubmit";
}
else if (!pluginDumpID) {
// If we don't have a minidumpID, we can't (or didn't) submit anything.
// This can happen if the plugin is killed from the task manager.
status = "noReport";
}
else {
status = "please";
crashData.instances.delete(target);
if (crashData.instances.length == 0) {
this.pluginCrashData.delete(target.runID);
}
// If we don't have a minidumpID, we can't (or didn't) submit anything.
// This can happen if the plugin is killed from the task manager.
if (!pluginDumpID) {
status = "noReport";
}
this.setCrashedNPAPIPluginState({
plugin: target,
state: crashData.state,
message: crashData.message,
});
},
// If we're showing the link to manually trigger report submission, we'll
// want to be able to update all the instances of the UI for this crash to
// show an updated message when a report is submitted.
if (AppConstants.MOZ_CRASHREPORTER && doPrompt) {
let observer = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
observe : (subject, topic, data) => {
let propertyBag = subject;
if (!(propertyBag instanceof Ci.nsIPropertyBag2))
return;
// Ignore notifications for other crashes.
if (propertyBag.get("minidumpID") != pluginDumpID)
return;
let statusDiv = this.getPluginUI(plugin, "submitStatus");
statusDiv.setAttribute("status", data);
},
NPAPIPluginProcessCrashed: function ({pluginName, runID, state}) {
let message =
gNavigatorBundle.formatStringFromName("crashedpluginsMessage.title",
[pluginName], 1);
handleEvent : function(event) {
// Not expected to be called, just here for the closure.
let contentWindow = this.global.content;
let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
let plugins = cwu.plugins;
for (let plugin of plugins) {
if (plugin instanceof Ci.nsIObjectLoadingContent &&
plugin.runID == runID) {
// The parent has told us that the plugin process has died.
// It's possible that this content process hasn't yet noticed,
// in which case we need to stash this data around until the
// PluginCrashed events get sent up.
if (plugin.pluginFallbackType == Ci.nsIObjectLoadingContent.PLUGIN_CRASHED) {
// This plugin has already been put into the crashed state by the
// content process, so we can tweak its crash UI without delay.
this.setCrashedNPAPIPluginState({plugin, state, message});
} else {
// The content process hasn't yet determined that the plugin has crashed.
// Stash the data in our map, and throw the plugin into a WeakSet. When
// the PluginCrashed event fires on the <object>/<embed>, we'll retrieve
// the information we need from the Map and remove the instance from the
// WeakSet. Once the WeakSet is empty, we can clear the map.
if (!this.pluginCrashData.has(runID)) {
this.pluginCrashData.set(runID, {
state: state,
message: message,
instances: new WeakSet(),
});
}
let crashData = this.pluginCrashData.get(runID);
crashData.instances.add(plugin);
}
}
// Use a weak reference, so we don't have to remove it...
Services.obs.addObserver(observer, "crash-report-status", true);
// ...alas, now we need something to hold a strong reference to prevent
// it from being GC. But I don't want to manually manage the reference's
// lifetime (which should be no greater than the page).
// Clever solution? Use a closue with an event listener on the document.
// When the doc goes away, so do the listener references and the closure.
doc.addEventListener("mozCleverClosureHack", observer, false);
}
},
let isShowing = false;
setCrashedNPAPIPluginState: function ({plugin, state, message}) {
// Force a layout flush so the binding is attached.
plugin.clientTop;
let overlay = this.getPluginUI(plugin, "main");
let statusDiv = this.getPluginUI(plugin, "submitStatus");
let optInCB = this.getPluginUI(plugin, "submitURLOptIn");
if (plugin) {
// If there's no plugin (an <object> or <embed> element), this call is
// for a window-global plugin. In this case, there's no overlay to show.
isShowing = _setUpPluginOverlay.call(this, plugin, doPrompt);
this.getPluginUI(plugin, "submitButton")
.addEventListener("click", (event) => {
if (event.button != 0 || !event.isTrusted)
return;
this.submitReport(plugin);
});
let pref = Services.prefs.getBranch("dom.ipc.plugins.reportCrashURL");
optInCB.checked = pref.getBoolPref("");
statusDiv.setAttribute("status", state);
let helpIcon = this.getPluginUI(plugin, "helpIcon");
this.addLinkClickCallback(helpIcon, "openHelpPage");
let crashText = this.getPluginUI(plugin, "crashedText");
crashText.textContent = message;
let link = this.getPluginUI(plugin, "reloadLink");
this.addLinkClickCallback(link, "reloadPage");
let isShowing = this.shouldShowOverlay(plugin, overlay);
// Is the <object>'s size too small to hold what we want to show?
if (!isShowing) {
// First try hiding the crash report submission UI.
statusDiv.removeAttribute("status");
isShowing = this.shouldShowOverlay(plugin, overlay);
}
this.setVisibility(plugin, overlay, isShowing);
let doc = plugin.ownerDocument;
let runID = plugin.runID;
if (isShowing) {
// If a previous plugin on the page was too small and resulted in adding a
@ -962,66 +1025,63 @@ PluginContent.prototype = {
// If another plugin on the page was large enough to show our UI, we don't
// want to show a notification bar.
if (!doc.mozNoPluginCrashedNotification) {
this.global.sendAsyncMessage("PluginContent:ShowPluginCrashedNotification", {
messageString: messageString,
pluginDumpID: pluginDumpID,
browserDumpID: browserDumpID,
});
this.global.sendAsyncMessage("PluginContent:ShowNPAPIPluginCrashedNotification",
{ message, runID });
// Remove the notification when the page is reloaded.
doc.defaultView.top.addEventListener("unload", event => {
this.hideNotificationBar("plugin-crashed");
}, false);
}
}
},
// Configure the crashed-plugin placeholder.
// Returns true if the plugin overlay is visible.
function _setUpPluginOverlay(plugin, doPromptSubmit) {
if (!plugin) {
return false;
NPAPIPluginCrashReportSubmitted: function({ runID, state }) {
this.pluginCrashData.delete(runID);
let contentWindow = this.global.content;
let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
let plugins = cwu.plugins;
for (let plugin of plugins) {
if (plugin instanceof Ci.nsIObjectLoadingContent &&
plugin.runID == runID) {
let statusDiv = this.getPluginUI(plugin, "submitStatus");
statusDiv.setAttribute("status", state);
}
// Force a layout flush so the binding is attached.
plugin.clientTop;
let overlay = this.getPluginUI(plugin, "main");
let statusDiv = this.getPluginUI(plugin, "submitStatus");
if (doPromptSubmit) {
this.getPluginUI(plugin, "submitButton").addEventListener("click",
function (event) {
if (event.button != 0 || !event.isTrusted)
return;
this.submitReport(pluginDumpID, browserDumpID, plugin);
pref.setBoolPref("", optInCB.checked);
}.bind(this));
let optInCB = this.getPluginUI(plugin, "submitURLOptIn");
let pref = Services.prefs.getBranch("dom.ipc.plugins.reportCrashURL");
optInCB.checked = pref.getBoolPref("");
}
statusDiv.setAttribute("status", status);
let helpIcon = this.getPluginUI(plugin, "helpIcon");
this.addLinkClickCallback(helpIcon, "openHelpPage");
let crashText = this.getPluginUI(plugin, "crashedText");
crashText.textContent = messageString;
let link = this.getPluginUI(plugin, "reloadLink");
this.addLinkClickCallback(link, "reloadPage");
let isShowing = this.shouldShowOverlay(plugin, overlay);
// Is the <object>'s size too small to hold what we want to show?
if (!isShowing) {
// First try hiding the crash report submission UI.
statusDiv.removeAttribute("status");
isShowing = this.shouldShowOverlay(plugin, overlay);
}
this.setVisibility(plugin, overlay, isShowing);
return isShowing;
}
}
},
/**
* Currently, GMP crash events are only handled in the non-e10s case.
* e10s support for GMP crash events is being tracked in bug 1146955.
*/
GMPCrashed: function(aEvent) {
let target = aEvent.target;
let submittedReport = aEvent.submittedCrashReport;
let pluginName = aEvent.pluginName;
let pluginDumpID = aEvent.pluginDumpID;
let browserDumpID = aEvent.browserDumpID;
let gmpPlugin = aEvent.gmpPlugin;
let doc = target.document;
if (!gmpPlugin || !doc) {
// TODO: Throw exception? How did we get here?
return;
}
let messageString =
gNavigatorBundle.formatStringFromName("crashedpluginsMessage.title",
[pluginName], 1);
this.global.sendAsyncMessage("PluginContent:ShowGMPCrashedNotification", {
messageString: messageString,
pluginDumpID: pluginDumpID,
browserDumpID: browserDumpID,
});
// Remove the notification when the page is reloaded.
doc.defaultView.top.addEventListener("unload", event => {
this.hideNotificationBar("plugin-crashed");
}, false);
},
};

View File

@ -1,101 +0,0 @@
/* 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/. */
"use strict";
let Cc = Components.classes;
let Ci = Components.interfaces;
let Cu = Components.utils;
this.EXPORTED_SYMBOLS = [ "TabCrashReporter" ];
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CrashSubmit",
"resource://gre/modules/CrashSubmit.jsm");
this.TabCrashReporter = {
init: function () {
if (this.initialized)
return;
this.initialized = true;
Services.obs.addObserver(this, "ipc:content-shutdown", false);
Services.obs.addObserver(this, "oop-frameloader-crashed", false);
this.childMap = new Map();
this.browserMap = new WeakMap();
},
observe: function (aSubject, aTopic, aData) {
switch (aTopic) {
case "ipc:content-shutdown":
aSubject.QueryInterface(Ci.nsIPropertyBag2);
if (!aSubject.get("abnormal"))
return;
this.childMap.set(aSubject.get("childID"), aSubject.get("dumpID"));
break;
case "oop-frameloader-crashed":
aSubject.QueryInterface(Ci.nsIFrameLoader);
let browser = aSubject.ownerElement;
if (!browser)
return;
this.browserMap.set(browser, aSubject.childID);
break;
}
},
submitCrashReport: function (aBrowser) {
let childID = this.browserMap.get(aBrowser);
let dumpID = this.childMap.get(childID);
if (!dumpID)
return
if (CrashSubmit.submit(dumpID, { recordSubmission: true })) {
this.childMap.set(childID, null); // Avoid resubmission.
this.removeSubmitCheckboxesForSameCrash(childID);
}
},
removeSubmitCheckboxesForSameCrash: function(childID) {
let enumerator = Services.wm.getEnumerator("navigator:browser");
while (enumerator.hasMoreElements()) {
let window = enumerator.getNext();
if (!window.gMultiProcessBrowser)
continue;
for (let browser of window.gBrowser.browsers) {
if (browser.isRemoteBrowser)
continue;
let doc = browser.contentDocument;
if (!doc.documentURI.startsWith("about:tabcrashed"))
continue;
if (this.browserMap.get(browser) == childID) {
this.browserMap.delete(browser);
browser.contentDocument.documentElement.classList.remove("crashDumpAvailable");
browser.contentDocument.documentElement.classList.add("crashDumpSubmitted");
}
}
}
},
onAboutTabCrashedLoad: function (aBrowser) {
if (!this.childMap)
return;
let dumpID = this.childMap.get(this.browserMap.get(aBrowser));
if (!dumpID)
return;
aBrowser.contentDocument.documentElement.classList.add("crashDumpAvailable");
}
}

View File

@ -16,6 +16,7 @@ EXTRA_JS_MODULES += [
'CastingApps.jsm',
'Chat.jsm',
'ContentClick.jsm',
'ContentCrashReporters.jsm',
'ContentLinkHandler.jsm',
'ContentObservers.jsm',
'ContentSearch.jsm',
@ -38,7 +39,6 @@ EXTRA_JS_MODULES += [
'SelfSupportBackend.jsm',
'SitePermissions.jsm',
'Social.jsm',
'TabCrashReporter.jsm',
'WebappManager.jsm',
'webrtcUI.jsm',
]