tor-browser

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

StartupPerformance.sys.mjs (8767B)


      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 file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 const lazy = {};
      6 ChromeUtils.defineESModuleGetters(lazy, {
      7  clearTimeout: "resource://gre/modules/Timer.sys.mjs",
      8  setTimeout: "resource://gre/modules/Timer.sys.mjs",
      9 });
     10 
     11 const COLLECT_RESULTS_AFTER_MS = 10000;
     12 
     13 const OBSERVED_TOPICS = [
     14  "sessionstore-restoring-on-startup",
     15  "sessionstore-initiating-manual-restore",
     16 ];
     17 
     18 export var StartupPerformance = {
     19  /**
     20   * Once we have finished restoring initial tabs, we broadcast on this topic.
     21   */
     22  RESTORED_TOPIC: "sessionstore-finished-restoring-initial-tabs",
     23 
     24  // Instant at which we have started restoration (notification "sessionstore-restoring-on-startup")
     25  _startTimeStamp: null,
     26 
     27  // Latest instant at which we have finished restoring a tab (DOM event "SSTabRestored")
     28  _latestRestoredTimeStamp: null,
     29 
     30  // A promise resolved once we have finished restoring all the startup tabs.
     31  _promiseFinished: null,
     32 
     33  // Function `resolve()` for `_promiseFinished`.
     34  _resolveFinished: null,
     35 
     36  // A timer
     37  _deadlineTimer: null,
     38 
     39  // `true` once the timer has fired
     40  _hasFired: false,
     41 
     42  // `true` once we are restored
     43  _isRestored: false,
     44 
     45  // Statistics on the session we need to restore.
     46  _totalNumberOfEagerTabs: 0,
     47  _totalNumberOfTabs: 0,
     48  _totalNumberOfWindows: 0,
     49 
     50  init() {
     51    for (let topic of OBSERVED_TOPICS) {
     52      Services.obs.addObserver(this, topic);
     53    }
     54  },
     55 
     56  /**
     57   * Return the timestamp at which we finished restoring the latest tab.
     58   *
     59   * This information is not really interesting until we have finished restoring
     60   * tabs.
     61   */
     62  get latestRestoredTimeStamp() {
     63    return this._latestRestoredTimeStamp;
     64  },
     65 
     66  /**
     67   * `true` once we have finished restoring startup tabs.
     68   */
     69  get isRestored() {
     70    return this._isRestored;
     71  },
     72 
     73  // Called when restoration starts.
     74  // Record the start timestamp, setup the timer and `this._promiseFinished`.
     75  // Behavior is unspecified if there was already an ongoing measure.
     76  _onRestorationStarts(isAutoRestore) {
     77    ChromeUtils.addProfilerMarker("_onRestorationStarts");
     78    this._latestRestoredTimeStamp = this._startTimeStamp = Date.now();
     79    this._totalNumberOfEagerTabs = 0;
     80    this._totalNumberOfTabs = 0;
     81    this._totalNumberOfWindows = 0;
     82 
     83    // While we may restore several sessions in a single run of the browser,
     84    // that's a very unusual case, and not really worth measuring, so let's
     85    // stop listening for further restorations.
     86 
     87    for (let topic of OBSERVED_TOPICS) {
     88      Services.obs.removeObserver(this, topic);
     89    }
     90 
     91    Services.obs.addObserver(this, "sessionstore-single-window-restored");
     92    this._promiseFinished = new Promise(resolve => {
     93      this._resolveFinished = resolve;
     94    });
     95    this._promiseFinished.then(() => {
     96      try {
     97        this._isRestored = true;
     98        Services.obs.notifyObservers(null, this.RESTORED_TOPIC);
     99 
    100        if (this._latestRestoredTimeStamp == this._startTimeStamp) {
    101          // Apparently, we haven't restored any tab.
    102          return;
    103        }
    104 
    105        // Once we are done restoring tabs, update Telemetry.
    106        let delta = this._latestRestoredTimeStamp - this._startTimeStamp;
    107        if (isAutoRestore) {
    108          Glean.sessionRestore.autoRestoreDurationUntilEagerTabsRestored.accumulateSingleSample(
    109            delta
    110          );
    111        } else {
    112          Glean.sessionRestore.manualRestoreDurationUntilEagerTabsRestored.accumulateSingleSample(
    113            delta
    114          );
    115        }
    116        Glean.sessionRestore.numberOfEagerTabsRestored.accumulateSingleSample(
    117          this._totalNumberOfEagerTabs
    118        );
    119        Glean.sessionRestore.numberOfTabsRestored.accumulateSingleSample(
    120          this._totalNumberOfTabs
    121        );
    122        Glean.sessionRestore.numberOfWindowsRestored.accumulateSingleSample(
    123          this._totalNumberOfWindows
    124        );
    125 
    126        // Reset
    127        this._startTimeStamp = null;
    128      } catch (ex) {
    129        console.error("StartupPerformance: error after resolving promise", ex);
    130      }
    131    });
    132  },
    133 
    134  _startTimer() {
    135    if (this._hasFired) {
    136      return;
    137    }
    138    if (this._deadlineTimer) {
    139      lazy.clearTimeout(this._deadlineTimer);
    140    }
    141    this._deadlineTimer = lazy.setTimeout(() => {
    142      try {
    143        this._resolveFinished();
    144      } catch (ex) {
    145        console.error("StartupPerformance: Error in timeout handler", ex);
    146      } finally {
    147        // Clean up.
    148        this._deadlineTimer = null;
    149        this._hasFired = true;
    150        this._resolveFinished = null;
    151        Services.obs.removeObserver(
    152          this,
    153          "sessionstore-single-window-restored"
    154        );
    155      }
    156    }, COLLECT_RESULTS_AFTER_MS);
    157  },
    158 
    159  observe(subject, topic) {
    160    try {
    161      switch (topic) {
    162        case "sessionstore-restoring-on-startup":
    163          this._onRestorationStarts(true);
    164          break;
    165        case "sessionstore-initiating-manual-restore":
    166          this._onRestorationStarts(false);
    167          break;
    168        case "sessionstore-single-window-restored":
    169          {
    170            // Session Restore has just opened a window with (initially empty) tabs.
    171            // Some of these tabs will be restored eagerly, while others will be
    172            // restored on demand. The process becomes usable only when all windows
    173            // have finished restored their eager tabs.
    174            //
    175            // While it would be possible to track the restoration of each tab
    176            // from within SessionRestore to determine exactly when the process
    177            // becomes usable, experience shows that this is too invasive. Rather,
    178            // we employ the following heuristic:
    179            // - we maintain a timer of `COLLECT_RESULTS_AFTER_MS` that we expect
    180            //   will be triggered only once all tabs have been restored;
    181            // - whenever we restore a new window (hence a bunch of eager tabs),
    182            //   we postpone the timer to ensure that the new eager tabs have
    183            //   `COLLECT_RESULTS_AFTER_MS` to be restored;
    184            // - whenever a tab is restored, we update
    185            //   `this._latestRestoredTimeStamp`;
    186            // - after `COLLECT_RESULTS_AFTER_MS`, we collect the final version
    187            //   of `this._latestRestoredTimeStamp`, and use it to determine the
    188            //   entire duration of the collection.
    189            //
    190            // Note that this heuristic may be inaccurate if a user clicks
    191            // immediately on a restore-on-demand tab before the end of
    192            // `COLLECT_RESULTS_AFTER_MS`. We assume that this will not
    193            // affect too much the results.
    194            //
    195            // Reset the delay, to give the tabs a little (more) time to restore.
    196            this._startTimer();
    197 
    198            this._totalNumberOfWindows += 1;
    199 
    200            // Observe the restoration of all tabs. We assume that all tabs of this
    201            // window will have been restored before `COLLECT_RESULTS_AFTER_MS`.
    202            // The last call to `observer` will let us determine how long it took
    203            // to reach that point.
    204            let win = subject;
    205 
    206            let observer = event => {
    207              // We don't care about tab restorations that are due to
    208              // a browser flipping from out-of-main-process to in-main-process
    209              // or vice-versa. We only care about restorations that are due
    210              // to the user switching to a lazily restored tab, or for tabs
    211              // that are restoring eagerly.
    212              if (!event.detail.isRemotenessUpdate) {
    213                ChromeUtils.addProfilerMarker("SSTabRestored");
    214                this._latestRestoredTimeStamp = Date.now();
    215                this._totalNumberOfEagerTabs += 1;
    216              }
    217            };
    218            win.gBrowser.tabContainer.addEventListener(
    219              "SSTabRestored",
    220              observer
    221            );
    222            this._totalNumberOfTabs += win.gBrowser.tabContainer.itemCount;
    223 
    224            // Once we have finished collecting the results, clean up the observers.
    225            this._promiseFinished.then(() => {
    226              if (!win.gBrowser.tabContainer) {
    227                // May be undefined during shutdown and/or some tests.
    228                return;
    229              }
    230              win.gBrowser.tabContainer.removeEventListener(
    231                "SSTabRestored",
    232                observer
    233              );
    234            });
    235          }
    236          break;
    237        default:
    238          throw new Error(`Unexpected topic ${topic}`);
    239      }
    240    } catch (ex) {
    241      console.error("StartupPerformance error", ex, ex.stack);
    242      throw ex;
    243    }
    244  },
    245 };