tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

GeckoViewProcessHangMonitor.sys.mjs (5359B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 import { GeckoViewModule } from "resource://gre/modules/GeckoViewModule.sys.mjs";
      6 
      7 export class GeckoViewProcessHangMonitor extends GeckoViewModule {
      8  constructor(aModuleInfo) {
      9    super(aModuleInfo);
     10 
     11    /**
     12     * Collection of hang reports that haven't expired or been dismissed
     13     * by the user. These are nsIHangReports.
     14     */
     15    this._activeReports = new Set();
     16 
     17    /**
     18     * Collection of hang reports that have been suppressed for a short
     19     * period of time. Keys are nsIHangReports. Values are timeouts for
     20     * when the wait time expires.
     21     */
     22    this._pausedReports = new Map();
     23 
     24    /**
     25     * Simple index used for report identification
     26     */
     27    this._nextIndex = 0;
     28 
     29    /**
     30     * Map of report IDs to report objects.
     31     * Keys are numbers. Values are nsIHangReports.
     32     */
     33    this._reportIndex = new Map();
     34 
     35    /**
     36     * Map of report objects to report IDs.
     37     * Keys are nsIHangReports. Values are numbers.
     38     */
     39    this._reportLookupIndex = new Map();
     40  }
     41 
     42  onInit() {
     43    debug`onInit`;
     44    Services.obs.addObserver(this, "process-hang-report");
     45    Services.obs.addObserver(this, "clear-hang-report");
     46  }
     47 
     48  onDestroy() {
     49    debug`onDestroy`;
     50    Services.obs.removeObserver(this, "process-hang-report");
     51    Services.obs.removeObserver(this, "clear-hang-report");
     52  }
     53 
     54  onEnable() {
     55    debug`onEnable`;
     56    this.registerListener([
     57      "GeckoView:HangReportStop",
     58      "GeckoView:HangReportWait",
     59    ]);
     60  }
     61 
     62  onDisable() {
     63    debug`onDisable`;
     64    this.unregisterListener();
     65  }
     66 
     67  // Bundle event handler.
     68  onEvent(aEvent, aData) {
     69    debug`onEvent: event=${aEvent}, data=${aData}`;
     70 
     71    if (this._reportIndex.has(aData.hangId)) {
     72      const report = this._reportIndex.get(aData.hangId);
     73      switch (aEvent) {
     74        case "GeckoView:HangReportStop":
     75          this.stopHang(report);
     76          break;
     77        case "GeckoView:HangReportWait":
     78          this.pauseHang(report);
     79          break;
     80      }
     81    } else {
     82      debug`Report not found: reportIndex=${this._reportIndex}`;
     83    }
     84  }
     85 
     86  // nsIObserver event handler
     87  observe(aSubject, aTopic) {
     88    debug`observe(aTopic=${aTopic})`;
     89    aSubject.QueryInterface(Ci.nsIHangReport);
     90    if (!aSubject.isReportForBrowserOrChildren(this.browser.frameLoader)) {
     91      return;
     92    }
     93 
     94    switch (aTopic) {
     95      case "process-hang-report": {
     96        this.reportHang(aSubject);
     97        break;
     98      }
     99      case "clear-hang-report": {
    100        this.clearHang(aSubject);
    101        break;
    102      }
    103    }
    104  }
    105 
    106  /**
    107   * This timeout is the wait period applied after a user selects "Wait" in
    108   * an existing notification.
    109   */
    110  get WAIT_EXPIRATION_TIME() {
    111    try {
    112      return Services.prefs.getIntPref("browser.hangNotification.waitPeriod");
    113    } catch (ex) {
    114      return 10000;
    115    }
    116  }
    117 
    118  /**
    119   * Terminate whatever is causing this report, be it an add-on or page script.
    120   * This is done without updating any report notifications.
    121   */
    122  stopHang(report) {
    123    report.terminateScript();
    124  }
    125 
    126  /**
    127   *
    128   */
    129  pauseHang(report) {
    130    this._activeReports.delete(report);
    131 
    132    // Create a new timeout with notify callback
    133    const timer = this.window.setTimeout(() => {
    134      for (const [stashedReport, otherTimer] of this._pausedReports) {
    135        if (otherTimer === timer) {
    136          this._pausedReports.delete(stashedReport);
    137 
    138          // We're still hung, so move the report back to the active
    139          // list.
    140          this._activeReports.add(report);
    141          break;
    142        }
    143      }
    144    }, this.WAIT_EXPIRATION_TIME);
    145 
    146    this._pausedReports.set(report, timer);
    147  }
    148 
    149  /**
    150   * construct an information bundle
    151   */
    152  notifyReport(report) {
    153    this.eventDispatcher.sendRequest({
    154      type: "GeckoView:HangReport",
    155      hangId: this._reportLookupIndex.get(report),
    156      scriptFileName: report.scriptFileName,
    157    });
    158  }
    159 
    160  /**
    161   * Handle a potentially new hang report.
    162   */
    163  reportHang(report) {
    164    // if we aren't enabled then default to stopping the script
    165    if (!this.enabled) {
    166      this.stopHang(report);
    167      return;
    168    }
    169 
    170    // if we have already notified, remind
    171    if (this._activeReports.has(report)) {
    172      this.notifyReport(report);
    173      return;
    174    }
    175 
    176    // If this hang was already reported and paused by the user then ignore it.
    177    if (this._pausedReports.has(report)) {
    178      return;
    179    }
    180 
    181    const index = this._nextIndex++;
    182    this._reportLookupIndex.set(report, index);
    183    this._reportIndex.set(index, report);
    184    this._activeReports.add(report);
    185 
    186    // Actually notify the new report
    187    this.notifyReport(report);
    188  }
    189 
    190  clearHang(report) {
    191    this._activeReports.delete(report);
    192 
    193    const timer = this._pausedReports.get(report);
    194    if (timer) {
    195      this.window.clearTimeout(timer);
    196    }
    197    this._pausedReports.delete(report);
    198 
    199    if (this._reportLookupIndex.has(report)) {
    200      const index = this._reportLookupIndex.get(report);
    201      this._reportIndex.delete(index);
    202    }
    203    this._reportLookupIndex.delete(report);
    204    report.userCanceled();
    205  }
    206 }
    207 
    208 const { debug, warn } = GeckoViewProcessHangMonitor.initLogging(
    209  "GeckoViewProcessHangMonitor"
    210 );