summaryrefslogtreecommitdiffstats
path: root/mobile/android/modules/geckoview/GeckoViewSessionStore.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/modules/geckoview/GeckoViewSessionStore.sys.mjs')
-rw-r--r--mobile/android/modules/geckoview/GeckoViewSessionStore.sys.mjs187
1 files changed, 187 insertions, 0 deletions
diff --git a/mobile/android/modules/geckoview/GeckoViewSessionStore.sys.mjs b/mobile/android/modules/geckoview/GeckoViewSessionStore.sys.mjs
new file mode 100644
index 0000000000..584429295e
--- /dev/null
+++ b/mobile/android/modules/geckoview/GeckoViewSessionStore.sys.mjs
@@ -0,0 +1,187 @@
+/* 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 { GeckoViewUtils } from "resource://gre/modules/GeckoViewUtils.sys.mjs";
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ SessionHistory: "resource://gre/modules/sessionstore/SessionHistory.sys.mjs",
+});
+
+const { debug, warn } = GeckoViewUtils.initLogging("SessionStore");
+const kNoIndex = Number.MAX_SAFE_INTEGER;
+const kLastIndex = Number.MAX_SAFE_INTEGER - 1;
+
+class SHistoryListener {
+ constructor(browsingContext) {
+ this.QueryInterface = ChromeUtils.generateQI([
+ "nsISHistoryListener",
+ "nsISupportsWeakReference",
+ ]);
+
+ this._browserId = browsingContext.browserId;
+ this._fromIndex = kNoIndex;
+ }
+
+ unregister(permanentKey) {
+ const bc = BrowsingContext.getCurrentTopByBrowserId(this._browserId);
+ bc?.sessionHistory?.removeSHistoryListener(this);
+ GeckoViewSessionStore._browserSHistoryListener?.delete(permanentKey);
+ }
+
+ collect(
+ permanentKey, // eslint-disable-line no-shadow
+ browsingContext, // eslint-disable-line no-shadow
+ { collectFull = true, writeToCache = false }
+ ) {
+ // Don't bother doing anything if we haven't seen any navigations.
+ if (!collectFull && this._fromIndex === kNoIndex) {
+ return null;
+ }
+
+ const fromIndex = collectFull ? -1 : this._fromIndex;
+ this._fromIndex = kNoIndex;
+
+ const historychange = lazy.SessionHistory.collectFromParent(
+ browsingContext.currentURI?.spec,
+ true, // Bug 1704574
+ browsingContext.sessionHistory,
+ fromIndex
+ );
+
+ if (writeToCache) {
+ const win =
+ browsingContext.embedderElement?.ownerGlobal ||
+ browsingContext.currentWindowGlobal?.browsingContext?.window;
+
+ GeckoViewSessionStore.onTabStateUpdate(permanentKey, win, {
+ data: { historychange },
+ });
+ }
+
+ return historychange;
+ }
+
+ collectFrom(index) {
+ if (this._fromIndex <= index) {
+ // If we already know that we need to update history from index N we
+ // can ignore any changes that happened with an element with index
+ // larger than N.
+ //
+ // Note: initially we use kNoIndex which is MAX_SAFE_INTEGER which
+ // means we don't ignore anything here, and in case of navigation in
+ // the history back and forth cases we use kLastIndex which ignores
+ // only the subsequent navigations, but not any new elements added.
+ return;
+ }
+
+ const bc = BrowsingContext.getCurrentTopByBrowserId(this._browserId);
+ if (bc?.embedderElement?.frameLoader) {
+ this._fromIndex = index;
+
+ // Queue a tab state update on the |browser.sessionstore.interval|
+ // timer. We'll call this.collect() when we receive the update.
+ bc.embedderElement.frameLoader.requestSHistoryUpdate();
+ }
+ }
+
+ OnHistoryNewEntry(newURI, oldIndex) {
+ // We use oldIndex - 1 to collect the current entry as well. This makes
+ // sure to collect any changes that were made to the entry while the
+ // document was active.
+ this.collectFrom(oldIndex == -1 ? oldIndex : oldIndex - 1);
+ }
+ OnHistoryGotoIndex() {
+ this.collectFrom(kLastIndex);
+ }
+ OnHistoryPurge() {
+ this.collectFrom(-1);
+ }
+ OnHistoryReload() {
+ this.collectFrom(-1);
+ return true;
+ }
+ OnHistoryReplaceEntry() {
+ this.collectFrom(-1);
+ }
+}
+
+export var GeckoViewSessionStore = {
+ // For each <browser> element, records the SHistoryListener.
+ _browserSHistoryListener: new WeakMap(),
+
+ observe(aSubject, aTopic, aData) {
+ debug`observe ${aTopic}`;
+
+ switch (aTopic) {
+ case "browsing-context-did-set-embedder": {
+ if (
+ aSubject &&
+ aSubject === aSubject.top &&
+ aSubject.isContent &&
+ aSubject.embedderElement &&
+ aSubject.embedderElement.permanentKey
+ ) {
+ const permanentKey = aSubject.embedderElement.permanentKey;
+ this._browserSHistoryListener
+ .get(permanentKey)
+ ?.unregister(permanentKey);
+
+ this.getOrCreateSHistoryListener(permanentKey, aSubject, true);
+ }
+ break;
+ }
+ case "browsing-context-discarded":
+ const permanentKey = aSubject?.embedderElement?.permanentKey;
+ if (permanentKey) {
+ this._browserSHistoryListener
+ .get(permanentKey)
+ ?.unregister(permanentKey);
+ }
+ break;
+ }
+ },
+
+ onTabStateUpdate(permanentKey, win, data) {
+ win.WindowEventDispatcher.sendRequest({
+ type: "GeckoView:StateUpdated",
+ data: data.data,
+ });
+ },
+
+ getOrCreateSHistoryListener(
+ permanentKey,
+ browsingContext,
+ collectImmediately = false
+ ) {
+ if (!permanentKey || browsingContext !== browsingContext.top) {
+ return null;
+ }
+
+ const sessionHistory = browsingContext.sessionHistory;
+ if (!sessionHistory) {
+ return null;
+ }
+
+ let listener = this._browserSHistoryListener.get(permanentKey);
+ if (listener) {
+ return listener;
+ }
+
+ listener = new SHistoryListener(browsingContext);
+ sessionHistory.addSHistoryListener(listener);
+ this._browserSHistoryListener.set(permanentKey, listener);
+
+ if (
+ collectImmediately &&
+ (!(browsingContext.currentURI?.spec === "about:blank") ||
+ sessionHistory.count !== 0)
+ ) {
+ listener.collect(permanentKey, browsingContext, { writeToCache: true });
+ }
+
+ return listener;
+ },
+};