add toJson object and fromJson value

This commit is contained in:
Muki Kiboigo
2025-09-23 20:44:51 -07:00
parent 202e137d77
commit c3ad054bb3
7 changed files with 146 additions and 98 deletions

View File

@@ -0,0 +1,118 @@
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// 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 <https://www.gnu.org/licenses/>.
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");
}

View File

@@ -1,93 +0,0 @@
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// 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 <https://www.gnu.org/licenses/>.
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");
}

View File

@@ -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;

View File

@@ -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.

View File

@@ -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

View File

@@ -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 {

View File

@@ -22,3 +22,10 @@
testing.expectEqual(undefined, history.forward());
testing.expectEqual(undefined, history.back());
</script>
<script id=history-states>
testing.expectEqual(null, history.state)
history.pushState({}, { "abc": "def" }, '');
testing.expectEqual({ "abc": "def"}, history.state);
</script>