/* 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; this.EXPORTED_SYMBOLS = [ "TabCrashHandler", "PluginCrashReporter" ]; 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, "AppConstants", "resource://gre/modules/AppConstants.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "RemotePages", "resource://gre/modules/RemotePageManager.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "SessionStore", "resource:///modules/sessionstore/SessionStore.jsm"); this.TabCrashHandler = { _crashedTabCount: 0, get prefs() { delete this.prefs; return this.prefs = Services.prefs.getBranch("browser.tabs.crashReporting."); }, init: function () { if (this.initialized) return; this.initialized = true; if (AppConstants.MOZ_CRASHREPORTER) { Services.obs.addObserver(this, "ipc:content-shutdown", false); Services.obs.addObserver(this, "oop-frameloader-crashed", false); this.childMap = new Map(); this.browserMap = new WeakMap(); } 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: 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.permanentKey, aSubject.childID); break; } }, receiveMessage: function(message) { let browser = message.target.browser; let gBrowser = browser.ownerDocument.defaultView.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; } } }, /** * Submits a crash report from about:tabcrashed, if the crash * reporter is enabled and a crash report can be found. * * @param aBrowser * The that the report was sent from. * @param aFormData * An Object with the following properties: * * includeURL (bool): * Whether to include the URL that the user was on * in the crashed tab before the crash occurred. * URL (String) * The URL that the user was on in the crashed tab * before the crash occurred. * emailMe (bool): * Whether or not to include the user's email address * in the crash report. * email (String): * The email address of the user. * comments (String): * Any additional comments from the user. * * Note that it is expected that all properties are set, * even if they are empty. */ maybeSendCrashReport(message) { if (!AppConstants.MOZ_CRASHREPORTER) return; let browser = message.target.browser; let childID = this.browserMap.get(browser.permanentKey); let dumpID = this.childMap.get(childID); if (!dumpID) return if (!message.data.sendReport) { this.prefs.setBoolPref("sendReport", false); return; } let { includeURL, comments, email, emailMe, URL, } = message.data; CrashSubmit.submit(dumpID, { recordSubmission: true, extraExtraKeyVals: { Comments: comments, Email: email, URL: URL, }, }).then(null, Cu.reportError); this.prefs.setBoolPref("sendReport", true); this.prefs.setBoolPref("includeURL", includeURL); this.prefs.setBoolPref("emailMe", emailMe); if (emailMe) { this.prefs.setCharPref("email", email); } else { this.prefs.setCharPref("email", ""); } 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.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: function (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 dumpID = this.getDumpID(browser); if (!dumpID) { message.target.sendAsyncMessge("SetCrashReportAvailable", { hasReport: false, }); return; } 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 }; if (emailMe) { data.email = this.prefs.getCharPref("email", ""); } message.target.sendAsyncMessage("SetCrashReportAvailable", data); }, onAboutTabCrashedUnload: function() { 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, }); }, /** * For some , return a crash report dump ID for that browser * if we have been informed of one. Otherwise, return null. * * @param browser ( { 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); }, };