tor-browser

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

GeckoViewSessionStore.sys.mjs (7575B)


      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 import { GeckoViewUtils } from "resource://gre/modules/GeckoViewUtils.sys.mjs";
      6 
      7 const lazy = {};
      8 
      9 ChromeUtils.defineESModuleGetters(lazy, {
     10  SessionHistory: "resource://gre/modules/sessionstore/SessionHistory.sys.mjs",
     11 });
     12 
     13 const { debug, warn } = GeckoViewUtils.initLogging("SessionStore");
     14 const kNoIndex = Number.MAX_SAFE_INTEGER;
     15 const kLastIndex = Number.MAX_SAFE_INTEGER - 1;
     16 
     17 class SHistoryListener {
     18  constructor(browsingContext) {
     19    this.QueryInterface = ChromeUtils.generateQI([
     20      "nsISHistoryListener",
     21      "nsISupportsWeakReference",
     22    ]);
     23 
     24    this._browserId = browsingContext.browserId;
     25    this._fromIndex = kNoIndex;
     26  }
     27 
     28  unregister(permanentKey) {
     29    const bc = BrowsingContext.getCurrentTopByBrowserId(this._browserId);
     30    bc?.sessionHistory?.removeSHistoryListener(this);
     31    GeckoViewSessionStore._browserSHistoryListener?.delete(permanentKey);
     32  }
     33 
     34  collect(
     35    permanentKey, // eslint-disable-line no-shadow
     36    browsingContext, // eslint-disable-line no-shadow
     37    { collectFull = true, writeToCache = false }
     38  ) {
     39    // Don't bother doing anything if we haven't seen any navigations.
     40    if (!collectFull && this._fromIndex === kNoIndex) {
     41      return null;
     42    }
     43 
     44    // In a private session, when the first loaded uri is one of the about pages, the
     45    // browsingContext.currentURI is null which causes a crash (see Bug 1928295).
     46    // TODO: Remove this if check when Bug 1915362 gets implemented where the partial
     47    // state update will be fixing this issue. See Bug 1933630.
     48    if (!browsingContext.currentURI?.spec) {
     49      return null;
     50    }
     51 
     52    const fromIndex = collectFull ? -1 : this._fromIndex;
     53    this._fromIndex = kNoIndex;
     54 
     55    const historychange = lazy.SessionHistory.collectFromParent(
     56      browsingContext.currentURI?.spec,
     57      true, // Bug 1704574
     58      browsingContext.sessionHistory,
     59      fromIndex
     60    );
     61 
     62    if (writeToCache) {
     63      const win =
     64        browsingContext.embedderElement?.ownerGlobal ||
     65        browsingContext.currentWindowGlobal?.browsingContext?.window;
     66 
     67      GeckoViewSessionStore.onTabStateUpdate(permanentKey, win, {
     68        data: { historychange },
     69      });
     70    }
     71 
     72    return historychange;
     73  }
     74 
     75  collectFrom(index) {
     76    if (this._fromIndex <= index) {
     77      // If we already know that we need to update history from index N we
     78      // can ignore any changes that happened with an element with index
     79      // larger than N.
     80      //
     81      // Note: initially we use kNoIndex which is MAX_SAFE_INTEGER which
     82      // means we don't ignore anything here, and in case of navigation in
     83      // the history back and forth cases we use kLastIndex which ignores
     84      // only the subsequent navigations, but not any new elements added.
     85      return;
     86    }
     87 
     88    const bc = BrowsingContext.getCurrentTopByBrowserId(this._browserId);
     89    if (bc?.embedderElement?.frameLoader) {
     90      this._fromIndex = index;
     91 
     92      // Queue a tab state update on the |browser.sessionstore.interval|
     93      // timer. We'll call this.collect() when we receive the update.
     94      bc.embedderElement.frameLoader.requestSHistoryUpdate();
     95    }
     96  }
     97 
     98  OnHistoryNewEntry(newURI, oldIndex) {
     99    // We use oldIndex - 1 to collect the current entry as well. This makes
    100    // sure to collect any changes that were made to the entry while the
    101    // document was active.
    102    this.collectFrom(oldIndex == -1 ? oldIndex : oldIndex - 1);
    103  }
    104  OnHistoryGotoIndex() {
    105    this.collectFrom(kLastIndex);
    106  }
    107  OnHistoryPurge() {
    108    this.collectFrom(-1);
    109  }
    110  OnHistoryReload() {
    111    this.collectFrom(-1);
    112    return true;
    113  }
    114  OnHistoryReplaceEntry() {
    115    this.collectFrom(-1);
    116  }
    117 }
    118 
    119 export var GeckoViewSessionStore = {
    120  // For each <browser> element, records the SHistoryListener.
    121  _browserSHistoryListener: new WeakMap(),
    122 
    123  observe(aSubject, aTopic) {
    124    debug`observe ${aTopic}`;
    125 
    126    switch (aTopic) {
    127      case "browsing-context-did-set-embedder": {
    128        if (aSubject === aSubject.top && aSubject.isContent) {
    129          const permanentKey = aSubject.embedderElement?.permanentKey;
    130          if (permanentKey) {
    131            this.maybeRecreateSHistoryListener(permanentKey, aSubject);
    132          }
    133        }
    134        break;
    135      }
    136      case "browsing-context-discarded": {
    137        const permanentKey = aSubject?.embedderElement?.permanentKey;
    138        if (permanentKey) {
    139          this._browserSHistoryListener
    140            .get(permanentKey)
    141            ?.unregister(permanentKey);
    142        }
    143        break;
    144      }
    145    }
    146  },
    147 
    148  onTabStateUpdate(permanentKey, win, data) {
    149    win.WindowEventDispatcher.sendRequest({
    150      type: "GeckoView:StateUpdated",
    151      data: data.data,
    152    });
    153  },
    154 
    155  getOrCreateSHistoryListener(permanentKey, browsingContext) {
    156    if (!permanentKey || browsingContext !== browsingContext.top) {
    157      return null;
    158    }
    159 
    160    const listener = this._browserSHistoryListener.get(permanentKey);
    161    if (listener) {
    162      return listener;
    163    }
    164 
    165    return this.createSHistoryListener(permanentKey, browsingContext, false);
    166  },
    167 
    168  maybeRecreateSHistoryListener(permanentKey, browsingContext) {
    169    const listener = this._browserSHistoryListener.get(permanentKey);
    170    if (!listener || listener._browserId != browsingContext.browserId) {
    171      listener?.unregister(permanentKey);
    172      this.createSHistoryListener(permanentKey, browsingContext, true);
    173    }
    174  },
    175 
    176  createSHistoryListener(permanentKey, browsingContext, collectImmediately) {
    177    const sessionHistory = browsingContext.sessionHistory;
    178    if (!sessionHistory) {
    179      return null;
    180    }
    181 
    182    const listener = new SHistoryListener(browsingContext);
    183    sessionHistory.addSHistoryListener(listener);
    184    this._browserSHistoryListener.set(permanentKey, listener);
    185 
    186    if (
    187      collectImmediately &&
    188      (!(browsingContext.currentURI?.spec === "about:blank") ||
    189        sessionHistory.count !== 0)
    190    ) {
    191      listener.collect(permanentKey, browsingContext, { writeToCache: true });
    192    }
    193 
    194    return listener;
    195  },
    196 
    197  updateSessionStoreFromTabListener(
    198    browser,
    199    browsingContext,
    200    permanentKey,
    201    update,
    202    forStorage = false
    203  ) {
    204    permanentKey = browser?.permanentKey ?? permanentKey;
    205    if (!permanentKey) {
    206      return;
    207    }
    208 
    209    if (browsingContext.isReplaced) {
    210      return;
    211    }
    212 
    213    const listener = this.getOrCreateSHistoryListener(
    214      permanentKey,
    215      browsingContext
    216    );
    217 
    218    if (listener) {
    219      const historychange =
    220        // If it is not the scheduled update (tab closed, window closed etc),
    221        // try to store the loading non-web-controlled page opened in _blank
    222        // first.
    223        (forStorage &&
    224          lazy.SessionHistory.collectNonWebControlledBlankLoadingSession(
    225            browsingContext
    226          )) ||
    227        listener.collect(permanentKey, browsingContext, {
    228          // TODO: See Bug 1915362 where partial history collection will be implemented for better
    229          // performance and uncomment the line below. See Bug 1933630.
    230          // collectFull: !!update.sHistoryNeeded,
    231          writeToCache: false,
    232        });
    233 
    234      if (historychange) {
    235        update.data.historychange = historychange;
    236      }
    237    }
    238 
    239    const win =
    240      browsingContext.embedderElement?.ownerGlobal ||
    241      browsingContext.currentWindowGlobal?.browsingContext?.window;
    242 
    243    this.onTabStateUpdate(permanentKey, win, update);
    244  },
    245 };