diff --git a/src/browser/dom/document.zig b/src/browser/dom/document.zig index bc8dccbc..fe4a499a 100644 --- a/src/browser/dom/document.zig +++ b/src/browser/dom/document.zig @@ -18,6 +18,7 @@ const std = @import("std"); +const log = @import("../../log.zig"); const parser = @import("../netsurf.zig"); const Page = @import("../page.zig").Page; @@ -120,9 +121,28 @@ pub const Document = struct { return try Element.toInterface(e); } - pub fn _createElement(self: *parser.Document, tag_name: []const u8) !ElementUnion { - const e = try parser.documentCreateElement(self, tag_name); - return try Element.toInterface(e); + const CreateElementResult = union(enum) { + element: ElementUnion, + custom: Env.JsObject, + }; + + 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) }; + }; + + 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 }; } pub fn _createElementNS(self: *parser.Document, ns: []const u8, tag_name: []const u8) !ElementUnion { diff --git a/src/browser/env.zig b/src/browser/env.zig index 7d6627ec..806391e5 100644 --- a/src/browser/env.zig +++ b/src/browser/env.zig @@ -33,6 +33,7 @@ const WebApis = struct { @import("xhr/xhr.zig").Interfaces, @import("xhr/form_data.zig").Interfaces, @import("xmlserializer/xmlserializer.zig").Interfaces, + @import("webcomponents/webcomponents.zig").Interfaces, }); }; diff --git a/src/browser/html/elements.zig b/src/browser/html/elements.zig index 3ce70361..948810a2 100644 --- a/src/browser/html/elements.zig +++ b/src/browser/html/elements.zig @@ -16,6 +16,7 @@ // 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 log = @import("../../log.zig"); const parser = @import("../netsurf.zig"); const generate = @import("../../runtime/generate.zig"); @@ -112,6 +113,10 @@ pub const HTMLElement = struct { pub const prototype = *Element; pub const subtype = .node; + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } + pub fn get_style(e: *parser.ElementHTML, page: *Page) !*CSSStyleDeclaration { const state = try page.getOrCreateNodeState(@ptrCast(e)); return &state.style; @@ -174,6 +179,10 @@ pub const HTMLMediaElement = struct { pub const Self = parser.MediaElement; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; // HTML elements @@ -183,6 +192,10 @@ pub const HTMLUnknownElement = struct { pub const Self = parser.Unknown; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; // https://html.spec.whatwg.org/#the-a-element @@ -191,6 +204,10 @@ pub const HTMLAnchorElement = struct { pub const prototype = *HTMLElement; pub const subtype = .node; + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } + pub fn get_target(self: *parser.Anchor) ![]const u8 { return try parser.anchorGetTarget(self); } @@ -428,144 +445,240 @@ pub const HTMLAppletElement = struct { pub const Self = parser.Applet; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLAreaElement = struct { pub const Self = parser.Area; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLAudioElement = struct { pub const Self = parser.Audio; pub const prototype = *HTMLMediaElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLBRElement = struct { pub const Self = parser.BR; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLBaseElement = struct { pub const Self = parser.Base; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLBodyElement = struct { pub const Self = parser.Body; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLButtonElement = struct { pub const Self = parser.Button; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLCanvasElement = struct { pub const Self = parser.Canvas; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLDListElement = struct { pub const Self = parser.DList; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLDataElement = struct { pub const Self = parser.Data; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLDataListElement = struct { pub const Self = parser.DataList; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLDialogElement = struct { pub const Self = parser.Dialog; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLDirectoryElement = struct { pub const Self = parser.Directory; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLDivElement = struct { pub const Self = parser.Div; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLEmbedElement = struct { pub const Self = parser.Embed; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLFieldSetElement = struct { pub const Self = parser.FieldSet; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLFontElement = struct { pub const Self = parser.Font; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLFrameElement = struct { pub const Self = parser.Frame; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLFrameSetElement = struct { pub const Self = parser.FrameSet; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLHRElement = struct { pub const Self = parser.HR; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLHeadElement = struct { pub const Self = parser.Head; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLHeadingElement = struct { pub const Self = parser.Heading; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLHtmlElement = struct { pub const Self = parser.Html; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLIFrameElement = struct { pub const Self = parser.IFrame; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLImageElement = struct { @@ -573,6 +686,10 @@ pub const HTMLImageElement = struct { pub const prototype = *HTMLElement; pub const subtype = .node; + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } + pub fn get_alt(self: *parser.Image) ![]const u8 { return try parser.imageGetAlt(self); } @@ -613,6 +730,7 @@ pub const HTMLImageElement = struct { pub const Factory = struct { pub const js_name = "Image"; pub const subtype = .node; + pub const js_legacy_factory = true; pub const prototype = *HTMLImageElement; @@ -631,6 +749,10 @@ pub const HTMLInputElement = struct { pub const prototype = *HTMLElement; pub const subtype = .node; + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } + pub fn get_defaultValue(self: *parser.Input) ![]const u8 { return try parser.inputGetDefaultValue(self); } @@ -719,114 +841,190 @@ pub const HTMLLIElement = struct { pub const Self = parser.LI; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLLabelElement = struct { pub const Self = parser.Label; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLLegendElement = struct { pub const Self = parser.Legend; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLLinkElement = struct { pub const Self = parser.Link; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLMapElement = struct { pub const Self = parser.Map; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLMetaElement = struct { pub const Self = parser.Meta; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLMeterElement = struct { pub const Self = parser.Meter; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLModElement = struct { pub const Self = parser.Mod; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLOListElement = struct { pub const Self = parser.OList; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLObjectElement = struct { pub const Self = parser.Object; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLOptGroupElement = struct { pub const Self = parser.OptGroup; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLOptionElement = struct { pub const Self = parser.Option; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLOutputElement = struct { pub const Self = parser.Output; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLParagraphElement = struct { pub const Self = parser.Paragraph; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLParamElement = struct { pub const Self = parser.Param; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLPictureElement = struct { pub const Self = parser.Picture; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLPreElement = struct { pub const Self = parser.Pre; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLProgressElement = struct { pub const Self = parser.Progress; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLQuoteElement = struct { pub const Self = parser.Quote; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; // https://html.spec.whatwg.org/#the-script-element @@ -835,6 +1033,10 @@ pub const HTMLScriptElement = struct { pub const prototype = *HTMLElement; pub const subtype = .node; + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } + pub fn get_src(self: *parser.Script) !?[]const u8 { return try parser.elementGetAttribute( parser.scriptToElt(self), @@ -969,101 +1171,166 @@ pub const HTMLSourceElement = struct { pub const Self = parser.Source; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLSpanElement = struct { pub const Self = parser.Span; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLStyleElement = struct { pub const Self = parser.Style; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLTableElement = struct { pub const Self = parser.Table; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLTableCaptionElement = struct { pub const Self = parser.TableCaption; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLTableCellElement = struct { pub const Self = parser.TableCell; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLTableColElement = struct { pub const Self = parser.TableCol; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLTableRowElement = struct { pub const Self = parser.TableRow; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLTableSectionElement = struct { pub const Self = parser.TableSection; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLTemplateElement = struct { pub const Self = parser.Template; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLTextAreaElement = struct { pub const Self = parser.TextArea; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLTimeElement = struct { pub const Self = parser.Time; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLTitleElement = struct { pub const Self = parser.Title; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLTrackElement = struct { pub const Self = parser.Track; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLUListElement = struct { pub const Self = parser.UList; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub const HTMLVideoElement = struct { pub const Self = parser.Video; pub const prototype = *HTMLElement; pub const subtype = .node; + + pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { + return constructHtmlElement(page, js_this); + } }; pub fn toInterface(comptime T: type, e: *parser.Element) !T { const elem: *align(@alignOf(*parser.Element)) parser.Element = @alignCast(e); const tag = try parser.elementHTMLGetTagType(@as(*parser.ElementHTML, @ptrCast(elem))); + return switch (tag) { .abbr, .acronym, .address, .article, .aside, .b, .basefont, .bdi, .bdo, .bgsound, .big, .center, .cite, .code, .dd, .details, .dfn, .dt, .em, .figcaption, .figure, .footer, .header, .hgroup, .i, .isindex, .keygen, .kbd, .main, .mark, .marquee, .menu, .menuitem, .nav, .nobr, .noframes, .noscript, .rp, .rt, .ruby, .s, .samp, .section, .small, .spacer, .strike, .strong, .sub, .summary, .sup, .tt, .u, .wbr, ._var => .{ .HTMLElement = @as(*parser.ElementHTML, @ptrCast(elem)) }, .a => .{ .HTMLAnchorElement = @as(*parser.Anchor, @ptrCast(elem)) }, @@ -1135,6 +1402,16 @@ pub fn toInterface(comptime T: type, e: *parser.Element) !T { }; } +fn constructHtmlElement(page: *Page, js_this: Env.JsThis) !*parser.Element { + const constructor_name = try js_this.constructorName(page.call_arena); + if (!page.window.custom_elements.lookup.contains(constructor_name)) { + return error.IllegalContructor; + } + + const el = try parser.documentCreateElement(@ptrCast(page.window.document), constructor_name); + return el; +} + const testing = @import("../../testing.zig"); test "Browser.HTML.Element" { var runner = try testing.jsRunner(testing.tracking_allocator, .{}); diff --git a/src/browser/html/window.zig b/src/browser/html/window.zig index f2fd3a18..74fa28b3 100644 --- a/src/browser/html/window.zig +++ b/src/browser/html/window.zig @@ -33,6 +33,7 @@ const EventTarget = @import("../dom/event_target.zig").EventTarget; const MediaQueryList = @import("media_query_list.zig").MediaQueryList; const Performance = @import("performance.zig").Performance; const CSSStyleDeclaration = @import("../cssom/css_style_declaration.zig").CSSStyleDeclaration; +const CustomElementRegistry = @import("../webcomponents/custom_element_registry.zig").CustomElementRegistry; const storage = @import("../storage/storage.zig"); @@ -58,6 +59,7 @@ pub const Window = struct { console: Console = .{}, navigator: Navigator = .{}, performance: Performance, + custom_elements: CustomElementRegistry = .{}, pub fn create(target: ?[]const u8, navigator: ?Navigator) !Window { var fbs = std.io.fixedBufferStream(""); @@ -163,6 +165,10 @@ pub const Window = struct { return &self.performance; } + pub fn get_customElements(self: *Window) *CustomElementRegistry { + return &self.custom_elements; + } + pub fn _requestAnimationFrame(self: *Window, cbk: Function, page: *Page) !u32 { return self.createTimeout(cbk, 5, page, .{ .animation_frame = true }); } diff --git a/src/browser/page.zig b/src/browser/page.zig index 396022db..368a3c5a 100644 --- a/src/browser/page.zig +++ b/src/browser/page.zig @@ -352,8 +352,23 @@ pub const Page = struct { continue; } - const e = parser.nodeToElement(next.?); + const current = next.?; + + const e = parser.nodeToElement(current); const tag = try parser.elementHTMLGetTagType(@as(*parser.ElementHTML, @ptrCast(e))); + + // if (tag == .undef) { + // const tag_name = try parser.nodeLocalName(@ptrCast(e)); + // const custom_elements = &self.window.custom_elements; + // if (custom_elements._get(tag_name)) |construct| { + // try construct.printFunc(); + // // This is just here for testing for now. + // // var result: Env.Function.Result = undefined; + // // _ = try construct.newInstance(*parser.Element, &result); + // log.info(.browser, "Registered WebComponent Found", .{ .element_name = tag_name }); + // } + // } + if (tag != .script) { // ignore non-js script. continue; diff --git a/src/browser/webcomponents/custom_element_registry.zig b/src/browser/webcomponents/custom_element_registry.zig new file mode 100644 index 00000000..3e429a67 --- /dev/null +++ b/src/browser/webcomponents/custom_element_registry.zig @@ -0,0 +1,91 @@ +// 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 log = @import("../../log.zig"); +const v8 = @import("v8"); + +const Env = @import("../env.zig").Env; +const Page = @import("../page.zig").Page; + +const Element = @import("../dom/element.zig").Element; + +pub const CustomElementRegistry = struct { + // tag_name -> Function + lookup: std.StringHashMapUnmanaged(Env.Function) = .empty, + + pub fn _define(self: *CustomElementRegistry, tag_name: []const u8, fun: Env.Function, page: *Page) !void { + log.info(.browser, "define custom element", .{ .name = tag_name }); + + const arena = page.arena; + const gop = try self.lookup.getOrPut(arena, tag_name); + if (!gop.found_existing) { + errdefer _ = self.lookup.remove(tag_name); + const owned_tag_name = try arena.dupe(u8, tag_name); + gop.key_ptr.* = owned_tag_name; + } + gop.value_ptr.* = fun; + fun.setName(tag_name); + } + + pub fn _get(self: *CustomElementRegistry, name: []const u8) ?Env.Function { + return self.lookup.get(name); + } +}; + +const testing = @import("../../testing.zig"); + +test "Browser.CustomElementRegistry" { + var runner = try testing.jsRunner(testing.tracking_allocator, .{}); + defer runner.deinit(); + try runner.testCases(&.{ + // Basic registry access + .{ "typeof customElements", "object" }, + .{ "customElements instanceof CustomElementRegistry", "true" }, + + // Define a simple custom element + .{ + \\ class MyElement extends HTMLElement { + \\ constructor() { + \\ super(); + \\ this.textContent = 'Hello World'; + \\ } + \\ } + , + null, + }, + .{ "customElements.define('my-element', MyElement)", "undefined" }, + + // Check if element is defined + .{ "customElements.get('my-element') === MyElement", "true" }, + // .{ "customElements.get('non-existent')", "null" }, + + // Create element via document.createElement + .{ "let el = document.createElement('my-element')", "undefined" }, + .{ "el instanceof MyElement", "true" }, + .{ "el instanceof HTMLElement", "true" }, + .{ "el.tagName", "MY-ELEMENT" }, + .{ "el.textContent", "Hello World" }, + + // Create element via HTML parsing + // .{ "document.body.innerHTML = ''", "undefined" }, + // .{ "let parsed = document.querySelector('my-element')", "undefined" }, + // .{ "parsed instanceof MyElement", "true" }, + // .{ "parsed.textContent", "Hello World" }, + }, .{}); +} diff --git a/src/browser/webcomponents/webcomponents.zig b/src/browser/webcomponents/webcomponents.zig new file mode 100644 index 00000000..2ba2a30f --- /dev/null +++ b/src/browser/webcomponents/webcomponents.zig @@ -0,0 +1,23 @@ +// 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 CustomElementRegistry = @import("custom_element_registry.zig").CustomElementRegistry; + +pub const Interfaces = .{ + CustomElementRegistry, +}; diff --git a/src/runtime/js.zig b/src/runtime/js.zig index 2a3e5635..083b86e3 100644 --- a/src/runtime/js.zig +++ b/src/runtime/js.zig @@ -1257,6 +1257,16 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { exception: []const u8, }; + pub fn getName(self: *const Function, allocator: Allocator) ![]const u8 { + const name = self.func.castToFunction().getName(); + return valueToString(allocator, name, self.js_context.isolate, self.js_context.v8_context); + } + + pub fn setName(self: *const Function, name: []const u8) void { + const v8_name = v8.String.initUtf8(self.js_context.isolate, name); + self.func.castToFunction().setName(v8_name); + } + pub fn withThis(self: *const Function, value: anytype) !Function { const this_obj = if (@TypeOf(value) == JsObject) value.js_obj @@ -1271,6 +1281,33 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { }; } + pub fn newInstance(self: *const Function, result: *Result) !JsObject { + const context = self.js_context; + + var try_catch: TryCatch = undefined; + try_catch.init(context); + defer try_catch.deinit(); + + // This creates a new instance using this Function as a constructor. + // This returns a generic Object + const js_obj = self.func.castToFunction().initInstance(context.v8_context, &.{}) orelse { + if (try_catch.hasCaught()) { + const allocator = context.call_arena; + result.stack = try_catch.stack(allocator) catch null; + result.exception = (try_catch.exception(allocator) catch "???") orelse "???"; + } else { + result.stack = null; + result.exception = "???"; + } + return error.JsConstructorFailed; + }; + + return .{ + .js_context = context, + .js_obj = js_obj, + }; + } + pub fn call(self: *const Function, comptime T: type, args: anytype) !T { return self.callWithThis(T, self.getThis(), args); } @@ -1450,6 +1487,11 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { .js_obj = array.castTo(v8.Object), }; } + + pub fn constructorName(self: JsObject, allocator: Allocator) ![]const u8 { + const str = try self.js_obj.getConstructorName(); + return jsStringToZig(allocator, str, self.js_context.isolate); + } }; // This only exists so that we know whether a function wants the opaque @@ -1472,6 +1514,10 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { pub fn set(self: JsThis, key: []const u8, value: anytype, opts: JsObject.SetOpts) !void { return self.obj.set(key, value, opts); } + + pub fn constructorName(self: JsThis, allocator: Allocator) ![]const u8 { + return try self.obj.constructorName(allocator); + } }; pub const TryCatch = struct { @@ -1784,7 +1830,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { // a constructor function, we'll return an error. if (@hasDecl(Struct, "constructor") == false) { const iso = caller.isolate; - const js_exception = iso.throwException(createException(iso, "illegal constructor")); + const js_exception = iso.throwException(createException(iso, "Illegal Constructor")); info.getReturnValue().set(js_exception); return; } @@ -2543,6 +2589,7 @@ fn Caller(comptime E: type, comptime State: type) type { var js_err: ?v8.Value = switch (err) { error.InvalidArgument => createTypeException(isolate, "invalid argument"), error.OutOfMemory => createException(isolate, "out of memory"), + error.IllegalConstructor => createException(isolate, "Illegal Contructor"), else => blk: { const func = @field(Struct, named_function.name); const return_type = @typeInfo(@TypeOf(func)).@"fn".return_type orelse { @@ -2656,8 +2703,8 @@ fn Caller(comptime E: type, comptime State: type) type { // a JS argument if (comptime isJsThis(params[params.len - 1].type.?)) { @field(args, std.fmt.comptimePrint("{d}", .{params.len - 1 + offset})) = .{ .obj = .{ + .js_context = js_context, .js_obj = info.getThis(), - .executor = self.executor, } }; // AND the 2nd last parameter is state