From d7069df80d13484c3bef7a60bd89390ff7fd470e Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Wed, 15 Jan 2025 15:40:20 +0100 Subject: [PATCH 1/5] dom: first draft for location --- src/html/html.zig | 2 + src/html/location.zig | 120 ++++++++++++++++++++++++++++++++++++++++++ src/html/window.zig | 10 ++++ src/run_tests.zig | 1 + 4 files changed, 133 insertions(+) create mode 100644 src/html/location.zig diff --git a/src/html/html.zig b/src/html/html.zig index 29b467cb..43535e17 100644 --- a/src/html/html.zig +++ b/src/html/html.zig @@ -23,6 +23,7 @@ const HTMLElem = @import("elements.zig"); const Window = @import("window.zig").Window; const Navigator = @import("navigator.zig").Navigator; const History = @import("history.zig").History; +const Location = @import("location.zig").Location; pub const Interfaces = generate.Tuple(.{ HTMLDocument, @@ -32,4 +33,5 @@ pub const Interfaces = generate.Tuple(.{ Window, Navigator, History, + Location, }); diff --git a/src/html/location.zig b/src/html/location.zig new file mode 100644 index 00000000..ac72d1ce --- /dev/null +++ b/src/html/location.zig @@ -0,0 +1,120 @@ +// 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 builtin = @import("builtin"); +const jsruntime = @import("jsruntime"); + +const URL = @import("../url/url.zig").URL; + +const Case = jsruntime.test_utils.Case; +const checkCases = jsruntime.test_utils.checkCases; + +// https://html.spec.whatwg.org/multipage/nav-history-apis.html#the-location-interface +pub const Location = struct { + pub const mem_guarantied = true; + + url: ?*URL = null, + + pub fn deinit(_: *Location, _: std.mem.Allocator) void {} + + pub fn get_href(self: *Location, alloc: std.mem.Allocator) ![]const u8 { + if (self.url) |u| return u.get_href(alloc); + + return ""; + } + + pub fn get_protocol(self: *Location, alloc: std.mem.Allocator) ![]const u8 { + if (self.url) |u| return u.get_protocol(alloc); + + return ""; + } + + pub fn get_host(self: *Location, alloc: std.mem.Allocator) ![]const u8 { + if (self.url) |u| return u.get_host(alloc); + + return ""; + } + + pub fn get_hostname(self: *Location) []const u8 { + if (self.url) |u| return u.get_hostname(); + + return ""; + } + + pub fn get_port(self: *Location, alloc: std.mem.Allocator) ![]const u8 { + if (self.url) |u| return u.get_port(alloc); + + return ""; + } + + pub fn get_pathname(self: *Location) []const u8 { + if (self.url) |u| return u.get_pathname(); + + return ""; + } + + pub fn get_search(self: *Location, alloc: std.mem.Allocator) ![]const u8 { + if (self.url) |u| return u.get_search(alloc); + + return ""; + } + + pub fn get_hash(self: *Location, alloc: std.mem.Allocator) ![]const u8 { + if (self.url) |u| return u.get_hash(alloc); + + return ""; + } + + pub fn get_origin(self: *Location, alloc: std.mem.Allocator) ![]const u8 { + if (self.url) |u| return u.get_origin(alloc); + + return ""; + } + + // TODO + pub fn _assign(_: *Location, url: []const u8) !void { + _ = url; + } + + // TODO + pub fn _replace(_: *Location, url: []const u8) !void { + _ = url; + } + + // TODO + pub fn _reload(_: *Location) !void {} + + pub fn _toString(self: *Location, alloc: std.mem.Allocator) ![]const u8 { + return try self.get_href(alloc); + } +}; + +// Tests +// ----- + +pub fn testExecFn( + _: std.mem.Allocator, + js_env: *jsruntime.Env, +) anyerror!void { + var location = [_]Case{ + .{ .src = "location.href", .ex = "" }, + }; + try checkCases(js_env, &location); +} diff --git a/src/html/window.zig b/src/html/window.zig index 65a8af67..0a73adc0 100644 --- a/src/html/window.zig +++ b/src/html/window.zig @@ -27,6 +27,7 @@ const Loop = jsruntime.Loop; const EventTarget = @import("../dom/event_target.zig").EventTarget; const Navigator = @import("navigator.zig").Navigator; const History = @import("history.zig").History; +const Location = @import("location.zig").Location; const storage = @import("../storage/storage.zig"); @@ -43,6 +44,7 @@ pub const Window = struct { document: ?*parser.DocumentHTML = null, target: []const u8, history: History = .{}, + location: Location = .{}, storageShelf: ?*storage.Shelf = null, @@ -60,6 +62,10 @@ pub const Window = struct { }; } + pub fn replaceLocation(self: *Window, loc: Location) void { + self.location = loc; + } + pub fn replaceDocument(self: *Window, doc: *parser.DocumentHTML) void { self.document = doc; } @@ -76,6 +82,10 @@ pub const Window = struct { return &self.navigator; } + pub fn get_location(self: *Window) *Location { + return &self.location; + } + pub fn get_self(self: *Window) *Window { return self; } diff --git a/src/run_tests.zig b/src/run_tests.zig index 838a6047..c0ca2e2a 100644 --- a/src/run_tests.zig +++ b/src/run_tests.zig @@ -139,6 +139,7 @@ fn testsAllExecFn( @import("polyfill/fetch.zig").testExecFn, @import("html/navigator.zig").testExecFn, @import("html/history.zig").testExecFn, + @import("html/location.zig").testExecFn, }; inline for (testFns) |testFn| { From 341f5725a417afa81e8fc56df784df665f324409 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Thu, 23 Jan 2025 15:18:32 +0100 Subject: [PATCH 2/5] netsurf: implement location setter/getter --- src/netsurf/netsurf.zig | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/netsurf/netsurf.zig b/src/netsurf/netsurf.zig index a3560d25..1019b153 100644 --- a/src/netsurf/netsurf.zig +++ b/src/netsurf/netsurf.zig @@ -2264,3 +2264,15 @@ pub fn documentHTMLGetCurrentScript(doc: *DocumentHTML) !?*Script { if (elem == null) return null; return @ptrCast(elem.?); } + +pub fn documentHTMLSetLocation(doc: *DocumentHTML, location: ?*anyopaque) !void { + const err = documentHTMLVtable(doc).set_location.?(doc, location); + try DOMErr(err); +} + +pub fn documentHTMLGetLocation(doc: *DocumentHTML) !?*anyopaque { + var loc: ?*anyopaque = undefined; + const err = documentHTMLVtable(doc).get_location.?(doc, &loc); + try DOMErr(err); + return loc; +} From 09ba4bcf43b8c0434854e5fa675b8244b2f2276d Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Thu, 23 Jan 2025 15:19:06 +0100 Subject: [PATCH 3/5] upgrade libdom --- vendor/netsurf/libdom | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/netsurf/libdom b/vendor/netsurf/libdom index 279398be..da8b9679 160000 --- a/vendor/netsurf/libdom +++ b/vendor/netsurf/libdom @@ -1 +1 @@ -Subproject commit 279398bebbef59fd5f38fca1d65d220fd18e06f2 +Subproject commit da8b967905a38455e4f6b75cc9ad2767bae3ccde From 318e2bd1c6c7848f11905af2a199066dadb1eeea Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Thu, 23 Jan 2025 17:08:02 +0100 Subject: [PATCH 4/5] dom: expose document.location --- src/browser/browser.zig | 2 +- src/html/document.zig | 5 +++++ src/html/location.zig | 1 + src/html/window.zig | 9 +++++++-- src/main_shell.zig | 2 +- src/netsurf/netsurf.zig | 17 +++++++++++------ src/run_tests.zig | 2 +- src/wpt/run.zig | 2 +- 8 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/browser/browser.zig b/src/browser/browser.zig index c421498c..65aafa83 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -391,7 +391,7 @@ pub const Page = struct { // TODO set the referrer to the document. - self.session.window.replaceDocument(html_doc); + try self.session.window.replaceDocument(html_doc); self.session.window.setStorageShelf( try self.session.storageShed.getOrPut(self.origin orelse "null"), ); diff --git a/src/html/document.zig b/src/html/document.zig index 78ffa864..0f278f0d 100644 --- a/src/html/document.zig +++ b/src/html/document.zig @@ -28,6 +28,7 @@ const Node = @import("../dom/node.zig").Node; const Document = @import("../dom/document.zig").Document; const NodeList = @import("../dom/nodelist.zig").NodeList; const HTMLElem = @import("elements.zig"); +const Location = @import("location.zig").Location; const collection = @import("../dom/html_collection.zig"); const Walker = @import("../dom/walker.zig").WalkerDepthFirst; @@ -157,6 +158,10 @@ pub const HTMLDocument = struct { return try parser.documentHTMLGetCurrentScript(self); } + pub fn get_location(self: *parser.DocumentHTML) !?*Location { + return try parser.documentHTMLGetLocation(Location, self); + } + pub fn get_designMode(_: *parser.DocumentHTML) []const u8 { return "off"; } diff --git a/src/html/location.zig b/src/html/location.zig index ac72d1ce..b1ec2913 100644 --- a/src/html/location.zig +++ b/src/html/location.zig @@ -115,6 +115,7 @@ pub fn testExecFn( ) anyerror!void { var location = [_]Case{ .{ .src = "location.href", .ex = "" }, + .{ .src = "document.location.href", .ex = "" }, }; try checkCases(js_env, &location); } diff --git a/src/html/window.zig b/src/html/window.zig index 0a73adc0..b5530857 100644 --- a/src/html/window.zig +++ b/src/html/window.zig @@ -62,12 +62,17 @@ pub const Window = struct { }; } - pub fn replaceLocation(self: *Window, loc: Location) void { + pub fn replaceLocation(self: *Window, loc: Location) !void { self.location = loc; + + if (self.doc != null) { + try parser.documentHTMLSetLocation(Location, self.doc.?, &self.location); + } } - pub fn replaceDocument(self: *Window, doc: *parser.DocumentHTML) void { + pub fn replaceDocument(self: *Window, doc: *parser.DocumentHTML) !void { self.document = doc; + try parser.documentHTMLSetLocation(Location, doc, &self.location); } pub fn setStorageShelf(self: *Window, shelf: *storage.Shelf) void { diff --git a/src/main_shell.zig b/src/main_shell.zig index 362f68e1..dff00963 100644 --- a/src/main_shell.zig +++ b/src/main_shell.zig @@ -55,7 +55,7 @@ fn execJS( // alias global as self and window var window = Window.create(null, null); - window.replaceDocument(doc); + try window.replaceDocument(doc); window.setStorageShelf(&storageShelf); try js_env.bindGlobal(window); diff --git a/src/netsurf/netsurf.zig b/src/netsurf/netsurf.zig index 1019b153..97716683 100644 --- a/src/netsurf/netsurf.zig +++ b/src/netsurf/netsurf.zig @@ -2265,14 +2265,19 @@ pub fn documentHTMLGetCurrentScript(doc: *DocumentHTML) !?*Script { return @ptrCast(elem.?); } -pub fn documentHTMLSetLocation(doc: *DocumentHTML, location: ?*anyopaque) !void { - const err = documentHTMLVtable(doc).set_location.?(doc, location); +pub fn documentHTMLSetLocation(T: type, doc: *DocumentHTML, location: *T) !void { + const l = @as(*anyopaque, @ptrCast(location)); + const err = documentHTMLVtable(doc).set_location.?(doc, l); try DOMErr(err); } -pub fn documentHTMLGetLocation(doc: *DocumentHTML) !?*anyopaque { - var loc: ?*anyopaque = undefined; - const err = documentHTMLVtable(doc).get_location.?(doc, &loc); +pub fn documentHTMLGetLocation(T: type, doc: *DocumentHTML) !?*T { + var l: ?*anyopaque = undefined; + const err = documentHTMLVtable(doc).get_location.?(doc, &l); try DOMErr(err); - return loc; + + if (l == null) return null; + + const ptr: *align(@alignOf(*T)) anyopaque = @alignCast(l.?); + return @as(*T, @ptrCast(ptr)); } diff --git a/src/run_tests.zig b/src/run_tests.zig index c0ca2e2a..7481d266 100644 --- a/src/run_tests.zig +++ b/src/run_tests.zig @@ -98,7 +98,7 @@ fn testExecFn( // alias global as self and window var window = Window.create(null, null); - window.replaceDocument(doc); + try window.replaceDocument(doc); window.setStorageShelf(&storageShelf); try js_env.bindGlobal(window); diff --git a/src/wpt/run.zig b/src/wpt/run.zig index 208078b9..3a00db6b 100644 --- a/src/wpt/run.zig +++ b/src/wpt/run.zig @@ -91,7 +91,7 @@ pub fn run(arena: *std.heap.ArenaAllocator, comptime dir: []const u8, f: []const // setup global env vars. var window = Window.create(null, null); - window.replaceDocument(html_doc); + try window.replaceDocument(html_doc); window.setStorageShelf(&storageShelf); try js_env.bindGlobal(&window); From 7b35bb4c0f094702f3daf04f06095e843b992837 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Fri, 24 Jan 2025 16:42:48 +0100 Subject: [PATCH 5/5] dom: improve location impl --- src/browser/browser.zig | 21 +++++++++++++++++++++ src/html/location.zig | 12 ++++++++++-- src/html/window.zig | 14 ++++++++------ src/run_tests.zig | 7 +++++++ 4 files changed, 46 insertions(+), 8 deletions(-) diff --git a/src/browser/browser.zig b/src/browser/browser.zig index 65aafa83..e361838e 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -36,6 +36,9 @@ const apiweb = @import("../apiweb.zig"); const Window = @import("../html/window.zig").Window; const Walker = @import("../dom/walker.zig").WalkerDepthFirst; +const URL = @import("../url/url.zig").URL; +const Location = @import("../html/location.zig").Location; + const storage = @import("../storage/storage.zig"); const FetchResult = @import("../http/Client.zig").Client.FetchResult; @@ -102,7 +105,9 @@ pub const Session = struct { loader: Loader, env: Env = undefined, inspector: ?jsruntime.Inspector = null, + window: Window, + // TODO move the shed to the browser? storageShed: storage.Shed, page: ?Page = null, @@ -201,6 +206,10 @@ pub const Page = struct { uri: std.Uri = undefined, origin: ?[]const u8 = null, + // html url and location + url: ?URL = null, + location: Location = .{}, + raw_data: ?[]const u8 = null, fn init( @@ -244,6 +253,13 @@ pub const Page = struct { self.session.env.stop(); // TODO unload document: https://html.spec.whatwg.org/#unloading-documents + if (self.url) |*u| u.deinit(self.arena.allocator()); + self.url = null; + self.location.url = null; + self.session.window.replaceLocation(&self.location) catch |e| { + log.err("reset window location: {any}", .{e}); + }; + // clear netsurf memory arena. parser.deinit(); @@ -308,6 +324,11 @@ pub const Page = struct { self.rawuri = try alloc.dupe(u8, uri); self.uri = std.Uri.parse(self.rawuri.?) catch try std.Uri.parseAfterScheme("", self.rawuri.?); + if (self.url) |*prev| prev.deinit(alloc); + self.url = try URL.constructor(alloc, self.rawuri.?, null); + self.location.url = &self.url.?; + try self.session.window.replaceLocation(&self.location); + // prepare origin value. var buf = std.ArrayList(u8).init(alloc); defer buf.deinit(); diff --git a/src/html/location.zig b/src/html/location.zig index b1ec2913..70e69025 100644 --- a/src/html/location.zig +++ b/src/html/location.zig @@ -114,8 +114,16 @@ pub fn testExecFn( js_env: *jsruntime.Env, ) anyerror!void { var location = [_]Case{ - .{ .src = "location.href", .ex = "" }, - .{ .src = "document.location.href", .ex = "" }, + .{ .src = "location.href", .ex = "https://lightpanda.io/opensource-browser/" }, + .{ .src = "document.location.href", .ex = "https://lightpanda.io/opensource-browser/" }, + + .{ .src = "location.host", .ex = "lightpanda.io" }, + .{ .src = "location.hostname", .ex = "lightpanda.io" }, + .{ .src = "location.origin", .ex = "https://lightpanda.io" }, + .{ .src = "location.pathname", .ex = "/opensource-browser/" }, + .{ .src = "location.hash", .ex = "" }, + .{ .src = "location.port", .ex = "" }, + .{ .src = "location.search", .ex = "" }, }; try checkCases(js_env, &location); } diff --git a/src/html/window.zig b/src/html/window.zig index b5530857..42aef90d 100644 --- a/src/html/window.zig +++ b/src/html/window.zig @@ -31,6 +31,8 @@ const Location = @import("location.zig").Location; const storage = @import("../storage/storage.zig"); +var emptyLocation = Location{}; + // https://dom.spec.whatwg.org/#interface-window-extensions // https://html.spec.whatwg.org/multipage/nav-history-apis.html#window pub const Window = struct { @@ -44,7 +46,7 @@ pub const Window = struct { document: ?*parser.DocumentHTML = null, target: []const u8, history: History = .{}, - location: Location = .{}, + location: *Location = &emptyLocation, storageShelf: ?*storage.Shelf = null, @@ -62,17 +64,17 @@ pub const Window = struct { }; } - pub fn replaceLocation(self: *Window, loc: Location) !void { + pub fn replaceLocation(self: *Window, loc: *Location) !void { self.location = loc; - if (self.doc != null) { - try parser.documentHTMLSetLocation(Location, self.doc.?, &self.location); + if (self.document != null) { + try parser.documentHTMLSetLocation(Location, self.document.?, self.location); } } pub fn replaceDocument(self: *Window, doc: *parser.DocumentHTML) !void { self.document = doc; - try parser.documentHTMLSetLocation(Location, doc, &self.location); + try parser.documentHTMLSetLocation(Location, doc, self.location); } pub fn setStorageShelf(self: *Window, shelf: *storage.Shelf) void { @@ -88,7 +90,7 @@ pub const Window = struct { } pub fn get_location(self: *Window) *Location { - return &self.location; + return self.location; } pub fn get_self(self: *Window) *Window { diff --git a/src/run_tests.zig b/src/run_tests.zig index 7481d266..bc0b5631 100644 --- a/src/run_tests.zig +++ b/src/run_tests.zig @@ -29,8 +29,10 @@ const Window = @import("html/window.zig").Window; const xhr = @import("xhr/xhr.zig"); const storage = @import("storage/storage.zig"); const url = @import("url/url.zig"); +const URL = url.URL; const urlquery = @import("url/query.zig"); const Client = @import("asyncio").Client; +const Location = @import("html/location.zig").Location; const documentTestExecFn = @import("dom/document.zig").testExecFn; const HTMLDocumentTestExecFn = @import("html/document.zig").testExecFn; @@ -98,6 +100,11 @@ fn testExecFn( // alias global as self and window var window = Window.create(null, null); + var u = try URL.constructor(alloc, "https://lightpanda.io/opensource-browser/", null); + defer u.deinit(alloc); + var location = Location{ .url = &u }; + try window.replaceLocation(&location); + try window.replaceDocument(doc); window.setStorageShelf(&storageShelf);