mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			1133 lines
		
	
	
	
		
			35 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1133 lines
		
	
	
	
		
			35 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* 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/. */
 | 
						|
 | 
						|
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
 | 
						|
 | 
						|
const lazy = {};
 | 
						|
 | 
						|
ChromeUtils.defineESModuleGetters(lazy, {
 | 
						|
  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
 | 
						|
  CrashSubmit: "resource://gre/modules/CrashSubmit.sys.mjs",
 | 
						|
  E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
 | 
						|
  SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
 | 
						|
  clearTimeout: "resource://gre/modules/Timer.sys.mjs",
 | 
						|
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
 | 
						|
});
 | 
						|
 | 
						|
// 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;
 | 
						|
const MAX_UNSEEN_CRASHED_SUBFRAME_IDS = 10;
 | 
						|
 | 
						|
// Time after which we will begin scanning for unsubmitted crash reports
 | 
						|
const CHECK_FOR_UNSUBMITTED_CRASH_REPORTS_DELAY_MS = 60 * 10000; // 10 minutes
 | 
						|
 | 
						|
// This is SIGUSR1 and indicates a user-invoked crash
 | 
						|
const EXIT_CODE_CONTENT_CRASHED = 245;
 | 
						|
 | 
						|
const TABCRASHED_ICON_URI = "chrome://browser/skin/tab-crashed.svg";
 | 
						|
 | 
						|
const SUBFRAMECRASH_LEARNMORE_URI =
 | 
						|
  "https://support.mozilla.org/kb/firefox-crashes-troubleshoot-prevent-and-get-help";
 | 
						|
 | 
						|
/**
 | 
						|
 * BrowserWeakMap is exactly like a WeakMap, but expects <xul:browser>
 | 
						|
 * objects only.
 | 
						|
 *
 | 
						|
 * Under the hood, BrowserWeakMap keys the map off of the <xul:browser>
 | 
						|
 * permanentKey. If, however, the browser has never gotten a permanentKey,
 | 
						|
 * it falls back to keying on the <xul:browser> element itself.
 | 
						|
 */
 | 
						|
