diff options
Diffstat (limited to 'application/basilisk/modules/ContentCrashHandlers.jsm')
-rw-r--r-- | application/basilisk/modules/ContentCrashHandlers.jsm | 921 |
1 files changed, 0 insertions, 921 deletions
diff --git a/application/basilisk/modules/ContentCrashHandlers.jsm b/application/basilisk/modules/ContentCrashHandlers.jsm deleted file mode 100644 index 5e96a31345..0000000000 --- a/application/basilisk/modules/ContentCrashHandlers.jsm +++ /dev/null @@ -1,921 +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"; - -var Cc = Components.classes; -var Ci = Components.interfaces; -var Cu = Components.utils; -var Cr = Components.results; - -this.EXPORTED_SYMBOLS = [ "TabCrashHandler", - "PluginCrashReporter", - "UnsubmittedCrashHandler" ]; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "CrashSubmit", - "resource://gre/modules/CrashSubmit.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "RemotePages", - "resource://gre/modules/RemotePageManager.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "SessionStore", - "resource:///modules/sessionstore/SessionStore.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Task", - "resource://gre/modules/Task.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow", - "resource:///modules/RecentWindow.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", - "resource://gre/modules/PluralForm.jsm"); - -XPCOMUtils.defineLazyGetter(this, "gNavigatorBundle", function() { - const url = "chrome://browser/locale/browser.properties"; - return Services.strings.createBundle(url); -}); - -// We don't process crash reports older than 28 days, so don't bother -// submitting them -const PENDING_CRASH_REPORT_DAYS = 28; -const DAY = 24 * 60 * 60 * 1000; // milliseconds -const DAYS_TO_SUPPRESS = 30; -const MAX_UNSEEN_CRASHED_CHILD_IDS = 20; - -this.TabCrashHandler = { - _crashedTabCount: 0, - childMap: new Map(), - browserMap: new WeakMap(), - unseenCrashedChildIDs: [], - crashedBrowserQueues: new Map(), - - get prefs() { - delete this.prefs; - return this.prefs = Services.prefs.getBranch("browser.tabs.crashReporting."); - }, - - init() { - if (this.initialized) - return; - this.initialized = true; - - Services.obs.addObserver(this, "ipc:content-shutdown", false); - Services.obs.addObserver(this, "oop-frameloader-crashed", false); - - this.pageListener = new RemotePages("about:tabcrashed"); - // LOAD_BACKGROUND pages don't fire load events, so the about:tabcrashed - // content will fire up its own message when its initial scripts have - // finished running. - this.pageListener.addMessageListener("Load", this.receiveMessage.bind(this)); - this.pageListener.addMessageListener("RemotePage:Unload", this.receiveMessage.bind(this)); - this.pageListener.addMessageListener("closeTab", this.receiveMessage.bind(this)); - this.pageListener.addMessageListener("restoreTab", this.receiveMessage.bind(this)); - this.pageListener.addMessageListener("restoreAll", this.receiveMessage.bind(this)); - }, - - observe(aSubject, aTopic, aData) { - switch (aTopic) { - case "ipc:content-shutdown": { - aSubject.QueryInterface(Ci.nsIPropertyBag2); - - if (!aSubject.get("abnormal")) { - return; - } - - let childID = aSubject.get("childID"); - let dumpID = aSubject.get("dumpID"); - - if (!dumpID) { - Services.telemetry - .getHistogramById("FX_CONTENT_CRASH_DUMP_UNAVAILABLE") - .add(1); - } - - if (!this.flushCrashedBrowserQueue(childID)) { - this.unseenCrashedChildIDs.push(childID); - // The elements in unseenCrashedChildIDs will only be removed if - // the tab crash page is shown. However, ipc:content-shutdown might - // be fired for processes for which we'll never show the tab crash - // page - for example, the thumbnailing process. Another case to - // consider is if the user is configured to submit backlogged crash - // reports automatically, and a background tab crashes. In that case, - // we will never show the tab crash page, and never remove the element - // from the list. - // - // Instead of trying to account for all of those cases, we prevent - // this list from getting too large by putting a reasonable upper - // limit on how many childIDs we track. It's unlikely that this - // array would ever get so large as to be unwieldy (that'd be a lot - // or crashes!), but a leak is a leak. - if (this.unseenCrashedChildIDs.length > MAX_UNSEEN_CRASHED_CHILD_IDS) { - this.unseenCrashedChildIDs.shift(); - } - } - - // check for environment affecting crash reporting - let env = Cc["@mozilla.org/process/environment;1"] - .getService(Ci.nsIEnvironment); - let shutdown = env.exists("MOZ_CRASHREPORTER_SHUTDOWN"); - - if (shutdown) { - Services.startup.quit(Ci.nsIAppStartup.eForceQuit); - } - - break; - } - case "oop-frameloader-crashed": { - aSubject.QueryInterface(Ci.nsIFrameLoader); - - let browser = aSubject.ownerElement; - if (!browser) { - return; - } - - this.browserMap.set(browser.permanentKey, aSubject.childID); - break; - } - } - }, - - receiveMessage(message) { - let browser = message.target.browser; - let gBrowser = browser.ownerGlobal.gBrowser; - let tab = gBrowser.getTabForBrowser(browser); - - switch (message.name) { - case "Load": { - this.onAboutTabCrashedLoad(message); - break; - } - - case "RemotePage:Unload": { - this.onAboutTabCrashedUnload(message); - break; - } - - case "closeTab": { - this.maybeSendCrashReport(message); - gBrowser.removeTab(tab, { animate: true }); - break; - } - - case "restoreTab": { - this.maybeSendCrashReport(message); - SessionStore.reviveCrashedTab(tab); - break; - } - - case "restoreAll": { - this.maybeSendCrashReport(message); - SessionStore.reviveAllCrashedTabs(); - break; - } - } - }, - - /** - * This should be called once a content process has finished - * shutting down abnormally. Any tabbrowser browsers that were - * selected at the time of the crash will then be sent to - * the crashed tab page. - * - * @param childID (int) - * The childID of the content process that just crashed. - * @returns boolean - * True if one or more browsers were sent to the tab crashed - * page. - */ - flushCrashedBrowserQueue(childID) { - let browserQueue = this.crashedBrowserQueues.get(childID); - if (!browserQueue) { - return false; - } - - this.crashedBrowserQueues.delete(childID); - - let sentBrowser = false; - for (let weakBrowser of browserQueue) { - let browser = weakBrowser.get(); - if (browser) { - this.sendToTabCrashedPage(browser); - sentBrowser = true; - } - } - - return sentBrowser; - }, - - /** - * Called by a tabbrowser when it notices that its selected browser - * has crashed. This will queue the browser to show the tab crash - * page once the content process has finished tearing down. - * - * @param browser (<xul:browser>) - * The selected browser that just crashed. - */ - onSelectedBrowserCrash(browser) { - if (!browser.isRemoteBrowser) { - Cu.reportError("Selected crashed browser is not remote.") - return; - } - if (!browser.frameLoader) { - Cu.reportError("Selected crashed browser has no frameloader."); - return; - } - - let childID = browser.frameLoader.childID; - let browserQueue = this.crashedBrowserQueues.get(childID); - if (!browserQueue) { - browserQueue = []; - this.crashedBrowserQueues.set(childID, browserQueue); - } - // It's probably unnecessary to store this browser as a - // weak reference, since the content process should complete - // its teardown in the same tick of the event loop, and then - // this queue will be flushed. The weak reference is to avoid - // leaking browsers in case anything goes wrong during this - // teardown process. - browserQueue.push(Cu.getWeakReference(browser)); - }, - - /** - * This method is exposed for SessionStore to call if the user selects - * a tab which will restore on demand. It's possible that the tab - * is in this state because it recently crashed. If that's the case, then - * it's also possible that the user has not seen the tab crash page for - * that particular crash, in which case, we might show it to them instead - * of restoring the tab. - * - * @param browser (<xul:browser>) - * A browser from a browser tab that the user has just selected - * to restore on demand. - * @returns (boolean) - * True if TabCrashHandler will send the user to the tab crash - * page instead. - */ - willShowCrashedTab(browser) { - let childID = this.browserMap.get(browser.permanentKey); - // We will only show the tab crash page if: - // 1) We are aware that this browser crashed - // 2) We know we've never shown the tab crash page for the - // crash yet - // 3) The user is not configured to automatically submit backlogged - // crash reports. If they are, we'll send the crash report - // immediately. - if (childID && - this.unseenCrashedChildIDs.indexOf(childID) != -1) { - if (UnsubmittedCrashHandler.autoSubmit) { - let dumpID = this.childMap.get(childID); - if (dumpID) { - UnsubmittedCrashHandler.submitReports([dumpID]); - } - } else { - this.sendToTabCrashedPage(browser); - return true; - } - } - - return false; - }, - - /** - * We show a special page to users when a normal browser tab has crashed. - * This method should be called to send a browser to that page once the - * process has completely closed. - * - * @param browser (<xul:browser>) - * The browser that has recently crashed. - */ - sendToTabCrashedPage(browser) { - let title = browser.contentTitle; - let uri = browser.currentURI; - let gBrowser = browser.ownerGlobal.gBrowser; - let tab = gBrowser.getTabForBrowser(browser); - // The tab crashed page is non-remote by default. - gBrowser.updateBrowserRemoteness(browser, false); - - browser.setAttribute("crashedPageTitle", title); - browser.docShell.displayLoadError(Cr.NS_ERROR_CONTENT_CRASHED, uri, null); - browser.removeAttribute("crashedPageTitle"); - tab.setAttribute("crashed", true); - }, - - maybeSendCrashReport(message) { - // **STUB** - return; - }, - - removeSubmitCheckboxesForSameCrash(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.permanentKey) == childID) { - this.browserMap.delete(browser.permanentKey); - let ports = this.pageListener.portsForBrowser(browser); - if (ports.length) { - // For about:tabcrashed, we don't expect subframes. We can - // assume sending to the first port is sufficient. - ports[0].sendAsyncMessage("CrashReportSent"); - } - } - } - } - }, - - onAboutTabCrashedLoad(message) { - this._crashedTabCount++; - - // Broadcast to all about:tabcrashed pages a count of - // how many about:tabcrashed pages exist, so that they - // can decide whether or not to display the "Restore All - // Crashed Tabs" button. - this.pageListener.sendAsyncMessage("UpdateCount", { - count: this._crashedTabCount, - }); - - let browser = message.target.browser; - - let childID = this.browserMap.get(browser.permanentKey); - let index = this.unseenCrashedChildIDs.indexOf(childID); - if (index != -1) { - this.unseenCrashedChildIDs.splice(index, 1); - } - - let dumpID = this.getDumpID(browser); - if (!dumpID) { - message.target.sendAsyncMessage("SetCrashReportAvailable", { - hasReport: false, - }); - return; - } - - let requestAutoSubmit = !UnsubmittedCrashHandler.autoSubmit; - let requestEmail = this.prefs.getBoolPref("requestEmail"); - let sendReport = this.prefs.getBoolPref("sendReport"); - let includeURL = this.prefs.getBoolPref("includeURL"); - let emailMe = this.prefs.getBoolPref("emailMe"); - - let data = { - hasReport: true, - sendReport, - includeURL, - emailMe, - requestAutoSubmit, - requestEmail, - }; - - if (emailMe) { - data.email = this.prefs.getCharPref("email"); - } - - // Make sure to only count once even if there are multiple windows - // that will all show about:tabcrashed. - if (this._crashedTabCount == 1) { - Services.telemetry.getHistogramById("FX_CONTENT_CRASH_PRESENTED").add(1); - } - - message.target.sendAsyncMessage("SetCrashReportAvailable", data); - }, - - onAboutTabCrashedUnload(message) { - if (!this._crashedTabCount) { - Cu.reportError("Can not decrement crashed tab count to below 0"); - return; - } - this._crashedTabCount--; - - // Broadcast to all about:tabcrashed pages a count of - // how many about:tabcrashed pages exist, so that they - // can decide whether or not to display the "Restore All - // Crashed Tabs" button. - this.pageListener.sendAsyncMessage("UpdateCount", { - count: this._crashedTabCount, - }); - - let browser = message.target.browser; - let childID = this.browserMap.get(browser.permanentKey); - - // Make sure to only count once even if there are multiple windows - // that will all show about:tabcrashed. - if (this._crashedTabCount == 0 && childID) { - Services.telemetry.getHistogramById("FX_CONTENT_CRASH_NOT_SUBMITTED").add(1); - } - }, - - getDumpID(browser) { - // **STUB** - return null; - }, -} - -/** - * This component is responsible for scanning the pending - * crash report directory for reports, and (if enabled), to - * prompt the user to submit those reports. It might also - * submit those reports automatically without prompting if - * the user has opted in. - */ -this.UnsubmittedCrashHandler = { - get prefs() { - delete this.prefs; - return this.prefs = - Services.prefs.getBranch("browser.crashReports.unsubmittedCheck."); - }, - - get enabled() { - return this.prefs.getBoolPref("enabled"); - }, - - // showingNotification is set to true once a notification - // is successfully shown, and then set back to false if - // the notification is dismissed by an action by the user. - showingNotification: false, - // suppressed is true if we've determined that we've shown - // the notification too many times across too many days without - // user interaction, so we're suppressing the notification for - // some number of days. See the documentation for - // shouldShowPendingSubmissionsNotification(). - suppressed: false, - - init() { - if (this.initialized) { - return; - } - - this.initialized = true; - - // UnsubmittedCrashHandler can be initialized but still be disabled. - // This is intentional, as this makes simulating UnsubmittedCrashHandler's - // reactions to browser startup and shutdown easier in test automation. - // - // UnsubmittedCrashHandler, when initialized but not enabled, is inert. - if (this.enabled) { - if (this.prefs.prefHasUserValue("suppressUntilDate")) { - if (this.prefs.getCharPref("suppressUntilDate") > this.dateString()) { - // We'll be suppressing any notifications until after suppressedDate, - // so there's no need to do anything more. - this.suppressed = true; - return; - } - - // We're done suppressing, so we don't need this pref anymore. - this.prefs.clearUserPref("suppressUntilDate"); - } - - Services.obs.addObserver(this, "browser-delayed-startup-finished", - false); - Services.obs.addObserver(this, "profile-before-change", - false); - } - }, - - uninit() { - if (!this.initialized) { - return; - } - - this.initialized = false; - - if (!this.enabled) { - return; - } - - if (this.suppressed) { - this.suppressed = false; - // No need to do any more clean-up, since we were suppressed. - return; - } - - if (this.showingNotification) { - this.prefs.setBoolPref("shutdownWhileShowing", true); - this.showingNotification = false; - } - - try { - Services.obs.removeObserver(this, "browser-delayed-startup-finished"); - } catch (e) { - // The browser-delayed-startup-finished observer might have already - // fired and removed itself, so if this fails, it's okay. - if (e.result != Cr.NS_ERROR_FAILURE) { - throw e; - } - } - - Services.obs.removeObserver(this, "profile-before-change"); - }, - - observe(subject, topic, data) { - switch (topic) { - case "browser-delayed-startup-finished": { - Services.obs.removeObserver(this, topic); - this.checkForUnsubmittedCrashReports(); - break; - } - case "profile-before-change": { - this.uninit(); - break; - } - } - }, - - /** - * Scans the profile directory for unsubmitted crash reports - * within the past PENDING_CRASH_REPORT_DAYS days. If it - * finds any, it will, if necessary, attempt to open a notification - * bar to prompt the user to submit them. - * - * @returns Promise - * Resolves with the <xul:notification> after it tries to - * show a notification on the most recent browser window. - * If a notification cannot be shown, will resolve with null. - */ - checkForUnsubmittedCrashReports: Task.async(function*() { - let dateLimit = new Date(); - dateLimit.setDate(dateLimit.getDate() - PENDING_CRASH_REPORT_DAYS); - - let reportIDs = []; - try { - reportIDs = yield CrashSubmit.pendingIDsAsync(dateLimit); - } catch (e) { - Cu.reportError(e); - return null; - } - - if (reportIDs.length) { - if (this.autoSubmit) { - this.submitReports(reportIDs); - } else if (this.shouldShowPendingSubmissionsNotification()) { - return this.showPendingSubmissionsNotification(reportIDs); - } - } - return null; - }), - - /** - * Returns true if the notification should be shown. - * shouldShowPendingSubmissionsNotification makes this decision - * by looking at whether or not the user has seen the notification - * over several days without ever interacting with it. If this occurs - * too many times, we suppress the notification for DAYS_TO_SUPPRESS - * days. - * - * @returns bool - */ - shouldShowPendingSubmissionsNotification() { - if (!this.prefs.prefHasUserValue("shutdownWhileShowing")) { - return true; - } - - let shutdownWhileShowing = this.prefs.getBoolPref("shutdownWhileShowing"); - this.prefs.clearUserPref("shutdownWhileShowing"); - - if (!this.prefs.prefHasUserValue("lastShownDate")) { - // This isn't expected, but we're being defensive here. We'll - // opt for showing the notification in this case. - return true; - } - - let lastShownDate = this.prefs.getCharPref("lastShownDate"); - if (this.dateString() > lastShownDate && shutdownWhileShowing) { - // We're on a newer day then when we last showed the - // notification without closing it. We don't want to do - // this too many times, so we'll decrement a counter for - // this situation. Too many of these, and we'll assume the - // user doesn't know or care about unsubmitted notifications, - // and we'll suppress the notification for a while. - let chances = this.prefs.getIntPref("chancesUntilSuppress"); - if (--chances < 0) { - // We're out of chances! - this.prefs.clearUserPref("chancesUntilSuppress"); - // We'll suppress for DAYS_TO_SUPPRESS days. - let suppressUntil = - this.dateString(new Date(Date.now() + (DAY * DAYS_TO_SUPPRESS))); - this.prefs.setCharPref("suppressUntilDate", suppressUntil); - return false; - } - this.prefs.setIntPref("chancesUntilSuppress", chances); - } - - return true; - }, - - /** - * Given an array of unsubmitted crash report IDs, try to open - * up a notification asking the user to submit them. - * - * @param reportIDs (Array<string>) - * The Array of report IDs to offer the user to send. - * @returns The <xul:notification> if one is shown. null otherwise. - */ - showPendingSubmissionsNotification(reportIDs) { - let count = reportIDs.length; - if (!count) { - return null; - } - - let messageTemplate = - gNavigatorBundle.GetStringFromName("pendingCrashReports2.label"); - - let message = PluralForm.get(count, messageTemplate).replace("#1", count); - - let notification = this.show({ - notificationID: "pending-crash-reports", - message, - reportIDs, - onAction: () => { - this.showingNotification = false; - }, - }); - - if (notification) { - this.showingNotification = true; - this.prefs.setCharPref("lastShownDate", this.dateString()); - } - - return notification; - }, - - /** - * Returns a string representation of a Date in the format - * YYYYMMDD. - * - * @param someDate (Date, optional) - * The Date to convert to the string. If not provided, - * defaults to today's date. - * @returns String - */ - dateString(someDate = new Date()) { - let year = String(someDate.getFullYear()).padStart(4, "0"); - let month = String(someDate.getMonth() + 1).padStart(2, "0"); - let day = String(someDate.getDate()).padStart(2, "0"); - return year + month + day; - }, - - /** - * Attempts to show a notification bar to the user in the most - * recent browser window asking them to submit some crash report - * IDs. If a notification cannot be shown (for example, there - * is no browser window), this method exits silently. - * - * The notification will allow the user to submit their crash - * reports. If the user dismissed the notification, the crash - * reports will be marked to be ignored (though they can - * still be manually submitted via about:crashes). - * - * @param JS Object - * An Object with the following properties: - * - * notificationID (string) - * The ID for the notification to be opened. - * - * message (string) - * The message to be displayed in the notification. - * - * reportIDs (Array<string>) - * The array of report IDs to offer to the user. - * - * onAction (function, optional) - * A callback to fire once the user performs an - * action on the notification bar (this includes - * dismissing the notification). - * - * @returns The <xul:notification> if one is shown. null otherwise. - */ - show({ notificationID, message, reportIDs, onAction }) { - let chromeWin = RecentWindow.getMostRecentBrowserWindow(); - if (!chromeWin) { - // Can't show a notification in this case. We'll hopefully - // get another opportunity to have the user submit their - // crash reports later. - return null; - } - - let nb = chromeWin.document.getElementById("global-notificationbox"); - let notification = nb.getNotificationWithValue(notificationID); - if (notification) { - return null; - } - - let buttons = [{ - label: gNavigatorBundle.GetStringFromName("pendingCrashReports.send"), - callback: () => { - this.submitReports(reportIDs); - if (onAction) { - onAction(); - } - }, - }, - { - label: gNavigatorBundle.GetStringFromName("pendingCrashReports.alwaysSend"), - callback: () => { - this.autoSubmit = true; - this.submitReports(reportIDs); - if (onAction) { - onAction(); - } - }, - }, - { - label: gNavigatorBundle.GetStringFromName("pendingCrashReports.viewAll"), - callback() { - chromeWin.openUILinkIn("about:crashes", "tab"); - return true; - }, - }]; - - let eventCallback = (eventType) => { - if (eventType == "dismissed") { - // The user intentionally dismissed the notification, - // which we interpret as meaning that they don't care - // to submit the reports. We'll ignore these particular - // reports going forward. - reportIDs.forEach(function(reportID) { - CrashSubmit.ignore(reportID); - }); - if (onAction) { - onAction(); - } - } - }; - - return nb.appendNotification(message, notificationID, - "chrome://browser/skin/tab-crashed.svg", - nb.PRIORITY_INFO_HIGH, buttons, - eventCallback); - }, - - get autoSubmit() { - return Services.prefs - .getBoolPref("browser.crashReports.unsubmittedCheck.autoSubmit"); - }, - - set autoSubmit(val) { - Services.prefs.setBoolPref("browser.crashReports.unsubmittedCheck.autoSubmit", - val); - }, - - /** - * Attempt to submit reports to the crash report server. Each - * report will have the "SubmittedFromInfobar" extra key set - * to true. - * - * @param reportIDs (Array<string>) - * The array of reportIDs to submit. - */ - submitReports(reportIDs) { - for (let reportID of reportIDs) { - CrashSubmit.submit(reportID, { - extraExtraKeyVals: { - "SubmittedFromInfobar": true, - }, - }); - } - }, -}; - -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); - Services.obs.addObserver(this, "gmp-plugin-crash", false); - Services.obs.addObserver(this, "profile-after-change", false); - }, - - uninit() { - Services.obs.removeObserver(this, "plugin-crashed"); - Services.obs.removeObserver(this, "gmp-plugin-crash"); - Services.obs.removeObserver(this, "profile-after-change"); - this.initialized = false; - }, - - observe(subject, topic, data) { - switch (topic) { - case "plugin-crashed": { - let propertyBag = subject; - if (!(propertyBag instanceof Ci.nsIPropertyBag2) || - !(propertyBag instanceof Ci.nsIWritablePropertyBag2) || - !propertyBag.hasKey("runID") || - !propertyBag.hasKey("pluginDumpID")) { - 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 }); - } - break; - } - case "gmp-plugin-crash": { - let propertyBag = subject; - if (!(propertyBag instanceof Ci.nsIWritablePropertyBag2) || - !propertyBag.hasKey("pluginID") || - !propertyBag.hasKey("pluginDumpID") || - !propertyBag.hasKey("pluginName")) { - Cu.reportError("PluginCrashReporter can not read plugin information."); - return; - } - - let pluginID = propertyBag.getPropertyAsUint32("pluginID"); - let pluginDumpID = propertyBag.getPropertyAsAString("pluginDumpID"); - if (pluginDumpID) { - this.crashReports.set(pluginID, { pluginDumpID }); - } - - // Only the parent process gets the gmp-plugin-crash observer - // notification, so we need to inform any content processes that - // the GMP has crashed. - if (Cc["@mozilla.org/parentprocessmessagemanager;1"]) { - let pluginName = propertyBag.getPropertyAsAString("pluginName"); - let mm = Cc["@mozilla.org/parentprocessmessagemanager;1"] - .getService(Ci.nsIMessageListenerManager); - mm.broadcastAsyncMessage("gmp-plugin-crash", - { pluginName, pluginID }); - } - break; - } - case "profile-after-change": - this.uninit(); - break; - } - }, - - /** - * 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 Goanna 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); - }, -}; |