From d7f209b70a273b5a60daa1b7666b2878071f8429 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Tue, 17 Jun 2025 18:33:28 +0800 Subject: [PATCH] experiment with executing custom element connectedCallback --- src/browser/dom/document.zig | 20 ++---- src/browser/netsurf.zig | 6 +- src/browser/page.zig | 72 ++++++++++++++----- .../webcomponents/custom_element_registry.zig | 32 +++++++-- src/runtime/js.zig | 10 ++- vendor/netsurf/libdom | 2 +- 6 files changed, 100 insertions(+), 42 deletions(-) diff --git a/src/browser/dom/document.zig b/src/browser/dom/document.zig index fe4a499a..ee022db5 100644 --- a/src/browser/dom/document.zig +++ b/src/browser/dom/document.zig @@ -127,22 +127,12 @@ pub const Document = struct { }; pub fn _createElement(self: *parser.Document, tag_name: []const u8, page: *Page) !CreateElementResult { - const custom_element = page.window.custom_elements._get(tag_name) orelse { - const e = try parser.documentCreateElement(self, tag_name); - return .{ .element = try Element.toInterface(e) }; - }; + if (try page.window.custom_elements.newInstance(tag_name)) |ce| { + return .{ .custom = ce }; + } - var result: Env.Function.Result = undefined; - const js_obj = custom_element.newInstance(&result) catch |err| { - log.fatal(.user_script, "newInstance error", .{ - .err = result.exception, - .stack = result.stack, - .tag_name = tag_name, - .source = "createElement", - }); - return err; - }; - return .{ .custom = js_obj }; + const e = try parser.documentCreateElement(self, tag_name); + return .{ .element = try Element.toInterface(e) }; } pub fn _createElementNS(self: *parser.Document, ns: []const u8, tag_name: []const u8) !ElementUnion { diff --git a/src/browser/netsurf.zig b/src/browser/netsurf.zig index 03db92e2..19601504 100644 --- a/src/browser/netsurf.zig +++ b/src/browser/netsurf.zig @@ -2189,12 +2189,12 @@ pub inline fn documentCreateAttributeNS(doc: *Document, ns: []const u8, qname: [ return attr.?; } -pub fn documentSetScriptAddedCallback( +pub fn documentSetElementAddedCallback( doc: *Document, ctx: *anyopaque, - callback: c.dom_script_added_callback, + callback: c.dom_element_added_callback, ) void { - c._dom_document_set_script_added_callback(doc, ctx, callback); + c._dom_document_set_element_added_callback(doc, ctx, callback); } // DocumentHTML diff --git a/src/browser/page.zig b/src/browser/page.zig index 368a3c5a..f0459114 100644 --- a/src/browser/page.zig +++ b/src/browser/page.zig @@ -297,16 +297,17 @@ pub const Page = struct { self.window.setStorageShelf( try self.session.storage_shed.getOrPut(try self.origin(self.arena)), ); + + // we want to be notified of any dynamically added script tags + // so that we can load the script. Or dynamically added custom elements + // for their lifecycle callbacks. + parser.documentSetElementAddedCallback(doc, self, elementAddedCallback); } fn processHTMLDoc(self: *Page) !void { const html_doc = self.window.document; const doc = parser.documentHTMLToDocument(html_doc); - // we want to be notified of any dynamically added script tags - // so that we can load the script - parser.documentSetScriptAddedCallback(doc, self, scriptAddedCallback); - const document_element = (try parser.documentGetDocumentElement(doc)) orelse return error.DocumentElementError; _ = try parser.eventTargetAddEventListener( parser.toEventTarget(parser.Element, document_element), @@ -817,6 +818,43 @@ pub const Page = struct { } return null; } + + fn elementAdded(self: *Page, element: *parser.Element) !void { + if (self.delayed_navigation) { + // if we're planning on navigating to another page, we can skip whatever + // this is. + return; + } + + switch (try parser.elementHTMLGetTagType(@as(*parser.ElementHTML, @ptrCast(element)))) { + .script => { + var script = Script.init(element, self) catch |err| { + log.warn(.browser, "script added", .{ .err = err }); + return; + } orelse return; + _ = self.evalScript(&script); + }, + .undef => { + // a custom element + const js_obj = self.main_context.getJsObject(element) orelse return; + + // @memory + // getFunction, and more generally Env.Function always create + // a Persisted Object. But, in cases like this, we don't need + // it to persist. + const func = (try js_obj.getFunction("connectedCallback")) orelse return; + + var result: Env.Function.Result = undefined; + func.tryCallWithThis(void, js_obj, .{}, &result) catch { + log.warn(.user_script, "connected callback", .{ + .err = result.exception, + .stack = result.stack, + }); + }; + }, + else => {}, + } + } }; const DelayedNavigation = struct { @@ -1090,24 +1128,22 @@ fn timestamp() u32 { return @intCast(ts.sec); } -// A callback from libdom whenever a script tag is added to the DOM. +// A callback from libdom whenever an html element is added to the DOM. +// Note that "added" could mean that it was removed from one parent and re-added +// to another, which is how MOST APIs implement "move" (corrently so). +// +// The only API which seems to actual "move" is Element.moveBefore, which we +// don't currently implement, but should support in general, and should handle +// specifically here. // element is guaranteed to be a script element. -// The script tag might not have a src. It might be any attribute, like +// The script tag might not have a src. It might have any attribute, like // `nomodule`, `defer` and `async`. `Script.init` will return null on `nomodule` // so that's handled. And because we're only executing the inline