class BrowserWeakMap extends WeakMap {
 | 
						|
  get(browser) {
 | 
						|
    if (browser.permanentKey) {
 | 
						|
      return super.get(browser.permanentKey);
 | 
						|
    }
 | 
						|
    return super.get(browser);
 | 
						|
  }
 | 
						|
 | 
						|
  set(browser, value) {
 | 
						|
    if (browser.permanentKey) {
 | 
						|
      return super.set(browser.permanentKey, value);
 | 
						|
    }
 | 
						|
    return super.set(browser, value);
 | 
						|
  }
 | 
						|
 | 
						|
  delete(browser) {
 | 
						|
    if (browser.permanentKey) {
 | 
						|
      return super.delete(browser.permanentKey);
 | 
						|
    }
 | 
						|
    return super.delete(browser);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
export var TabCrashHandler = {
 | 
						|
  _crashedTabCount: 0,
 | 
						|
  childMap: new Map(),
 | 
						|
  browserMap: new BrowserWeakMap(),
 | 
						|
  notificationsMap: new Map(),
 | 
						|
  unseenCrashedChildIDs: [],
 | 
						|
  pendingSubFrameCrashes: new Map(),
 | 
						|
  pendingSubFrameCrashesIDs: [],
 | 
						|
  crashedBrowserQueues: new Map(),
 | 
						|
  restartRequiredBrowsers: new WeakSet(),
 | 
						|
  testBuildIDMismatch: false,
 | 
						|
 | 
						|
  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");
 | 
						|
    Services.obs.addObserver(this, "oop-frameloader-crashed");
 | 
						|
  },
 | 
						|
 | 
						|
  observe(aSubject, aTopic) {
 | 
						|
    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");
 | 
						|
 | 
						|
        // Get and remove the subframe crash info first.
 | 
						|
        let subframeCrashItem = this.getAndRemoveSubframeCrash(childID);
 | 
						|
 | 
						|
        if (!dumpID) {
 | 
						|
          Services.telemetry
 | 
						|
            .getHistogramById("FX_CONTENT_CRASH_DUMP_UNAVAILABLE")
 | 
						|
            .add(1);
 | 
						|
        } else if (AppConstants.MOZ_CRASHREPORTER) {
 | 
						|
          this.childMap.set(childID, dumpID);
 | 
						|
 | 
						|
          // If this is a subframe crash, show the crash notification. Only
 | 
						|
          // show subframe notifications when there is a minidump available.
 | 
						|
          if (subframeCrashItem) {
 | 
						|
            let browsers =
 | 
						|
              ChromeUtils.nondeterministicGetWeakMapKeys(subframeCrashItem) ||
 | 
						|
              [];
 | 
						|
            for (let browserItem of browsers) {
 | 
						|
              let browser = subframeCrashItem.get(browserItem);
 | 
						|
              if (browser.isConnected && !browser.ownerGlobal.closed) {
 | 
						|
                this.showSubFrameNotification(browser, childID, dumpID);
 | 
						|
              }
 | 
						|
            }
 | 
						|
          }
 | 
						|
        }
 | 
						|
 | 
						|
        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 shutdown = Services.env.exists("MOZ_CRASHREPORTER_SHUTDOWN");
 | 
						|
 | 
						|
        if (shutdown) {
 | 
						|
          dump(
 | 
						|
            "A content process crashed and MOZ_CRASHREPORTER_SHUTDOWN is " +
 | 
						|
              "set, shutting down\n"
 | 
						|
          );
 | 
						|
          Services.startup.quit(
 | 
						|
            Ci.nsIAppStartup.eForceQuit,
 | 
						|
            EXIT_CODE_CONTENT_CRASHED
 | 
						|
          );
 | 
						|
        }
 | 
						|
 | 
						|
        break;
 | 
						|
      }
 | 
						|
      case "oop-frameloader-crashed": {
 | 
						|
        let browser = aSubject.ownerElement;
 | 
						|
        if (!browser) {
 | 
						|
          return;
 | 
						|
        }
 | 
						|
 | 
						|
        this.browserMap.set(browser, aSubject.childID);
 | 
						|
        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) {
 | 
						|
        if (
 | 
						|
          this.restartRequiredBrowsers.has(browser) ||
 | 
						|
          this.testBuildIDMismatch
 | 
						|
        ) {
 | 
						|
          this.sendToRestartRequiredPage(browser);
 | 
						|
        } else {
 | 
						|
          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.
 | 
						|
   * @param restartRequired (bool)
 | 
						|
   *        Whether or not a browser restart is required to recover.
 | 
						|
   */
 | 
						|
  onSelectedBrowserCrash(browser, restartRequired) {
 | 
						|
    if (!browser.isRemoteBrowser) {
 | 
						|
      console.error("Selected crashed browser is not remote.");
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    if (!browser.frameLoader) {
 | 
						|
      console.error("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));
 | 
						|
 | 
						|
    if (restartRequired) {
 | 
						|
      this.restartRequiredBrowsers.add(browser);
 | 
						|
    }
 | 
						|
 | 
						|
    // In the event that the content process failed to launch, then
 | 
						|
    // the childID will be 0. In that case, we will never receive
 | 
						|
    // a dumpID nor an ipc:content-shutdown observer notification,
 | 
						|
    // so we should flush the queue for childID 0 immediately.
 | 
						|
    if (childID == 0) {
 | 
						|
      this.flushCrashedBrowserQueue(0);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Called by a tabbrowser when it notices that a background browser
 | 
						|
   * has crashed. This will flip its remoteness to non-remote, and attempt
 | 
						|
   * to revive the crashed tab so that upon selection the tab either shows
 | 
						|
   * an error page, or automatically restores.
 | 
						|
   *
 | 
						|
   * @param browser (<xul:browser>)
 | 
						|
   *        The background browser that just crashed.
 | 
						|
   * @param restartRequired (bool)
 | 
						|
   *        Whether or not a browser restart is required to recover.
 | 
						|
   */
 | 
						|
  onBackgroundBrowserCrash(browser, restartRequired) {
 | 
						|
    if (restartRequired) {
 | 
						|
      this.restartRequiredBrowsers.add(browser);
 | 
						|
    }
 | 
						|
 | 
						|
    let gBrowser = browser.getTabBrowser();
 | 
						|
    let tab = gBrowser.getTabForBrowser(browser);
 | 
						|
 | 
						|
    gBrowser.updateBrowserRemoteness(browser, {
 | 
						|
      remoteType: lazy.E10SUtils.NOT_REMOTE,
 | 
						|
    });
 | 
						|
 | 
						|
    lazy.SessionStore.reviveCrashedTab(tab);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Called when a subframe crashes. If the dump is available, shows a subframe
 | 
						|
   * crashed notification, otherwise waits for one to be available.
 | 
						|
   *
 | 
						|
   * @param browser (<xul:browser>)
 | 
						|
   *        The browser containing the frame that just crashed.
 | 
						|
   * @param childId
 | 
						|
   *        The id of the process that just crashed.
 | 
						|
   */
 | 
						|
  async onSubFrameCrash(browser, childID) {
 | 
						|
    if (!AppConstants.MOZ_CRASHREPORTER) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // If a crash dump is available, use it. Otherwise, add the child id to the pending
 | 
						|
    // subframe crashes list, and wait for the crash "ipc:content-shutdown" notification
 | 
						|
    // to get the minidump. If it never arrives, don't show the notification.
 | 
						|
    let dumpID = this.childMap.get(childID);
 | 
						|
    if (dumpID) {
 | 
						|
      this.showSubFrameNotification(browser, childID, dumpID);
 | 
						|
    } else {
 | 
						|
      let item = this.pendingSubFrameCrashes.get(childID);
 | 
						|
      if (!item) {
 | 
						|
        item = new BrowserWeakMap();
 | 
						|
        this.pendingSubFrameCrashes.set(childID, item);
 | 
						|
 | 
						|
        // Add the childID to an array that only has room for MAX_UNSEEN_CRASHED_SUBFRAME_IDS
 | 
						|
        // items. If there is no more room, pop the oldest off and remove it. This technique
 | 
						|
        // is used instead of a timeout.
 | 
						|
        if (
 | 
						|
          this.pendingSubFrameCrashesIDs.length >=
 | 
						|
          MAX_UNSEEN_CRASHED_SUBFRAME_IDS
 | 
						|
        ) {
 | 
						|
          let idToDelete = this.pendingSubFrameCrashesIDs.shift();
 | 
						|
          this.pendingSubFrameCrashes.delete(idToDelete);
 | 
						|
        }
 | 
						|
        this.pendingSubFrameCrashesIDs.push(childID);
 | 
						|
      }
 | 
						|
      item.set(browser, browser);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Given a childID, retrieve the subframe crash info for it
 | 
						|
   * from the pendingSubFrameCrashes map. The data is removed
 | 
						|
   * from the map and returned.
 | 
						|
   *
 | 
						|
   * @param childID number
 | 
						|
   *        childID of the content that crashed.
 | 
						|
   * @returns subframe crash info added by previous call to onSubFrameCrash.
 | 
						|
   */
 | 
						|
  getAndRemoveSubframeCrash(childID) {
 | 
						|
    let item = this.pendingSubFrameCrashes.get(childID);
 | 
						|
    if (item) {
 | 
						|
      this.pendingSubFrameCrashes.delete(childID);
 | 
						|
      let idx = this.pendingSubFrameCrashesIDs.indexOf(childID);
 | 
						|
      if (idx >= 0) {
 | 
						|
        this.pendingSubFrameCrashesIDs.splice(idx, 1);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    return item;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Called to indicate that a subframe within a browser has crashed. A notification
 | 
						|
   * bar will be shown.
 | 
						|
   *
 | 
						|
   * @param browser (<xul:browser>)
 | 
						|
   *        The browser containing the frame that just crashed.
 | 
						|
   * @param childId
 | 
						|
   *        The id of the process that just crashed.
 | 
						|
   * @param dumpID
 | 
						|
   *        Minidump id of the crash.
 | 
						|
   */
 | 
						|
  async showSubFrameNotification(browser, childID, dumpID) {
 | 
						|
    let gBrowser = browser.getTabBrowser();
 | 
						|
    let notificationBox = gBrowser.getNotificationBox(browser);
 | 
						|
 | 
						|
    const value = "subframe-crashed";
 | 
						|
    let notification = notificationBox.getNotificationWithValue(value);
 | 
						|
    if (notification) {
 | 
						|
      // Don't show multiple notifications for a browser.
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let closeAllNotifications = () => {
 | 
						|
      // Close all other notifications on other tabs that might
 | 
						|
      // be open for the same crashed process.
 | 
						|
      let existingItem = this.notificationsMap.get(childID);
 | 
						|
      if (existingItem) {
 | 
						|
        for (let notif of existingItem.slice()) {
 | 
						|
          notif.close();
 | 
						|
        }
 | 
						|
      }
 | 
						|
    };
 | 
						|
 | 
						|
    gBrowser.ownerGlobal.MozXULElement.insertFTLIfNeeded(
 | 
						|
      "browser/contentCrash.ftl"
 | 
						|
    );
 | 
						|
 | 
						|
    let buttons = [
 | 
						|
      {
 | 
						|
        "l10n-id": "crashed-subframe-learnmore-link",
 | 
						|
        popup: null,
 | 
						|
        link: SUBFRAMECRASH_LEARNMORE_URI,
 | 
						|
      },
 | 
						|
      {
 | 
						|
        "l10n-id": "crashed-subframe-submit",
 | 
						|
        popup: null,
 | 
						|
        callback: async () => {
 | 
						|
          if (dumpID) {
 | 
						|
            UnsubmittedCrashHandler.submitReports(
 | 
						|
              [dumpID],
 | 
						|
              lazy.CrashSubmit.SUBMITTED_FROM_CRASH_TAB
 | 
						|
            );
 | 
						|
          }
 | 
						|
          closeAllNotifications();
 | 
						|
        },
 | 
						|
      },
 | 
						|
    ];
 | 
						|
 | 
						|
    notification = await notificationBox.appendNotification(
 | 
						|
      value,
 | 
						|
      {
 | 
						|
        label: { "l10n-id": "crashed-subframe-message" },
 | 
						|
        image: TABCRASHED_ICON_URI,
 | 
						|
        priority: notificationBox.PRIORITY_INFO_MEDIUM,
 | 
						|
        eventCallback: eventName => {
 | 
						|
          if (eventName == "disconnected") {
 | 
						|
            let existingItem = this.notificationsMap.get(childID);
 | 
						|
            if (existingItem) {
 | 
						|
              let idx = existingItem.indexOf(notification);
 | 
						|
              if (idx >= 0) {
 | 
						|
                existingItem.splice(idx, 1);
 | 
						|
              }
 | 
						|
 | 
						|
              if (!existingItem.length) {
 | 
						|
                this.notificationsMap.delete(childID);
 | 
						|
              }
 | 
						|
            }
 | 
						|
          } else if (eventName == "dismissed") {
 | 
						|
            if (dumpID) {
 | 
						|
              lazy.CrashSubmit.ignore(dumpID);
 | 
						|
              this.childMap.delete(childID);
 | 
						|
            }
 | 
						|
 | 
						|
            closeAllNotifications();
 | 
						|
          }
 | 
						|
        },
 | 
						|
      },
 | 
						|
      buttons
 | 
						|
    );
 | 
						|
 | 
						|
    let existingItem = this.notificationsMap.get(childID);
 | 
						|
    if (existingItem) {
 | 
						|
      existingItem.push(notification);
 | 
						|
    } else {
 | 
						|
      this.notificationsMap.set(childID, [notification]);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * 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);
 | 
						|
    // 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.includes(childID)) {
 | 
						|
      if (UnsubmittedCrashHandler.autoSubmit) {
 | 
						|
        let dumpID = this.childMap.get(childID);
 | 
						|
        if (dumpID) {
 | 
						|
          UnsubmittedCrashHandler.submitReports(
 | 
						|
            [dumpID],
 | 
						|
            lazy.CrashSubmit.SUBMITTED_FROM_AUTO
 | 
						|
          );
 | 
						|
        }
 | 
						|
      } else {
 | 
						|
        this.sendToTabCrashedPage(browser);
 | 
						|
        return true;
 | 
						|
      }
 | 
						|
    } else if (childID === 0) {
 | 
						|
      if (this.restartRequiredBrowsers.has(browser)) {
 | 
						|
        this.sendToRestartRequiredPage(browser);
 | 
						|
      } else {
 | 
						|
        this.sendToTabCrashedPage(browser);
 | 
						|
      }
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
 | 
						|
    return false;
 | 
						|
  },
 | 
						|
 | 
						|
  sendToRestartRequiredPage(browser) {
 | 
						|
    let uri = browser.currentURI;
 | 
						|
    let gBrowser = browser.getTabBrowser();
 | 
						|
    let tab = gBrowser.getTabForBrowser(browser);
 | 
						|
    // The restart required page is non-remote by default.
 | 
						|
    gBrowser.updateBrowserRemoteness(browser, {
 | 
						|
      remoteType: lazy.E10SUtils.NOT_REMOTE,
 | 
						|
    });
 | 
						|
 | 
						|
    browser.docShell.displayLoadError(Cr.NS_ERROR_BUILDID_MISMATCH, uri, null);
 | 
						|
    tab.setAttribute("crashed", true);
 | 
						|
    gBrowser.tabContainer.updateTabIndicatorAttr(tab);
 | 
						|
 | 
						|
    // Make sure to only count once even if there are multiple windows
 | 
						|
    // that will all show about:restartrequired.
 | 
						|
    if (this._crashedTabCount == 1) {
 | 
						|
      Services.telemetry.scalarAdd("dom.contentprocess.buildID_mismatch", 1);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * 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.getTabBrowser();
 | 
						|
    let tab = gBrowser.getTabForBrowser(browser);
 | 
						|
    // The tab crashed page is non-remote by default.
 | 
						|
    gBrowser.updateBrowserRemoteness(browser, {
 | 
						|
      remoteType: lazy.E10SUtils.NOT_REMOTE,
 | 
						|
    });
 | 
						|
 | 
						|
    browser.setAttribute("crashedPageTitle", title);
 | 
						|
    browser.docShell.displayLoadError(Cr.NS_ERROR_CONTENT_CRASHED, uri, null);
 | 
						|
    browser.removeAttribute("crashedPageTitle");
 | 
						|
    tab.setAttribute("crashed", true);
 | 
						|
    gBrowser.tabContainer.updateTabIndicatorAttr(tab);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Submits a crash report from about:tabcrashed, if the crash
 | 
						|
   * reporter is enabled and a crash report can be found.
 | 
						|
   *
 | 
						|
   * @param browser
 | 
						|
   *        The <xul:browser> that the report was sent from.
 | 
						|
   * @param message
 | 
						|
   *        Message data 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.
 | 
						|
   *        comments (String):
 | 
						|
   *          Any additional comments from the user.
 | 
						|
   *
 | 
						|
   *        Note that it is expected that all properties are set,
 | 
						|
   *        even if they are empty.
 | 
						|
   */
 | 
						|
  maybeSendCrashReport(browser, message) {
 | 
						|
    if (!AppConstants.MOZ_CRASHREPORTER) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!message.data.hasReport) {
 | 
						|
      // There was no report, so nothing to do.
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (message.data.autoSubmit) {
 | 
						|
      // The user has opted in to autosubmitted backlogged
 | 
						|
      // crash reports in the future.
 | 
						|
      UnsubmittedCrashHandler.autoSubmit = true;
 | 
						|
    }
 | 
						|
 | 
						|
    let childID = this.browserMap.get(browser);
 | 
						|
    let dumpID = this.childMap.get(childID);
 | 
						|
    if (!dumpID) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!message.data.sendReport) {
 | 
						|
      Services.telemetry
 | 
						|
        .getHistogramById("FX_CONTENT_CRASH_NOT_SUBMITTED")
 | 
						|
        .add(1);
 | 
						|
      this.prefs.setBoolPref("sendReport", false);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let { includeURL, comments, URL } = message.data;
 | 
						|
 | 
						|
    let extraExtraKeyVals = {
 | 
						|
      Comments: comments,
 | 
						|
      URL,
 | 
						|
    };
 | 
						|
 | 
						|
    // For the entries in extraExtraKeyVals, we only want to submit the
 | 
						|
    // extra data values where they are not the empty string.
 | 
						|
    for (let key in extraExtraKeyVals) {
 | 
						|
      let val = extraExtraKeyVals[key].trim();
 | 
						|
      if (!val) {
 | 
						|
        delete extraExtraKeyVals[key];
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // URL is special, since it's already been written to extra data by
 | 
						|
    // default. In order to make sure we don't send it, we overwrite it
 | 
						|
    // with the empty string.
 | 
						|
    if (!includeURL) {
 | 
						|
      extraExtraKeyVals.URL = "";
 | 
						|
    }
 | 
						|
 | 
						|
    lazy.CrashSubmit.submit(dumpID, lazy.CrashSubmit.SUBMITTED_FROM_CRASH_TAB, {
 | 
						|
      recordSubmission: true,
 | 
						|
      extraExtraKeyVals,
 | 
						|
    }).catch(console.error);
 | 
						|
 | 
						|
    this.prefs.setBoolPref("sendReport", true);
 | 
						|
    this.prefs.setBoolPref("includeURL", includeURL);
 | 
						|
 | 
						|
    this.childMap.set(childID, null); // Avoid resubmission.
 | 
						|
    this.removeSubmitCheckboxesForSameCrash(childID);
 | 
						|
  },
 | 
						|
 | 
						|
  removeSubmitCheckboxesForSameCrash(childID) {
 | 
						|
    for (let window of Services.wm.getEnumerator("navigator:browser")) {
 | 
						|
      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.sendMessageToActor("CrashReportSent", {}, "AboutTabCrashed");
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Process a crashed tab loaded into a browser.
 | 
						|
   *
 | 
						|
   * @param browser
 | 
						|
   *        The <xul:browser> containing the page that crashed.
 | 
						|
   * @returns crash data
 | 
						|
   *        Message data containing information about the crash.
 | 
						|
   */
 | 
						|
  onAboutTabCrashedLoad(browser) {
 | 
						|
    this._crashedTabCount++;
 | 
						|
 | 
						|
    let window = browser.ownerGlobal;
 | 
						|
 | 
						|
    // Reset the zoom for the tabcrashed page.
 | 
						|
    window.ZoomManager.setZoomForBrowser(browser, 1);
 | 
						|
 | 
						|
    let childID = this.browserMap.get(browser);
 | 
						|
    let index = this.unseenCrashedChildIDs.indexOf(childID);
 | 
						|
    if (index != -1) {
 | 
						|
      this.unseenCrashedChildIDs.splice(index, 1);
 | 
						|
    }
 | 
						|
 | 
						|
    let dumpID = this.getDumpID(browser);
 | 
						|
    if (!dumpID) {
 | 
						|
      return {
 | 
						|
        hasReport: false,
 | 
						|
      };
 | 
						|
    }
 | 
						|
 | 
						|
    let requestAutoSubmit = !UnsubmittedCrashHandler.autoSubmit;
 | 
						|
    let sendReport = this.prefs.getBoolPref("sendReport");
 | 
						|
    let includeURL = this.prefs.getBoolPref("includeURL");
 | 
						|
 | 
						|
    let data = {
 | 
						|
      hasReport: true,
 | 
						|
      sendReport,
 | 
						|
      includeURL,
 | 
						|
      requestAutoSubmit,
 | 
						|
    };
 | 
						|
 | 
						|
    return data;
 | 
						|
  },
 | 
						|
 | 
						|
  onAboutTabCrashedUnload(browser) {
 | 
						|
    if (!this._crashedTabCount) {
 | 
						|
      console.error("Can not decrement crashed tab count to below 0");
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    this._crashedTabCount--;
 | 
						|
 | 
						|
    let childID = this.browserMap.get(browser);
 | 
						|
 | 
						|
    // 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);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * For some <xul:browser>, return a crash report dump ID for that browser
 | 
						|
   * if we have been informed of one. Otherwise, return null.
 | 
						|
   *
 | 
						|
   * @param browser (<xul:browser)
 | 
						|
   *        The browser to try to get the dump ID for
 | 
						|
   * @returns dumpID (String)
 | 
						|
   */
 | 
						|
  getDumpID(browser) {
 | 
						|
    if (!AppConstants.MOZ_CRASHREPORTER) {
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
 | 
						|
    return this.childMap.get(this.browserMap.get(browser));
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * This is intended for TESTING ONLY. It returns the amount of
 | 
						|
   * content processes that have crashed such that we're still waiting
 | 
						|
   * for dump IDs for their crash reports.
 | 
						|
   *
 | 
						|
   * For our automated tests, accessing the crashed content process
 | 
						|
   * count helps us test the behaviour when content processes crash due
 | 
						|
   * to launch failure, since in those cases we should not increase the
 | 
						|
   * crashed browser queue (since we never receive dump IDs for launch
 | 
						|
   * failures).
 | 
						|
   */
 | 
						|
  get queuedCrashedBrowsers() {
 | 
						|
    return this.crashedBrowserQueues.size;
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * 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.
 | 
						|
 */
 | 
						|
export var 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,
 | 
						|
 | 
						|
  _checkTimeout: null,
 | 
						|
 | 
						|
  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, "profile-before-change");
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  uninit() {
 | 
						|
    if (!this.initialized) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    this.initialized = false;
 | 
						|
 | 
						|
    if (this._checkTimeout) {
 | 
						|
      lazy.clearTimeout(this._checkTimeout);
 | 
						|
      this._checkTimeout = null;
 | 
						|
    }
 | 
						|
 | 
						|
    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;
 | 
						|
    }
 | 
						|
 | 
						|
    Services.obs.removeObserver(this, "profile-before-change");
 | 
						|
  },
 | 
						|
 | 
						|
  observe(subject, topic) {
 | 
						|
    switch (topic) {
 | 
						|
      case "profile-before-change": {
 | 
						|
        this.uninit();
 | 
						|
        break;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  scheduleCheckForUnsubmittedCrashReports() {
 | 
						|
    this._checkTimeout = lazy.setTimeout(() => {
 | 
						|
      Services.tm.idleDispatchToMainThread(() => {
 | 
						|
        this.checkForUnsubmittedCrashReports();
 | 
						|
      });
 | 
						|
    }, CHECK_FOR_UNSUBMITTED_CRASH_REPORTS_DELAY_MS);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * 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.
 | 
						|
   */
 | 
						|
  async checkForUnsubmittedCrashReports() {
 | 
						|
    if (!this.enabled || this.suppressed) {
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
 | 
						|
    let dateLimit = new Date();
 | 
						|
    dateLimit.setDate(dateLimit.getDate() - PENDING_CRASH_REPORT_DAYS);
 | 
						|
 | 
						|
    let reportIDs = [];
 | 
						|
    try {
 | 
						|
      reportIDs = await lazy.CrashSubmit.pendingIDs(dateLimit);
 | 
						|
    } catch (e) {
 | 
						|
      console.error(e);
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
 | 
						|
    if (reportIDs.length) {
 | 
						|
      Glean.crashSubmission.pending.add(reportIDs.length);
 | 
						|
      if (this.autoSubmit) {
 | 
						|
        this.submitReports(reportIDs, lazy.CrashSubmit.SUBMITTED_FROM_AUTO);
 | 
						|
      } 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.
 | 
						|
   */
 | 
						|
  async showPendingSubmissionsNotification(reportIDs) {
 | 
						|
    if (!reportIDs.length) {
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
 | 
						|
    let notification = await this.show({
 | 
						|
      notificationID: "pending-crash-reports",
 | 
						|
      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.
 | 
						|
   *
 | 
						|
   *        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, reportIDs, onAction }) {
 | 
						|
    let chromeWin = lazy.BrowserWindowTracker.getTopWindow();
 | 
						|
    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 notification =
 | 
						|
      chromeWin.gNotificationBox.getNotificationWithValue(notificationID);
 | 
						|
    if (notification) {
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
 | 
						|
    chromeWin.MozXULElement.insertFTLIfNeeded("browser/contentCrash.ftl");
 | 
						|
 | 
						|
    let buttons = [
 | 
						|
      {
 | 
						|
        "l10n-id": "pending-crash-reports-send",
 | 
						|
        callback: () => {
 | 
						|
          this.submitReports(
 | 
						|
            reportIDs,
 | 
						|
            lazy.CrashSubmit.SUBMITTED_FROM_INFOBAR
 | 
						|
          );
 | 
						|
          if (onAction) {
 | 
						|
            onAction();
 | 
						|
          }
 | 
						|
        },
 | 
						|
      },
 | 
						|
      {
 | 
						|
        "l10n-id": "pending-crash-reports-always-send",
 | 
						|
        callback: () => {
 | 
						|
          this.autoSubmit = true;
 | 
						|
          this.submitReports(
 | 
						|
            reportIDs,
 | 
						|
            lazy.CrashSubmit.SUBMITTED_FROM_INFOBAR
 | 
						|
          );
 | 
						|
          if (onAction) {
 | 
						|
            onAction();
 | 
						|
          }
 | 
						|
        },
 | 
						|
      },
 | 
						|
      {
 | 
						|
        "l10n-id": "pending-crash-reports-view-all",
 | 
						|
        callback() {
 | 
						|
          chromeWin.openTrustedLinkIn("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) {
 | 
						|
          lazy.CrashSubmit.ignore(reportID);
 | 
						|
        });
 | 
						|
        if (onAction) {
 | 
						|
          onAction();
 | 
						|
        }
 | 
						|
      }
 | 
						|
    };
 | 
						|
 | 
						|
    return chromeWin.gNotificationBox.appendNotification(
 | 
						|
      notificationID,
 | 
						|
      {
 | 
						|
        label: {
 | 
						|
          "l10n-id": "pending-crash-reports-message",
 | 
						|
          "l10n-args": { reportCount: reportIDs.length },
 | 
						|
        },
 | 
						|
        image: TABCRASHED_ICON_URI,
 | 
						|
        priority: chromeWin.gNotificationBox.PRIORITY_INFO_HIGH,
 | 
						|
        eventCallback,
 | 
						|
      },
 | 
						|
      buttons
 | 
						|
    );
 | 
						|
  },
 | 
						|
 | 
						|
  get autoSubmit() {
 | 
						|
    return Services.prefs.getBoolPref(
 | 
						|
      "browser.crashReports.unsubmittedCheck.autoSubmit2"
 | 
						|
    );
 | 
						|
  },
 | 
						|
 | 
						|
  set autoSubmit(val) {
 | 
						|
    Services.prefs.setBoolPref(
 | 
						|
      "browser.crashReports.unsubmittedCheck.autoSubmit2",
 | 
						|
      val
 | 
						|
    );
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Attempt to submit reports to the crash report server.
 | 
						|
   *
 | 
						|
   * @param reportIDs (Array<string>)
 | 
						|
   *        The array of reportIDs to submit.
 | 
						|
   * @param submittedFrom (string)
 | 
						|
   *        One of the CrashSubmit.SUBMITTED_FROM_* constants representing
 | 
						|
   *        how this crash was submitted.
 | 
						|
   */
 | 
						|
  submitReports(reportIDs, submittedFrom) {
 | 
						|
    for (let reportID of reportIDs) {
 | 
						|
      lazy.CrashSubmit.submit(reportID, submittedFrom).catch(console.error);
 | 
						|
    }
 | 
						|
  },
 | 
						|
};
 |