From e18d04a799b431969a4d5fcb9e55441ae7730f08 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Tue, 16 Apr 2024 12:06:29 +0200 Subject: [PATCH 01/14] userctx: inject user context --- src/apiweb.zig | 2 ++ src/browser/browser.zig | 2 +- src/main.zig | 3 ++- src/main_get.zig | 1 + src/main_shell.zig | 5 +++++ src/main_wpt.zig | 1 + src/run_tests.zig | 9 ++++++++- src/test_runner.zig | 1 + src/user_context.zig | 6 ++++++ src/wpt/run.zig | 5 ++++- 10 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 src/user_context.zig diff --git a/src/apiweb.zig b/src/apiweb.zig index decac389..cbf2ffa0 100644 --- a/src/apiweb.zig +++ b/src/apiweb.zig @@ -39,3 +39,5 @@ pub const Interfaces = generate.Tuple(.{ Storage.Interfaces, URL.Interfaces, }); + +pub const UserContext = @import("user_context.zig").UserContext; diff --git a/src/browser/browser.zig b/src/browser/browser.zig index 2fb759cc..b17173da 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -107,7 +107,7 @@ pub const Session = struct { .storageShed = storage.Shed.init(alloc), }; - self.env = try Env.init(self.arena.allocator(), &self.loop); + self.env = try Env.init(self.arena.allocator(), &self.loop, null); try self.env.load(&self.jstypes); return self; diff --git a/src/main.zig b/src/main.zig index 1c032b68..ab4fb585 100644 --- a/src/main.zig +++ b/src/main.zig @@ -25,6 +25,7 @@ const apiweb = @import("apiweb.zig"); const Window = @import("html/window.zig").Window; pub const Types = jsruntime.reflect(apiweb.Interfaces); +pub const UserContext = apiweb.UserContext; const socket_path = "/tmp/browsercore-server.sock"; @@ -103,5 +104,5 @@ pub fn main() !void { try server.listen(addr); std.debug.print("Listening on: {s}...\n", .{socket_path}); - try jsruntime.loadEnv(&arena, execJS); + try jsruntime.loadEnv(&arena, null, execJS); } diff --git a/src/main_get.zig b/src/main_get.zig index 579edca2..387106a6 100644 --- a/src/main_get.zig +++ b/src/main_get.zig @@ -23,6 +23,7 @@ const jsruntime = @import("jsruntime"); const apiweb = @import("apiweb.zig"); pub const Types = jsruntime.reflect(apiweb.Interfaces); +pub const UserContext = apiweb.UserContext; pub const std_options = struct { pub const log_level = .debug; diff --git a/src/main_shell.zig b/src/main_shell.zig index 4b4967d1..7eca5f03 100644 --- a/src/main_shell.zig +++ b/src/main_shell.zig @@ -28,6 +28,7 @@ const storage = @import("storage/storage.zig"); const html_test = @import("html_test.zig").html; pub const Types = jsruntime.reflect(apiweb.Interfaces); +pub const UserContext = apiweb.UserContext; var doc: *parser.DocumentHTML = undefined; @@ -39,6 +40,10 @@ fn execJS( try js_env.start(alloc); defer js_env.stop(); + js_env.setUserContext(UserContext{ + .document = doc, + }); + var storageShelf = storage.Shelf.init(alloc); defer storageShelf.deinit(); diff --git a/src/main_wpt.zig b/src/main_wpt.zig index 06c6a249..bc9927fd 100644 --- a/src/main_wpt.zig +++ b/src/main_wpt.zig @@ -49,6 +49,7 @@ const Out = enum { pub const Types = jsruntime.reflect(apiweb.Interfaces); pub const GlobalType = apiweb.GlobalType; +pub const UserContext = apiweb.UserContext; // TODO For now the WPT tests run is specific to WPT. // It manually load js framwork libs, and run the first script w/ js content in diff --git a/src/run_tests.zig b/src/run_tests.zig index 3fe20b3f..be65e5be 100644 --- a/src/run_tests.zig +++ b/src/run_tests.zig @@ -54,6 +54,7 @@ const URLTestExecFn = url.testExecFn; const HTMLElementTestExecFn = @import("html/elements.zig").testExecFn; pub const Types = jsruntime.reflect(apiweb.Interfaces); +pub const UserContext = @import("user_context.zig").UserContext; var doc: *parser.DocumentHTML = undefined; @@ -81,6 +82,8 @@ fn testExecFn( std.debug.print("documentHTMLClose error: {s}\n", .{@errorName(err)}); }; + js_env.getUserContext().?.document = doc; + // alias global as self and window var window = Window.create(null); @@ -315,7 +318,11 @@ fn testJSRuntime(alloc: std.mem.Allocator) !void { var arena_alloc = std.heap.ArenaAllocator.init(alloc); defer arena_alloc.deinit(); - try jsruntime.loadEnv(&arena_alloc, testsAllExecFn); + const userctx = UserContext{ + .document = null, + }; + + try jsruntime.loadEnv(&arena_alloc, userctx, testsAllExecFn); } test "DocumentHTMLParseFromStr" { diff --git a/src/test_runner.zig b/src/test_runner.zig index d385c813..8b138d0b 100644 --- a/src/test_runner.zig +++ b/src/test_runner.zig @@ -21,6 +21,7 @@ const std = @import("std"); const tests = @import("run_tests.zig"); pub const Types = tests.Types; +pub const UserContext = tests.UserContext; pub fn main() !void { try tests.main(); diff --git a/src/user_context.zig b/src/user_context.zig new file mode 100644 index 00000000..a2bc3efb --- /dev/null +++ b/src/user_context.zig @@ -0,0 +1,6 @@ +const std = @import("std"); +const parser = @import("netsurf.zig"); + +pub const UserContext = struct { + document: ?*parser.DocumentHTML, +}; diff --git a/src/wpt/run.zig b/src/wpt/run.zig index c7c263a5..8b0e7516 100644 --- a/src/wpt/run.zig +++ b/src/wpt/run.zig @@ -30,6 +30,7 @@ const Window = @import("../html/window.zig").Window; const storage = @import("../storage/storage.zig"); const Types = @import("../main_wpt.zig").Types; +const UserContext = @import("../main_wpt.zig").UserContext; // runWPT parses the given HTML file, starts a js env and run the first script // tags containing javascript sources. @@ -50,7 +51,9 @@ pub fn run(arena: *std.heap.ArenaAllocator, comptime dir: []const u8, f: []const // create JS env var loop = try Loop.init(alloc); defer loop.deinit(); - var js_env = try Env.init(alloc, &loop); + var js_env = try Env.init(alloc, &loop, UserContext{ + .document = html_doc, + }); defer js_env.deinit(); var storageShelf = storage.Shelf.init(alloc); From b7f589ee1aed385994c586f318b37f177d70be72 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Tue, 16 Apr 2024 12:10:41 +0200 Subject: [PATCH 02/14] dom: fix document constructor --- src/dom/document.zig | 27 +++++++++++++++++++++++++-- src/dom/implementation.zig | 4 ++-- src/netsurf.zig | 16 ++++++++++++---- 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/dom/document.zig b/src/dom/document.zig index e9f22e76..46e24c32 100644 --- a/src/dom/document.zig +++ b/src/dom/document.zig @@ -40,14 +40,30 @@ const DocumentType = @import("document_type.zig").DocumentType; const DocumentFragment = @import("document_fragment.zig").DocumentFragment; const DOMImplementation = @import("implementation.zig").DOMImplementation; +const UserContext = @import("../user_context.zig").UserContext; + // WEB IDL https://dom.spec.whatwg.org/#document pub const Document = struct { pub const Self = parser.Document; pub const prototype = *Node; pub const mem_guarantied = true; - pub fn constructor() !*parser.Document { - return try parser.domImplementationCreateHTMLDocument(null); + pub fn constructor(userctx: UserContext) !*parser.DocumentHTML { + var title: ?[]const u8 = null; + if (userctx.document) |cur| { + title = try parser.documentHTMLGetTitle(cur); + } + const doc = try parser.domImplementationCreateHTMLDocument(title); + + if (userctx.document) |cur| { + // we have to work w/ document instead of html document. + const ddoc = parser.documentHTMLToDocument(doc); + const ccur = parser.documentHTMLToDocument(cur); + try parser.documentSetDocumentURI(ddoc, try parser.documentGetDocumentURI(ccur)); + try parser.documentSetInputEncoding(ddoc, try parser.documentGetInputEncoding(ccur)); + } + + return doc; } // JS funcs @@ -262,6 +278,13 @@ pub fn testExecFn( .{ .src = "newdoc.children.length", .ex = "0" }, .{ .src = "newdoc.getElementsByTagName('*').length", .ex = "0" }, .{ .src = "newdoc.getElementsByTagName('*').item(0)", .ex = "null" }, + .{ .src = "newdoc.inputEncoding === document.inputEncoding", .ex = "true" }, + .{ .src = "newdoc.documentURI === document.documentURI", .ex = "true" }, + .{ .src = "newdoc.URL === document.URL", .ex = "true" }, + .{ .src = "newdoc.compatMode === document.compatMode", .ex = "true" }, + .{ .src = "newdoc.characterSet === document.characterSet", .ex = "true" }, + .{ .src = "newdoc.charset === document.charset", .ex = "true" }, + .{ .src = "newdoc.contentType === document.contentType", .ex = "true" }, }; try checkCases(js_env, &constructor); diff --git a/src/dom/implementation.zig b/src/dom/implementation.zig index f7a613a0..5fa6206d 100644 --- a/src/dom/implementation.zig +++ b/src/dom/implementation.zig @@ -75,7 +75,7 @@ pub const DOMImplementation = struct { return try parser.domImplementationCreateDocument(cnamespace, cqname, doctype); } - pub fn _createHTMLDocument(_: *DOMImplementation, title: ?[]const u8) !*parser.Document { + pub fn _createHTMLDocument(_: *DOMImplementation, title: ?[]const u8) !*parser.DocumentHTML { return try parser.domImplementationCreateHTMLDocument(title); } @@ -95,7 +95,7 @@ pub fn testExecFn( ) anyerror!void { var getImplementation = [_]Case{ .{ .src = "let impl = document.implementation", .ex = "undefined" }, - .{ .src = "impl.createHTMLDocument();", .ex = "[object Document]" }, + .{ .src = "impl.createHTMLDocument();", .ex = "[object HTMLDocument]" }, .{ .src = "impl.createDocument(null, 'foo');", .ex = "[object Document]" }, .{ .src = "impl.createDocumentType('foo', 'bar', 'baz')", .ex = "[object DocumentType]" }, .{ .src = "impl.hasFeature()", .ex = "true" }, diff --git a/src/netsurf.zig b/src/netsurf.zig index f5b3a141..2f8b1efd 100644 --- a/src/netsurf.zig +++ b/src/netsurf.zig @@ -1763,7 +1763,7 @@ pub inline fn domImplementationCreateDocumentType( return dt.?; } -pub inline fn domImplementationCreateHTMLDocument(title: ?[]const u8) !*Document { +pub inline fn domImplementationCreateHTMLDocument(title: ?[]const u8) !*DocumentHTML { var doc: ?*Document = undefined; const err = c.dom_implementation_create_document( c.DOM_IMPLEMENTATION_HTML, @@ -1775,9 +1775,12 @@ pub inline fn domImplementationCreateHTMLDocument(title: ?[]const u8) !*Document &doc, ); try DOMErr(err); - // TODO set title - _ = title; - return doc.?; + + const doc_html = @as(*DocumentHTML, @ptrCast(doc.?)); + + if (title) |t| try documentHTMLSetTitle(doc_html, t); + + return doc_html; } // Document @@ -1833,6 +1836,11 @@ pub inline fn documentGetInputEncoding(doc: *Document) ![]const u8 { return strToData(s.?); } +pub inline fn documentSetInputEncoding(doc: *Document, enc: []const u8) !void { + const err = documentVtable(doc).dom_document_set_input_encoding.?(doc, try strFromData(enc)); + try DOMErr(err); +} + pub inline fn documentCreateElement(doc: *Document, tag_name: []const u8) !*Element { var elem: ?*Element = undefined; const err = documentVtable(doc).dom_document_create_element.?(doc, try strFromData(tag_name), &elem); From 49ee5e4e68c18d3165c797aa00d513772dbdb379 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Wed, 3 Jan 2024 14:59:48 +0100 Subject: [PATCH 03/14] comment: return error on constructor Blocked by https://github.com/lightpanda-io/browsercore/issues/102 --- src/dom/comment.zig | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/dom/comment.zig b/src/dom/comment.zig index c9baad26..289a7932 100644 --- a/src/dom/comment.zig +++ b/src/dom/comment.zig @@ -20,8 +20,19 @@ const parser = @import("../netsurf.zig"); const CharacterData = @import("character_data.zig").CharacterData; +// https://dom.spec.whatwg.org/#interface-comment pub const Comment = struct { pub const Self = parser.Comment; pub const prototype = *CharacterData; pub const mem_guarantied = true; + + // TODO add constructor, but I need to associate the new Comment + // with the current document global object... + // > The new Comment(data) constructor steps are to set this’s data to data + // > and this’s node document to current global object’s associated + // > Document. + // https://dom.spec.whatwg.org/#dom-comment-comment + pub fn constructor() !*parser.Comment { + return error.NotImplemented; + } }; From eef2fa94d018b5b3e57be46ff7211b67f6c43eeb Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Wed, 3 Jan 2024 15:03:04 +0100 Subject: [PATCH 04/14] text: return error on constructor Blocked by #102 --- src/dom/text.zig | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/dom/text.zig b/src/dom/text.zig index a64c4d88..32e9c20f 100644 --- a/src/dom/text.zig +++ b/src/dom/text.zig @@ -38,6 +38,16 @@ pub const Text = struct { pub const prototype = *CharacterData; pub const mem_guarantied = true; + // TODO add constructor, but I need to associate the new Text + // with the current document global object... + // > The new Text(data) constructor steps are to set this’s data to data + // > and this’s node document to current global object’s associated + // > Document. + // https://dom.spec.whatwg.org/#dom-text-text + pub fn constructor() !*parser.Comment { + return error.NotImplemented; + } + // JS funcs // -------- From 14e1c44eb02d1b17bbebab5733d6fe66ed7641f4 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Fri, 19 Apr 2024 18:01:22 +0200 Subject: [PATCH 05/14] dom: implement comment constructor --- src/dom/comment.zig | 33 +++++++++++++++++++++++++++++++-- src/run_tests.zig | 2 ++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/dom/comment.zig b/src/dom/comment.zig index 289a7932..59b9c0df 100644 --- a/src/dom/comment.zig +++ b/src/dom/comment.zig @@ -15,11 +15,18 @@ // // 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 parser = @import("../netsurf.zig"); +const jsruntime = @import("jsruntime"); +const Case = jsruntime.test_utils.Case; +const checkCases = jsruntime.test_utils.checkCases; + const CharacterData = @import("character_data.zig").CharacterData; +const UserContext = @import("../user_context.zig").UserContext; + // https://dom.spec.whatwg.org/#interface-comment pub const Comment = struct { pub const Self = parser.Comment; @@ -32,7 +39,29 @@ pub const Comment = struct { // > and this’s node document to current global object’s associated // > Document. // https://dom.spec.whatwg.org/#dom-comment-comment - pub fn constructor() !*parser.Comment { - return error.NotImplemented; + pub fn constructor(userctx: UserContext, data: ?[]const u8) !*parser.Comment { + if (userctx.document == null) return parser.DOMError.NotSupported; + + return parser.documentCreateComment( + parser.documentHTMLToDocument(userctx.document.?), + data orelse "", + ); } }; + +// Tests +// ----- + +pub fn testExecFn( + _: std.mem.Allocator, + js_env: *jsruntime.Env, +) anyerror!void { + var constructor = [_]Case{ + .{ .src = "let comment = new Comment('foo')", .ex = "undefined" }, + .{ .src = "comment.data", .ex = "foo" }, + + .{ .src = "let emptycomment = new Comment()", .ex = "undefined" }, + .{ .src = "emptycomment.data", .ex = "" }, + }; + try checkCases(js_env, &constructor); +} diff --git a/src/run_tests.zig b/src/run_tests.zig index be65e5be..49313cbf 100644 --- a/src/run_tests.zig +++ b/src/run_tests.zig @@ -46,6 +46,7 @@ const NodeListTestExecFn = @import("dom/nodelist.zig").testExecFn; const AttrTestExecFn = @import("dom/attribute.zig").testExecFn; const EventTargetTestExecFn = @import("dom/event_target.zig").testExecFn; const ProcessingInstructionTestExecFn = @import("dom/processing_instruction.zig").testExecFn; +const CommentTestExecFn = @import("dom/comment.zig").testExecFn; const EventTestExecFn = @import("events/event.zig").testExecFn; const XHRTestExecFn = xhr.testExecFn; const ProgressEventTestExecFn = @import("xhr/progress_event.zig").testExecFn; @@ -114,6 +115,7 @@ fn testsAllExecFn( DOMTokenListExecFn, NodeListTestExecFn, AttrTestExecFn, + CommentTestExecFn, EventTargetTestExecFn, EventTestExecFn, XHRTestExecFn, From 55b80ecd15483939c5e86730c604011b0c1b151b Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Fri, 19 Apr 2024 18:06:00 +0200 Subject: [PATCH 06/14] dom: implement text constructor --- src/dom/text.zig | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/dom/text.zig b/src/dom/text.zig index 32e9c20f..50f6ffb0 100644 --- a/src/dom/text.zig +++ b/src/dom/text.zig @@ -28,6 +28,8 @@ const parser = @import("../netsurf.zig"); const CharacterData = @import("character_data.zig").CharacterData; const CDATASection = @import("cdata_section.zig").CDATASection; +const UserContext = @import("../user_context.zig").UserContext; + // Text interfaces pub const Interfaces = generate.Tuple(.{ CDATASection, @@ -44,8 +46,13 @@ pub const Text = struct { // > and this’s node document to current global object’s associated // > Document. // https://dom.spec.whatwg.org/#dom-text-text - pub fn constructor() !*parser.Comment { - return error.NotImplemented; + pub fn constructor(userctx: UserContext, data: ?[]const u8) !*parser.Text { + if (userctx.document == null) return parser.DOMError.NotSupported; + + return parser.documentCreateTextNode( + parser.documentHTMLToDocument(userctx.document.?), + data orelse "", + ); } // JS funcs @@ -72,6 +79,15 @@ pub fn testExecFn( _: std.mem.Allocator, js_env: *jsruntime.Env, ) anyerror!void { + var constructor = [_]Case{ + .{ .src = "let t = new Text('foo')", .ex = "undefined" }, + .{ .src = "t.data", .ex = "foo" }, + + .{ .src = "let emptyt = new Text()", .ex = "undefined" }, + .{ .src = "emptyt.data", .ex = "" }, + }; + try checkCases(js_env, &constructor); + var get_whole_text = [_]Case{ .{ .src = "let text = document.getElementById('link').firstChild", .ex = "undefined" }, .{ .src = "text.wholeText === 'OK'", .ex = "true" }, From d823eebce5322d2495f1d13da8cc7c1427b17308 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Mon, 22 Apr 2024 12:26:58 +0200 Subject: [PATCH 07/14] dom: remove useless TODO --- src/dom/comment.zig | 6 ------ src/dom/text.zig | 6 ------ 2 files changed, 12 deletions(-) diff --git a/src/dom/comment.zig b/src/dom/comment.zig index 59b9c0df..ff7ebace 100644 --- a/src/dom/comment.zig +++ b/src/dom/comment.zig @@ -33,12 +33,6 @@ pub const Comment = struct { pub const prototype = *CharacterData; pub const mem_guarantied = true; - // TODO add constructor, but I need to associate the new Comment - // with the current document global object... - // > The new Comment(data) constructor steps are to set this’s data to data - // > and this’s node document to current global object’s associated - // > Document. - // https://dom.spec.whatwg.org/#dom-comment-comment pub fn constructor(userctx: UserContext, data: ?[]const u8) !*parser.Comment { if (userctx.document == null) return parser.DOMError.NotSupported; diff --git a/src/dom/text.zig b/src/dom/text.zig index 50f6ffb0..6805825e 100644 --- a/src/dom/text.zig +++ b/src/dom/text.zig @@ -40,12 +40,6 @@ pub const Text = struct { pub const prototype = *CharacterData; pub const mem_guarantied = true; - // TODO add constructor, but I need to associate the new Text - // with the current document global object... - // > The new Text(data) constructor steps are to set this’s data to data - // > and this’s node document to current global object’s associated - // > Document. - // https://dom.spec.whatwg.org/#dom-text-text pub fn constructor(userctx: UserContext, data: ?[]const u8) !*parser.Text { if (userctx.document == null) return parser.DOMError.NotSupported; From b2df0c1541ad2de9845dd5b4672b54518135f5d5 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Mon, 22 Apr 2024 12:27:08 +0200 Subject: [PATCH 08/14] dom: implement document fragment constructor --- src/dom/document_fragment.zig | 33 ++++++++++++++++++++++++++------- src/run_tests.zig | 2 ++ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/dom/document_fragment.zig b/src/dom/document_fragment.zig index 7cd1d27b..da21798e 100644 --- a/src/dom/document_fragment.zig +++ b/src/dom/document_fragment.zig @@ -20,20 +20,39 @@ const std = @import("std"); const parser = @import("../netsurf.zig"); +const jsruntime = @import("jsruntime"); +const Case = jsruntime.test_utils.Case; +const checkCases = jsruntime.test_utils.checkCases; + const Node = @import("node.zig").Node; +const UserContext = @import("../user_context.zig").UserContext; + // WEB IDL https://dom.spec.whatwg.org/#documentfragment pub const DocumentFragment = struct { pub const Self = parser.DocumentFragment; pub const prototype = *Node; pub const mem_guarantied = true; - // TODO add constructor, but I need to associate the new DocumentFragment - // with the current document global object... - // > The new DocumentFragment() constructor steps are to set this’s node - // > document to current global object’s associated Document. - // https://dom.spec.whatwg.org/#dom-documentfragment-documentfragment - pub fn constructor() !*parser.DocumentFragment { - return error.NotImplemented; + pub fn constructor(userctx: UserContext) !*parser.DocumentFragment { + if (userctx.document == null) return parser.DOMError.NotSupported; + + return parser.documentCreateDocumentFragment( + parser.documentHTMLToDocument(userctx.document.?), + ); } }; + +// Tests +// ----- + +pub fn testExecFn( + _: std.mem.Allocator, + js_env: *jsruntime.Env, +) anyerror!void { + var constructor = [_]Case{ + .{ .src = "const dc = new DocumentFragment()", .ex = "undefined" }, + .{ .src = "dc.constructor.name", .ex = "DocumentFragment" }, + }; + try checkCases(js_env, &constructor); +} diff --git a/src/run_tests.zig b/src/run_tests.zig index 49313cbf..5eb470be 100644 --- a/src/run_tests.zig +++ b/src/run_tests.zig @@ -47,6 +47,7 @@ const AttrTestExecFn = @import("dom/attribute.zig").testExecFn; const EventTargetTestExecFn = @import("dom/event_target.zig").testExecFn; const ProcessingInstructionTestExecFn = @import("dom/processing_instruction.zig").testExecFn; const CommentTestExecFn = @import("dom/comment.zig").testExecFn; +const DocumentFragmentTestExecFn = @import("dom/document_fragment.zig").testExecFn; const EventTestExecFn = @import("events/event.zig").testExecFn; const XHRTestExecFn = xhr.testExecFn; const ProgressEventTestExecFn = @import("xhr/progress_event.zig").testExecFn; @@ -116,6 +117,7 @@ fn testsAllExecFn( NodeListTestExecFn, AttrTestExecFn, CommentTestExecFn, + DocumentFragmentTestExecFn, EventTargetTestExecFn, EventTestExecFn, XHRTestExecFn, From 7d91f7992c8da8a11e2ee436a64539325ba6994b Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Mon, 22 Apr 2024 16:21:50 +0200 Subject: [PATCH 09/14] dom: first draft for hierachy check in nodes --- src/dom/node.zig | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/dom/node.zig b/src/dom/node.zig index 58dce2f7..1bfee86d 100644 --- a/src/dom/node.zig +++ b/src/dom/node.zig @@ -297,12 +297,29 @@ pub const Node = struct { } } + // Check if the hierarchy node tree constraints are respected. + // For now, it checks only if new nodes are not self. + // TODO implements the others contraints. + // see https://dom.spec.whatwg.org/#concept-node-tree + pub fn hierarchy(self: *parser.Node, nodes: ?Variadic(*parser.Node)) !bool { + if (nodes == null) return true; + if (nodes.?.slice.len == 0) return true; + + for (nodes.?.slice) |node| if (self == node) return false; + + return true; + } + // TODO according with https://dom.spec.whatwg.org/#parentnode, the // function must accept either node or string. // blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114 pub fn append(self: *parser.Node, nodes: ?Variadic(*parser.Node)) !void { if (nodes == null) return; if (nodes.?.slice.len == 0) return; + + // check hierarchy + if (!try hierarchy(self, nodes)) return parser.DOMError.HierarchyRequest; + for (nodes.?.slice) |node| { _ = try parser.nodeAppendChild(self, node); } @@ -312,12 +329,15 @@ pub const Node = struct { // function must accept either node or string. // blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114 pub fn replaceChildren(self: *parser.Node, nodes: ?Variadic(*parser.Node)) !void { - // remove existing children - try removeChildren(self); - if (nodes == null) return; if (nodes.?.slice.len == 0) return; + // check hierarchy + if (!try hierarchy(self, nodes)) return parser.DOMError.HierarchyRequest; + + // remove existing children + try removeChildren(self); + // add new children for (nodes.?.slice) |node| { _ = try parser.nodeAppendChild(self, node); From bf522937e17853cab28695a4b4dbd2d38a2555fd Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Mon, 22 Apr 2024 16:22:24 +0200 Subject: [PATCH 10/14] shell: add missing try --- src/main_shell.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main_shell.zig b/src/main_shell.zig index 7eca5f03..8560861a 100644 --- a/src/main_shell.zig +++ b/src/main_shell.zig @@ -40,7 +40,7 @@ fn execJS( try js_env.start(alloc); defer js_env.stop(); - js_env.setUserContext(UserContext{ + try js_env.setUserContext(UserContext{ .document = doc, }); From 840aea9013d29608072fa20b6cf7c9b5b2254995 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Mon, 22 Apr 2024 17:32:29 +0200 Subject: [PATCH 11/14] dom: fix document creation process --- src/dom/document.zig | 2 +- src/dom/implementation.zig | 1 + src/dom/node.zig | 43 ++++++++++++++++++---------------- src/netsurf.zig | 48 +++++++++++++++++++++++++++----------- 4 files changed, 60 insertions(+), 34 deletions(-) diff --git a/src/dom/document.zig b/src/dom/document.zig index 46e24c32..0c5ace87 100644 --- a/src/dom/document.zig +++ b/src/dom/document.zig @@ -53,7 +53,7 @@ pub const Document = struct { if (userctx.document) |cur| { title = try parser.documentHTMLGetTitle(cur); } - const doc = try parser.domImplementationCreateHTMLDocument(title); + const doc = try parser.documentCreateDocument(title); if (userctx.document) |cur| { // we have to work w/ document instead of html document. diff --git a/src/dom/implementation.zig b/src/dom/implementation.zig index 5fa6206d..e4fff404 100644 --- a/src/dom/implementation.zig +++ b/src/dom/implementation.zig @@ -96,6 +96,7 @@ pub fn testExecFn( var getImplementation = [_]Case{ .{ .src = "let impl = document.implementation", .ex = "undefined" }, .{ .src = "impl.createHTMLDocument();", .ex = "[object HTMLDocument]" }, + .{ .src = "impl.createHTMLDocument('foo');", .ex = "[object HTMLDocument]" }, .{ .src = "impl.createDocument(null, 'foo');", .ex = "[object Document]" }, .{ .src = "impl.createDocumentType('foo', 'bar', 'baz')", .ex = "[object DocumentType]" }, .{ .src = "impl.hasFeature()", .ex = "true" }, diff --git a/src/dom/node.zig b/src/dom/node.zig index 1bfee86d..5dd0c811 100644 --- a/src/dom/node.zig +++ b/src/dom/node.zig @@ -277,26 +277,6 @@ pub const Node = struct { return try Node.toInterface(res); } - // TODO according with https://dom.spec.whatwg.org/#parentnode, the - // function must accept either node or string. - // blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114 - pub fn prepend(self: *parser.Node, nodes: ?Variadic(*parser.Node)) !void { - if (nodes == null) return; - if (nodes.?.slice.len == 0) return; - const first = try parser.nodeFirstChild(self); - - if (first == null) { - for (nodes.?.slice) |node| { - _ = try parser.nodeAppendChild(self, node); - } - return; - } - - for (nodes.?.slice) |node| { - _ = try parser.nodeInsertBefore(self, node, first.?); - } - } - // Check if the hierarchy node tree constraints are respected. // For now, it checks only if new nodes are not self. // TODO implements the others contraints. @@ -310,6 +290,29 @@ pub const Node = struct { return true; } + // TODO according with https://dom.spec.whatwg.org/#parentnode, the + // function must accept either node or string. + // blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114 + pub fn prepend(self: *parser.Node, nodes: ?Variadic(*parser.Node)) !void { + if (nodes == null) return; + if (nodes.?.slice.len == 0) return; + + // check hierarchy + if (!try hierarchy(self, nodes)) return parser.DOMError.HierarchyRequest; + + const first = try parser.nodeFirstChild(self); + if (first == null) { + for (nodes.?.slice) |node| { + _ = try parser.nodeAppendChild(self, node); + } + return; + } + + for (nodes.?.slice) |node| { + _ = try parser.nodeInsertBefore(self, node, first.?); + } + } + // TODO according with https://dom.spec.whatwg.org/#parentnode, the // function must accept either node or string. // blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114 diff --git a/src/netsurf.zig b/src/netsurf.zig index 2f8b1efd..0c4f3b8a 100644 --- a/src/netsurf.zig +++ b/src/netsurf.zig @@ -1764,21 +1764,26 @@ pub inline fn domImplementationCreateDocumentType( } pub inline fn domImplementationCreateHTMLDocument(title: ?[]const u8) !*DocumentHTML { - var doc: ?*Document = undefined; - const err = c.dom_implementation_create_document( - c.DOM_IMPLEMENTATION_HTML, - null, - null, - null, - null, - null, - &doc, - ); - try DOMErr(err); + const doc_html = try documentCreateDocument(title); + const doc = documentHTMLToDocument(doc_html); - const doc_html = @as(*DocumentHTML, @ptrCast(doc.?)); + // add hierarchy: html, head, body. + const html = try documentCreateElement(doc, "html"); + _ = try nodeAppendChild(documentToNode(doc), elementToNode(html)); - if (title) |t| try documentHTMLSetTitle(doc_html, t); + const head = try documentCreateElement(doc, "head"); + _ = try nodeAppendChild(elementToNode(html), elementToNode(head)); + + if (title) |t| { + try documentHTMLSetTitle(doc_html, t); + const htitle = try documentCreateElement(doc, "title"); + const txt = try documentCreateTextNode(doc, t); + _ = try nodeAppendChild(elementToNode(htitle), @as(*Node, @ptrCast(txt))); + _ = try nodeAppendChild(elementToNode(head), elementToNode(htitle)); + } + + const body = try documentCreateElement(doc, "body"); + _ = try nodeAppendChild(elementToNode(html), elementToNode(body)); return doc_html; } @@ -1841,6 +1846,23 @@ pub inline fn documentSetInputEncoding(doc: *Document, enc: []const u8) !void { try DOMErr(err); } +pub inline fn documentCreateDocument(title: ?[]const u8) !*DocumentHTML { + var doc: ?*Document = undefined; + const err = c.dom_implementation_create_document( + c.DOM_IMPLEMENTATION_HTML, + null, + null, + null, + null, + null, + &doc, + ); + try DOMErr(err); + const doc_html = @as(*DocumentHTML, @ptrCast(doc.?)); + if (title) |t| try documentHTMLSetTitle(doc_html, t); + return doc_html; +} + pub inline fn documentCreateElement(doc: *Document, tag_name: []const u8) !*Element { var elem: ?*Element = undefined; const err = documentVtable(doc).dom_document_create_element.?(doc, try strFromData(tag_name), &elem); From c2e64c131a3fce2ede3e905c497a52bd8f44135a Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Tue, 23 Apr 2024 11:50:53 +0200 Subject: [PATCH 12/14] userctx: document is not opational anymore --- src/dom/comment.zig | 4 +--- src/dom/document.zig | 20 ++++++++------------ src/dom/document_fragment.zig | 4 +--- src/dom/text.zig | 4 +--- src/user_context.zig | 2 +- 5 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/dom/comment.zig b/src/dom/comment.zig index ff7ebace..e82c51bc 100644 --- a/src/dom/comment.zig +++ b/src/dom/comment.zig @@ -34,10 +34,8 @@ pub const Comment = struct { pub const mem_guarantied = true; pub fn constructor(userctx: UserContext, data: ?[]const u8) !*parser.Comment { - if (userctx.document == null) return parser.DOMError.NotSupported; - return parser.documentCreateComment( - parser.documentHTMLToDocument(userctx.document.?), + parser.documentHTMLToDocument(userctx.document), data orelse "", ); } diff --git a/src/dom/document.zig b/src/dom/document.zig index 0c5ace87..0741b1d9 100644 --- a/src/dom/document.zig +++ b/src/dom/document.zig @@ -49,19 +49,15 @@ pub const Document = struct { pub const mem_guarantied = true; pub fn constructor(userctx: UserContext) !*parser.DocumentHTML { - var title: ?[]const u8 = null; - if (userctx.document) |cur| { - title = try parser.documentHTMLGetTitle(cur); - } - const doc = try parser.documentCreateDocument(title); + const doc = try parser.documentCreateDocument( + try parser.documentHTMLGetTitle(userctx.document), + ); - if (userctx.document) |cur| { - // we have to work w/ document instead of html document. - const ddoc = parser.documentHTMLToDocument(doc); - const ccur = parser.documentHTMLToDocument(cur); - try parser.documentSetDocumentURI(ddoc, try parser.documentGetDocumentURI(ccur)); - try parser.documentSetInputEncoding(ddoc, try parser.documentGetInputEncoding(ccur)); - } + // we have to work w/ document instead of html document. + const ddoc = parser.documentHTMLToDocument(doc); + const ccur = parser.documentHTMLToDocument(userctx.document); + try parser.documentSetDocumentURI(ddoc, try parser.documentGetDocumentURI(ccur)); + try parser.documentSetInputEncoding(ddoc, try parser.documentGetInputEncoding(ccur)); return doc; } diff --git a/src/dom/document_fragment.zig b/src/dom/document_fragment.zig index da21798e..08d99165 100644 --- a/src/dom/document_fragment.zig +++ b/src/dom/document_fragment.zig @@ -35,10 +35,8 @@ pub const DocumentFragment = struct { pub const mem_guarantied = true; pub fn constructor(userctx: UserContext) !*parser.DocumentFragment { - if (userctx.document == null) return parser.DOMError.NotSupported; - return parser.documentCreateDocumentFragment( - parser.documentHTMLToDocument(userctx.document.?), + parser.documentHTMLToDocument(userctx.document), ); } }; diff --git a/src/dom/text.zig b/src/dom/text.zig index 6805825e..4ac35ed5 100644 --- a/src/dom/text.zig +++ b/src/dom/text.zig @@ -41,10 +41,8 @@ pub const Text = struct { pub const mem_guarantied = true; pub fn constructor(userctx: UserContext, data: ?[]const u8) !*parser.Text { - if (userctx.document == null) return parser.DOMError.NotSupported; - return parser.documentCreateTextNode( - parser.documentHTMLToDocument(userctx.document.?), + parser.documentHTMLToDocument(userctx.document), data orelse "", ); } diff --git a/src/user_context.zig b/src/user_context.zig index a2bc3efb..0a90bfd4 100644 --- a/src/user_context.zig +++ b/src/user_context.zig @@ -2,5 +2,5 @@ const std = @import("std"); const parser = @import("netsurf.zig"); pub const UserContext = struct { - document: ?*parser.DocumentHTML, + document: *parser.DocumentHTML, }; From 00d75584dbcd015f9406bfa208ce569bcb9d5c7c Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Tue, 23 Apr 2024 12:09:41 +0200 Subject: [PATCH 13/14] usrctx: use ctx http client with xhr --- src/browser/browser.zig | 13 +++++++++++++ src/main_shell.zig | 5 +++++ src/run_tests.zig | 15 +++++++++------ src/user_context.zig | 2 ++ src/wpt/run.zig | 6 ++++++ src/xhr/xhr.zig | 12 +++++------- 6 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/browser/browser.zig b/src/browser/browser.zig index b17173da..19982663 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -39,6 +39,9 @@ const storage = @import("../storage/storage.zig"); const FetchResult = std.http.Client.FetchResult; +const UserContext = @import("../user_context.zig").UserContext; +const HttpClient = @import("../async/Client.zig"); + const log = std.log.scoped(.browser); // Browser is an instance of the browser. @@ -92,6 +95,7 @@ pub const Session = struct { // TODO move the shed to the browser? storageShed: storage.Shed, page: ?*Page = null, + httpClient: HttpClient, jstypes: [Types.len]usize = undefined, @@ -105,9 +109,11 @@ pub const Session = struct { .loader = Loader.init(alloc), .loop = try Loop.init(alloc), .storageShed = storage.Shed.init(alloc), + .httpClient = undefined, }; self.env = try Env.init(self.arena.allocator(), &self.loop, null); + self.httpClient = .{ .allocator = alloc, .loop = &self.loop }; try self.env.load(&self.jstypes); return self; @@ -122,6 +128,7 @@ pub const Session = struct { self.loader.deinit(); self.loop.deinit(); self.storageShed.deinit(); + self.httpClient.deinit(); self.alloc.destroy(self); } @@ -289,6 +296,12 @@ pub const Page = struct { log.debug("start js env", .{}); try self.session.env.start(alloc); + // replace the user context document with the new one. + try self.session.env.setUserContext(.{ + .document = html_doc, + .httpClient = &self.session.httpClient, + }); + // add global objects log.debug("setup global env", .{}); try self.session.env.bindGlobal(&self.session.window); diff --git a/src/main_shell.zig b/src/main_shell.zig index 8560861a..6eb6c11a 100644 --- a/src/main_shell.zig +++ b/src/main_shell.zig @@ -29,6 +29,7 @@ const html_test = @import("html_test.zig").html; pub const Types = jsruntime.reflect(apiweb.Interfaces); pub const UserContext = apiweb.UserContext; +const Client = @import("async/Client.zig"); var doc: *parser.DocumentHTML = undefined; @@ -40,8 +41,12 @@ fn execJS( try js_env.start(alloc); defer js_env.stop(); + var cli = Client{ .allocator = alloc, .loop = js_env.nat_ctx.loop }; + defer cli.deinit(); + try js_env.setUserContext(UserContext{ .document = doc, + .httpClient = &cli, }); var storageShelf = storage.Shelf.init(alloc); diff --git a/src/run_tests.zig b/src/run_tests.zig index 5eb470be..700d15c0 100644 --- a/src/run_tests.zig +++ b/src/run_tests.zig @@ -30,6 +30,7 @@ const xhr = @import("xhr/xhr.zig"); const storage = @import("storage/storage.zig"); const url = @import("url/url.zig"); const urlquery = @import("url/query.zig"); +const Client = @import("async/Client.zig"); const documentTestExecFn = @import("dom/document.zig").testExecFn; const HTMLDocumentTestExecFn = @import("html/document.zig").testExecFn; @@ -84,7 +85,13 @@ fn testExecFn( std.debug.print("documentHTMLClose error: {s}\n", .{@errorName(err)}); }; - js_env.getUserContext().?.document = doc; + var cli = Client{ .allocator = alloc, .loop = js_env.nat_ctx.loop }; + defer cli.deinit(); + + try js_env.setUserContext(.{ + .document = doc, + .httpClient = &cli, + }); // alias global as self and window var window = Window.create(null); @@ -322,11 +329,7 @@ fn testJSRuntime(alloc: std.mem.Allocator) !void { var arena_alloc = std.heap.ArenaAllocator.init(alloc); defer arena_alloc.deinit(); - const userctx = UserContext{ - .document = null, - }; - - try jsruntime.loadEnv(&arena_alloc, userctx, testsAllExecFn); + try jsruntime.loadEnv(&arena_alloc, null, testsAllExecFn); } test "DocumentHTMLParseFromStr" { diff --git a/src/user_context.zig b/src/user_context.zig index 0a90bfd4..4860100d 100644 --- a/src/user_context.zig +++ b/src/user_context.zig @@ -1,6 +1,8 @@ const std = @import("std"); const parser = @import("netsurf.zig"); +const Client = @import("async/Client.zig"); pub const UserContext = struct { document: *parser.DocumentHTML, + httpClient: *Client, }; diff --git a/src/wpt/run.zig b/src/wpt/run.zig index 8b0e7516..4322ee40 100644 --- a/src/wpt/run.zig +++ b/src/wpt/run.zig @@ -31,6 +31,7 @@ const storage = @import("../storage/storage.zig"); const Types = @import("../main_wpt.zig").Types; const UserContext = @import("../main_wpt.zig").UserContext; +const Client = @import("../async/Client.zig"); // runWPT parses the given HTML file, starts a js env and run the first script // tags containing javascript sources. @@ -51,8 +52,13 @@ pub fn run(arena: *std.heap.ArenaAllocator, comptime dir: []const u8, f: []const // create JS env var loop = try Loop.init(alloc); defer loop.deinit(); + + var cli = Client{ .allocator = alloc, .loop = &loop }; + defer cli.deinit(); + var js_env = try Env.init(alloc, &loop, UserContext{ .document = html_doc, + .httpClient = &cli, }); defer js_env.deinit(); diff --git a/src/xhr/xhr.zig b/src/xhr/xhr.zig index 8719e2fc..e395144a 100644 --- a/src/xhr/xhr.zig +++ b/src/xhr/xhr.zig @@ -37,6 +37,8 @@ const Client = @import("../async/Client.zig"); const parser = @import("../netsurf.zig"); +const UserContext = @import("../user_context.zig").UserContext; + const log = std.log.scoped(.xhr); // XHR interfaces @@ -149,7 +151,7 @@ pub const XMLHttpRequest = struct { proto: XMLHttpRequestEventTarget = XMLHttpRequestEventTarget{}, alloc: std.mem.Allocator, - cli: Client, + cli: *Client, impl: YieldImpl, priv_state: PrivState = .new, @@ -185,7 +187,7 @@ pub const XMLHttpRequest = struct { const min_delay: u64 = 50000000; // 50ms - pub fn constructor(alloc: std.mem.Allocator, loop: *Loop) !XMLHttpRequest { + pub fn constructor(alloc: std.mem.Allocator, loop: *Loop, userctx: UserContext) !XMLHttpRequest { return .{ .alloc = alloc, .headers = .{ .allocator = alloc, .owned = true }, @@ -195,8 +197,7 @@ pub const XMLHttpRequest = struct { .url = null, .uri = undefined, .state = UNSENT, - // TODO retrieve the HTTP client globally to reuse existing connections. - .cli = .{ .allocator = alloc, .loop = loop }, + .cli = userctx.httpClient, }; } @@ -235,9 +236,6 @@ pub const XMLHttpRequest = struct { self.response_headers.deinit(); self.proto.deinit(alloc); - - // TODO the client must be shared between requests. - self.cli.deinit(); } pub fn get_readyState(self: *XMLHttpRequest) u16 { From 9ac46ea0cb133d4b9767e0134b501199bec5f60e Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Wed, 22 May 2024 14:59:36 +0200 Subject: [PATCH 14/14] upgrade zig-js-runtime --- vendor/zig-js-runtime | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/zig-js-runtime b/vendor/zig-js-runtime index fff1a677..d4a2eaef 160000 --- a/vendor/zig-js-runtime +++ b/vendor/zig-js-runtime @@ -1 +1 @@ -Subproject commit fff1a6778dfc91d71d77e5c593283b2aba78432f +Subproject commit d4a2eaefd8390b9483e5ad58d2992dc381632559