diff --git a/src/browser/html/History.zig b/src/browser/html/History.zig
new file mode 100644
index 00000000..eabae11d
--- /dev/null
+++ b/src/browser/html/History.zig
@@ -0,0 +1,118 @@
+// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
+//
+// Francis Bouvier
+// Pierre Tachoire
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+const std = @import("std");
+
+const Env = @import("../env.zig").Env;
+const Page = @import("../page.zig").Page;
+
+// https://html.spec.whatwg.org/multipage/nav-history-apis.html#the-history-interface
+const History = @This();
+
+const HistoryEntry = struct {
+ url: ?[]const u8,
+ // Serialized Env.JsObject
+ state: []u8,
+};
+
+const ScrollRestorationMode = enum {
+ auto,
+ manual,
+
+ pub fn fromString(str: []const u8) ?ScrollRestorationMode {
+ for (std.enums.values(ScrollRestorationMode)) |mode| {
+ if (std.ascii.eqlIgnoreCase(str, @tagName(mode))) {
+ return mode;
+ }
+ } else {
+ return null;
+ }
+ }
+};
+
+scrollRestoration: ScrollRestorationMode = .auto,
+stack: std.ArrayListUnmanaged(HistoryEntry) = .empty,
+current: ?usize = null,
+
+pub fn get_length(self: *History) u32 {
+ return @intCast(self.stack.items.len);
+}
+
+pub fn get_scrollRestoration(self: *History) []const u8 {
+ return switch (self.scrollRestoration) {
+ .auto => "auto",
+ .manual => "manual",
+ };
+}
+
+pub fn set_scrollRestoration(self: *History, mode: []const u8) void {
+ self.scrollRestoration = ScrollRestorationMode.fromString(mode) orelse self.scrollRestoration;
+}
+
+pub fn get_state(self: *History, page: *Page) !?Env.JsObject {
+ if (self.current) |curr| {
+ const entry = self.stack.items[curr];
+ const object = try Env.JsObject.fromJson(page.main_context, entry.state);
+ return object;
+ } else {
+ return null;
+ }
+}
+
+pub fn _pushState(self: *History, state: Env.JsObject, _: ?[]const u8, url: ?[]const u8, page: *Page) !void {
+ const json = try state.toJson(page.arena);
+ const entry = HistoryEntry{ .state = json, .url = url };
+ try self.stack.append(page.session.arena, entry);
+ self.current = self.stack.items.len;
+}
+
+// TODO implement the function
+// data must handle any argument. We could expect a std.json.Value but
+// https://github.com/lightpanda-io/zig-js-runtime/issues/267 is missing.
+pub fn _replaceState(self: *History, state: Env.JsObject, _: ?[]const u8, url: ?[]const u8) void {
+ _ = self;
+ _ = url;
+ _ = state;
+}
+
+// TODO implement the function
+pub fn _go(self: *History, delta: ?i32) void {
+ _ = self;
+ _ = delta;
+}
+
+pub fn _back(self: *History) void {
+ if (self.current) |curr| {
+ if (curr > 0) {
+ self.current = curr - 1;
+ }
+ }
+}
+
+pub fn _forward(self: *History) void {
+ if (self.current) |curr| {
+ if (curr < self.stack.items.len) {
+ self.current = curr + 1;
+ }
+ }
+}
+
+const testing = @import("../../testing.zig");
+test "Browser: HTML.History" {
+ try testing.htmlRunner("html/history.html");
+}
diff --git a/src/browser/html/history.zig b/src/browser/html/history.zig
deleted file mode 100644
index e7d6726b..00000000
--- a/src/browser/html/history.zig
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
-//
-// Francis Bouvier
-// Pierre Tachoire
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as
-// published by the Free Software Foundation, either version 3 of the
-// License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see .
-
-const std = @import("std");
-
-// https://html.spec.whatwg.org/multipage/nav-history-apis.html#the-history-interface
-pub const History = struct {
- const ScrollRestorationMode = enum {
- auto,
- manual,
- };
-
- scrollRestoration: ScrollRestorationMode = .auto,
- state: std.json.Value = .null,
-
- // count tracks the history length until we implement correctly pushstate.
- count: u32 = 0,
-
- pub fn get_length(self: *History) u32 {
- // TODO return the real history length value.
- return self.count;
- }
-
- pub fn get_scrollRestoration(self: *History) []const u8 {
- return switch (self.scrollRestoration) {
- .auto => "auto",
- .manual => "manual",
- };
- }
-
- pub fn set_scrollRestoration(self: *History, mode: []const u8) void {
- if (std.mem.eql(u8, "manual", mode)) self.scrollRestoration = .manual;
- if (std.mem.eql(u8, "auto", mode)) self.scrollRestoration = .auto;
- }
-
- pub fn get_state(self: *History) std.json.Value {
- return self.state;
- }
-
- // TODO implement the function
- // data must handle any argument. We could expect a std.json.Value but
- // https://github.com/lightpanda-io/zig-js-runtime/issues/267 is missing.
- pub fn _pushState(self: *History, data: []const u8, _: ?[]const u8, url: ?[]const u8) void {
- self.count += 1;
- _ = url;
- _ = data;
- }
-
- // TODO implement the function
- // data must handle any argument. We could expect a std.json.Value but
- // https://github.com/lightpanda-io/zig-js-runtime/issues/267 is missing.
- pub fn _replaceState(self: *History, data: []const u8, _: ?[]const u8, url: ?[]const u8) void {
- _ = self;
- _ = url;
- _ = data;
- }
-
- // TODO implement the function
- pub fn _go(self: *History, delta: ?i32) void {
- _ = self;
- _ = delta;
- }
-
- // TODO implement the function
- pub fn _back(self: *History) void {
- _ = self;
- }
-
- // TODO implement the function
- pub fn _forward(self: *History) void {
- _ = self;
- }
-};
-
-const testing = @import("../../testing.zig");
-test "Browser: HTML.History" {
- try testing.htmlRunner("html/history.html");
-}
diff --git a/src/browser/html/html.zig b/src/browser/html/html.zig
index d722ad53..ef8a99f7 100644
--- a/src/browser/html/html.zig
+++ b/src/browser/html/html.zig
@@ -21,7 +21,7 @@ const HTMLElem = @import("elements.zig");
const SVGElem = @import("svg_elements.zig");
const Window = @import("window.zig").Window;
const Navigator = @import("navigator.zig").Navigator;
-const History = @import("history.zig").History;
+const History = @import("History.zig");
const Location = @import("location.zig").Location;
const MediaQueryList = @import("media_query_list.zig").MediaQueryList;
diff --git a/src/browser/html/window.zig b/src/browser/html/window.zig
index e32f5e85..0478bea8 100644
--- a/src/browser/html/window.zig
+++ b/src/browser/html/window.zig
@@ -24,7 +24,7 @@ const Env = @import("../env.zig").Env;
const Page = @import("../page.zig").Page;
const Navigator = @import("navigator.zig").Navigator;
-const History = @import("history.zig").History;
+const History = @import("History.zig");
const Location = @import("location.zig").Location;
const Crypto = @import("../crypto/crypto.zig").Crypto;
const Console = @import("../console/console.zig").Console;
@@ -54,7 +54,6 @@ pub const Window = struct {
document: *parser.DocumentHTML,
target: []const u8 = "",
- history: History = .{},
location: Location = .{},
storage_shelf: ?*storage.Shelf = null,
@@ -179,8 +178,8 @@ pub const Window = struct {
return self.document;
}
- pub fn get_history(self: *Window) *History {
- return &self.history;
+ pub fn get_history(_: *Window, page: *Page) *History {
+ return &page.session.history;
}
// The interior height of the window in pixels, including the height of the horizontal scroll bar, if present.
diff --git a/src/browser/session.zig b/src/browser/session.zig
index 2f69f9ef..5241859e 100644
--- a/src/browser/session.zig
+++ b/src/browser/session.zig
@@ -24,6 +24,7 @@ const Env = @import("env.zig").Env;
const Page = @import("page.zig").Page;
const Browser = @import("browser.zig").Browser;
const NavigateOpts = @import("page.zig").NavigateOpts;
+const History = @import("html/History.zig");
const log = @import("../log.zig");
const parser = @import("netsurf.zig");
@@ -53,6 +54,10 @@ pub const Session = struct {
storage_shed: storage.Shed,
cookie_jar: storage.CookieJar,
+ // History is persistent across the "tab".
+ // https://developer.mozilla.org/en-US/docs/Web/API/History
+ history: History = .{},
+
page: ?Page = null,
// If the current page want to navigate to a new page
diff --git a/src/runtime/js.zig b/src/runtime/js.zig
index 977c96be..10708714 100644
--- a/src/runtime/js.zig
+++ b/src/runtime/js.zig
@@ -2005,6 +2005,12 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
return writer.writeAll(try self.toString());
}
+ pub fn toJson(self: JsObject, allocator: std.mem.Allocator) ![]u8 {
+ const json_string = try v8.Json.stringify(self.js_context.v8_context, self.js_obj.toValue(), null);
+ const str = try jsStringToZig(allocator, json_string, self.js_context.isolate);
+ return str;
+ }
+
pub fn persist(self: JsObject) !JsObject {
var js_context = self.js_context;
const js_obj = self.js_obj;
@@ -2400,6 +2406,12 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
const js_context = self.js_context;
return valueToString(allocator, self.value, js_context.isolate, js_context.v8_context);
}
+
+ pub fn fromJson(ctx: *JsContext, json: []const u8) !Value {
+ const json_string = v8.String.initUtf8(ctx.isolate, json);
+ const value = try v8.Json.parse(ctx.v8_context, json_string);
+ return Value{ .js_context = ctx, .value = value };
+ }
};
pub const ValueIterator = struct {
diff --git a/src/tests/html/history.html b/src/tests/html/history.html
index 796640e9..45ef8056 100644
--- a/src/tests/html/history.html
+++ b/src/tests/html/history.html
@@ -22,3 +22,10 @@
testing.expectEqual(undefined, history.forward());
testing.expectEqual(undefined, history.back());
+
+