mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
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:
parent
0c226a877b
commit
8ce112b8d8
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
207
browser/modules/ContentCrashReporters.jsm
Normal file
207
browser/modules/ContentCrashReporters.jsm
Normal 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);
|
||||
},
|
||||
};
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
@ -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',
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user