Skip to content

Commit

Permalink
Merge pull request #66 from PaulBlanche/fix/browsersession-reload
Browse files Browse the repository at this point in the history
fix: browsersession reload
  • Loading branch information
PaulBlanche authored Aug 26, 2023
2 parents 16770d0 + 062e4bd commit 8cc6813
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 6 deletions.
106 changes: 100 additions & 6 deletions src/runtime/session/History.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Navigator } from "./Navigator.ts";
import { Navigator, SerializedNavigator } from "./Navigator.ts";
import { NavigatorConfig } from "./Navigator.ts";
import { isUrlForSameDocument } from "./utils.ts";

Expand All @@ -14,7 +14,12 @@ export class History {
throw new Error("History was already initialised");
}

HistoryInternal.instance = new HistoryInternal(config);
const restoredHistory = restoreHistory();
if (restoredHistory) {
HistoryInternal.instance = HistoryInternal.deserialize(restoredHistory, config);
} else {
HistoryInternal.instance = new HistoryInternal(config);
}
}

static observe() {
Expand Down Expand Up @@ -42,21 +47,69 @@ export class History {
}
}

type SerializedHistory = {
stack: SerializedNavigator[];
index: number;
current: number;
};

class HistoryInternal {
_stack: Navigator[];
_index: number;
_minIndex: number;
_current: number;
_observing: boolean;
_config: NavigatorConfig;
_id: string;

static instance?: HistoryInternal;

static deserialize({ stack, index, current }: SerializedHistory, config: NavigatorConfig) {
const history = new HistoryInternal(config);
history._stack = stack.map((serialized) => Navigator.deserialize(serialized, config));
history._index = index;
history._current = current;

switch (getPerformanceNavigationType()) {
case "navigate": {
if (history._index === history._current) {
history._stack.push(new Navigator(new URL(location.href), config));
history._current += 1;
} else {
history._stack = history._stack.slice(0, history._current + 1);
history._stack[history._current] = new Navigator(
new URL(location.href),
config,
);
}
break;
}
case "back_forward": {
if (history._index === history._current) {
history._current += 1;
}
}
}

history._minIndex = history._stack.length - 1;

return history;
}

constructor(config: NavigatorConfig) {
this._config = config;
this._stack = [new Navigator(new URL(location.href), this._config)];
this._index = 0;
this._current = 0;
this._minIndex = 0;
this._observing = false;
this._id = String(Math.random());
}

serialize(): SerializedHistory {
return {
stack: this._stack.map((navigator) => navigator.serialize()),
index: this._index,
current: this._current,
};
}

observe() {
Expand All @@ -65,9 +118,17 @@ class HistoryInternal {
}
this._observing = true;

const onBeforeUnload = () => {
persistHistory(this.serialize());
removeEventListener("beforeunload", onBeforeUnload);
};
addEventListener("beforeunload", onBeforeUnload);

addEventListener("popstate", (event) => {
this._index = this._current;

const previous = this._stack[this._index];
const nextIndex = event.state?.index ?? 0;
const nextIndex = event.state?.index ?? this._minIndex;
const current = this._stack[nextIndex];

if (previous === undefined || current === undefined) {
Expand All @@ -84,6 +145,10 @@ class HistoryInternal {
}

this._index = nextIndex;
this._current = this._index;
if (this._index === this._minIndex) {
this._index -= 1;
}

event.preventDefault();

Expand All @@ -104,10 +169,39 @@ class HistoryInternal {
}

push(navigator: Navigator) {
this._index = this._current;
const stackIndex = this._index + 1;
this._stack = this._stack.slice(0, stackIndex);
this._stack.push(navigator);
this._index = stackIndex;
history.pushState({ index: stackIndex, id: this._id }, "", navigator.url);
this._current = this._index;
history.pushState({ index: this._index }, "", navigator.url);
}
}

const SERIALIZED_HISTORY_KEY = "frugal_browsersession_history";

function persistHistory(serializedHistory: SerializedHistory) {
sessionStorage.setItem(SERIALIZED_HISTORY_KEY, JSON.stringify(serializedHistory));
}

function restoreHistory(): SerializedHistory | undefined {
const persistedHistory = sessionStorage.getItem(SERIALIZED_HISTORY_KEY);
if (persistedHistory) {
return JSON.parse(persistedHistory);
}
}

function getPerformanceNavigationType(): NavigationTimingType | undefined {
try {
const entries = performance.getEntriesByType("navigation") as PerformanceNavigationTiming[];
return entries[0].type;
} catch {
if (window.performance.navigation.type === window.performance.navigation.TYPE_BACK_FORWARD) {
return "back_forward";
}
if (window.performance.navigation.type === window.performance.navigation.TYPE_NAVIGATE) {
return "navigate";
}
}
}
21 changes: 21 additions & 0 deletions src/runtime/session/Navigator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,39 @@ export type NavigatorConfig = {
viewTransition?: boolean;
};

export type SerializedNavigator = {
url: string;
scroll?: { x: number; y: number };
shouldRestoreScroll: boolean;
};

export class Navigator {
_url: URL;
_config: NavigatorConfig;
scroll?: { x: number; y: number };
_shouldRestoreScroll: boolean;

static deserialize({ url, scroll, shouldRestoreScroll }: SerializedNavigator, config: NavigatorConfig) {
const navigator = new Navigator(new URL(url), config);
navigator._shouldRestoreScroll = shouldRestoreScroll;
navigator.scroll = scroll;
return navigator;
}

constructor(url: URL, config: NavigatorConfig) {
this._url = url;
this._config = config;
this._shouldRestoreScroll = false;
}

serialize(): SerializedNavigator {
return {
url: this._url.href,
scroll: this.scroll,
shouldRestoreScroll: this._shouldRestoreScroll,
};
}

saveScroll() {
this.scroll = { x: scrollX, y: scrollY };
}
Expand Down

0 comments on commit 8cc6813

Please sign in to comment.