 this._promiseFinished = new Promise(resolve => {
      this._resolveFinished = resolve;
    });
    this._promiseFinished.then(() => {
      try {
        this._isRestored = true;
        Services.obs.notifyObservers(null, this.RESTORED_TOPIC);

        if (this._latestRestoredTimeStamp == this._startTimeStamp) {
          // Apparently, we haven't restored any tab.
          return;
        }

        // Once we are done restoring tabs, update Telemetry.
        let delta = this._latestRestoredTimeStamp - this._startTimeStamp;
        if (isAutoRestore) {
          Glean.sessionRestore.autoRestoreDurationUntilEagerTabsRestored.accumulateSingleSample(
            delta
          );
        } else {
          Glean.sessionRestore.manualRestoreDurationUntilEagerTabsRestored.accumulateSingleSample(
            delta
          );
        }
        Glean.sessionRestore.numberOfEagerTabsRestored.accumulateSingleSample(
          this._totalNumberOfEagerTabs
        );
        Glean.sessionRestore.numberOfTabsRestored.accumulateSingleSample(
          this._totalNumberOfTabs
        );
        Glean.sessionRestore.numberOfWindowsRestored.accumulateSingleSample(
          this._totalNumberOfWindows
        );

        // Reset
        this._startTimeStamp = null;
      } catch (ex) {
        console.error("StartupPerformance: error after resolving promise", ex);
      }
    });
  },

  _startTimer() {
    if (this._hasFired) {
      return;
    }
    if (this._deadlineTimer) {
      lazy.clearTimeout(this._deadlineTimer);
    }
    this._deadlineTimer = lazy.setTimeout(() => {
      try {
        this._resolveFinished();
      } catch (ex) {
        console.error("StartupPerformance: Error in timeout handler", ex);
      } finally {
        // Clean up.
        this._deadlineTimer = null;
        this._hasFired = true;
        this._resolveFinished = null;
        Services.obs.removeObserver(
          this,
          "sessionstore-single-window-restored"
        );
      }
    }, COLLECT_RESULTS_AFTER_MS);
  },

  observe(subject, topic) {
    try {
      switch (topic) {
        case "sessionstore-restoring-on-startup":
          this._onRestorationStarts(true);
          break;
        case "sessionstore-initiating-manual-restore":
          this._onRestorationStarts(false);
          break;
        case "sessionstore-single-window-restored":
          {
            // Session Restore has just opened a window with (initially empty) tabs.
            // Some of these tabs will be restored eagerly, while others will be
            // restored on demand. The process becomes usable only when all windows
            // have finished restored their eager tabs.
            //
            // While it would be possible to track the restoration of each tab
            // from within SessionRestore to determine exactly when the process
            // becomes usable, experience shows that this is too invasive. Rather,
            // we employ the following heuristic:
            // - we maintain a timer of `COLLECT_RESULTS_AFTER_MS` that we expect
            //   will be triggered only once all tabs have been restored;
            // - whenever we restore a new window (hence a bunch of eager tabs),
            //   we postpone the timer to ensure that the new eager tabs have
            //   `COLLECT_RESULTS_AFTER_MS` to be restored;
            // - whenever a tab is restored, we update
            //   `this._latestRestoredTimeStamp`;
            // - after `COLLECT_RESULTS_AFTER_MS`, we collect the final version
            //   of `this._latestRestoredTimeStamp`, and use it to determine the
            //   entire duration of the collection.
            //
            // Note that this heuristic may be inaccurate if a user clicks
            // immediately on a restore-on-demand tab before the end of
            // `COLLECT_RESULTS_AFTER_MS`. We assume that this will not
            // affect too much the results.
            //
            // Reset the delay, to give the tabs a little (more) time to restore.
            this._startTimer();

            this._totalNumberOfWindows += 1;

            // Observe the restoration of all tabs. We assume that all tabs of this
            // window will have been restored before `COLLECT_RESULTS_AFTER_MS`.
            // The last call to `observer` will let us determine how long it took
            // to reach that point.
            let win = subject;

            let observer = event => {
              // We don't care about tab restorations that are due to
              // a browser flipping from out-of-main-process to in-main-process
              // or vice-versa. We only care about restorations that are due
              // to the user switching to a lazily restored tab, or for tabs
              // that are restoring eagerly.
              if (!event.detail.isRemotenessUpdate) {
                ChromeUtils.addProfilerMarker("SSTabRestored");
                this._latestRestoredTimeStamp = Date.now();
                this._totalNumberOfEagerTabs += 1;
              }
            };
            win.gBrowser.tabContainer.addEventListener(
              "SSTabRestored",
              observer
            );
            this._totalNumberOfTabs += win.gBrowser.tabContainer.itemCount;

            // Once we have finished collecting the results, clean up the observers.
            this._promiseFinished.then(() => {
              if (!win.gBrowser.tabContainer) {
                // May be undefined during shutdown and/or some tests.
                return;
              }
              win.gBrowser.tabContainer.removeEventListener(
                "SSTabRestored",
                observer
              );
            });
          }
          break;
        default:
          throw new Error(`Unexpected topic ${topic}`);
      }
    } catch (ex) {
      console.error("StartupPerformance error", ex, ex.stack);
      throw ex;
    }
  },
};
PK