From 818f4540fd9ee82f617462aa6edb91f8db4b583e Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Fri, 11 Jul 2025 17:32:01 +0800 Subject: [PATCH] Add basic ShadowRoot implementation, polyfill webcomponents --- src/browser/State.zig | 3 + src/browser/dom/document.zig | 27 +- src/browser/dom/element.zig | 40 +++ src/browser/dom/shadow_root.zig | 66 +++++ src/browser/env.zig | 2 +- src/browser/html/elements.zig | 274 ------------------ src/browser/html/window.zig | 6 - src/browser/page.zig | 12 - src/browser/polyfill/fetch.zig | 2 - src/browser/polyfill/polyfill.zig | 44 ++- src/browser/polyfill/webcomponents.js | 61 ++++ src/browser/polyfill/webcomponents.zig | 35 +++ .../webcomponents/custom_element_registry.zig | 2 + src/browser/webcomponents/webcomponents.zig | 23 -- 14 files changed, 242 insertions(+), 355 deletions(-) create mode 100644 src/browser/dom/shadow_root.zig create mode 100644 src/browser/polyfill/webcomponents.js create mode 100644 src/browser/polyfill/webcomponents.zig delete mode 100644 src/browser/webcomponents/webcomponents.zig diff --git a/src/browser/State.zig b/src/browser/State.zig index 90e5494d..930a8815 100644 --- a/src/browser/State.zig +++ b/src/browser/State.zig @@ -29,6 +29,7 @@ const Env = @import("env.zig").Env; const parser = @import("netsurf.zig"); const DataSet = @import("html/DataSet.zig"); +const ShadowRoot = @import("dom/shadow_root.zig").ShadowRoot; const CSSStyleDeclaration = @import("cssom/css_style_declaration.zig").CSSStyleDeclaration; // for HTMLScript (but probably needs to be added to more) @@ -62,6 +63,8 @@ explicit_index_set: bool = false, template_content: ?*parser.DocumentFragment = null, +shadow_root: ?*ShadowRoot = null, + const ReadyState = enum { loading, interactive, diff --git a/src/browser/dom/document.zig b/src/browser/dom/document.zig index 911c3015..e446cc9d 100644 --- a/src/browser/dom/document.zig +++ b/src/browser/dom/document.zig @@ -123,28 +123,11 @@ pub const Document = struct { 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 _createElement(self: *parser.Document, tag_name: []const u8) !ElementUnion { + // The element’s namespace is the HTML namespace when document is an HTML document + // https://dom.spec.whatwg.org/#ref-for-dom-document-createelement%E2%91%A0 + const e = try parser.documentCreateElementNS(self, "http://www.w3.org/1999/xhtml", tag_name); + return Element.toInterface(e); } pub fn _createElementNS(self: *parser.Document, ns: []const u8, tag_name: []const u8) !ElementUnion { diff --git a/src/browser/dom/element.zig b/src/browser/dom/element.zig index f8cde653..753ad707 100644 --- a/src/browser/dom/element.zig +++ b/src/browser/dom/element.zig @@ -30,6 +30,8 @@ const Node = @import("node.zig").Node; const Walker = @import("walker.zig").WalkerDepthFirst; const NodeList = @import("nodelist.zig").NodeList; const HTMLElem = @import("../html/elements.zig"); +const ShadowRoot = @import("../dom/shadow_root.zig").ShadowRoot; + pub const Union = @import("../html/elements.zig").Union; // WEB IDL https://dom.spec.whatwg.org/#element @@ -459,6 +461,44 @@ pub const Element = struct { _ = opts; return true; } + + const AttachShadowOpts = struct { + mode: []const u8, // must be specified + }; + pub fn _attachShadow(self: *parser.Element, opts: AttachShadowOpts, page: *Page) !*ShadowRoot { + const mode = std.meta.stringToEnum(ShadowRoot.Mode, opts.mode) orelse return error.InvalidArgument; + const state = try page.getOrCreateNodeState(@alignCast(@ptrCast(self))); + if (state.shadow_root) |sr| { + if (mode != sr.mode) { + // this is the behavior per the spec + return error.NotSupportedError; + } + + // TODO: the existing shadow root should be cleared! + return sr; + } + + // Not sure what to do if there is no owner document + const doc = try parser.nodeOwnerDocument(@ptrCast(self)) orelse return error.InvalidArgument; + const fragment = try parser.documentCreateDocumentFragment(doc); + const sr = try page.arena.create(ShadowRoot); + sr.* = .{ + .host = self, + .mode = mode, + .proto = fragment, + }; + state.shadow_root = sr; + return sr; + } + + pub fn get_shadowRoot(self: *parser.Element, page: *Page) ?*ShadowRoot { + const state = page.getNodeState(@alignCast(@ptrCast(self))) orelse return null; + const sr = state.shadow_root orelse return null; + if (sr.mode == .closed) { + return null; + } + return sr; + } }; // Tests diff --git a/src/browser/dom/shadow_root.zig b/src/browser/dom/shadow_root.zig new file mode 100644 index 00000000..284e9f6d --- /dev/null +++ b/src/browser/dom/shadow_root.zig @@ -0,0 +1,66 @@ +// Copyright (C) 2023-2025 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 parser = @import("../netsurf.zig"); +const Element = @import("element.zig").Element; +const ElementUnion = @import("element.zig").Union; + +// WEB IDL https://dom.spec.whatwg.org/#interface-shadowroot +pub const ShadowRoot = struct { + pub const prototype = *parser.DocumentFragment; + pub const subtype = .node; + + mode: Mode, + host: *parser.Element, + proto: *parser.DocumentFragment, + + pub const Mode = enum { + open, + closed, + }; + + pub fn get_host(self: *const ShadowRoot) !ElementUnion { + return Element.toInterface(self.host); + } +}; + +const testing = @import("../../testing.zig"); +test "Browser.DOM.ShadowRoot" { + defer testing.reset(); + + var runner = try testing.jsRunner(testing.tracking_allocator, .{ .html = "" }); + defer runner.deinit(); + + try runner.testCases(&.{ + .{ "const div1 = document.createElement('div');", null }, + .{ "let sr1 = div1.attachShadow({mode: 'open'})", null }, + .{ "sr1.host == div1", "true" }, + .{ "div1.attachShadow({mode: 'open'}) == sr1", "true" }, + .{ "div1.shadowRoot == sr1", "true" }, + + .{ "try { div1.attachShadow({mode: 'closed'}) } catch (e) { e }", "Error: NotSupportedError" }, + }, .{}); + + try runner.testCases(&.{ + .{ "const div2 = document.createElement('di2');", null }, + .{ "let sr2 = div2.attachShadow({mode: 'closed'})", null }, + .{ "sr2.host == div2", "true" }, + .{ "div2.shadowRoot", "null" }, // null when attached with 'closed' + }, .{}); +} diff --git a/src/browser/env.zig b/src/browser/env.zig index 0222d780..bb05e2e3 100644 --- a/src/browser/env.zig +++ b/src/browser/env.zig @@ -25,6 +25,7 @@ const WebApis = struct { @import("css/css.zig").Interfaces, @import("cssom/cssom.zig").Interfaces, @import("dom/dom.zig").Interfaces, + @import("dom/shadow_root.zig").ShadowRoot, @import("encoding/text_encoder.zig").Interfaces, @import("events/event.zig").Interfaces, @import("html/html.zig").Interfaces, @@ -34,7 +35,6 @@ 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 a0f920d2..638208ce 100644 --- a/src/browser/html/elements.zig +++ b/src/browser/html/elements.zig @@ -114,10 +114,6 @@ 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; @@ -189,10 +185,6 @@ 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 @@ -202,10 +194,6 @@ 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 @@ -214,10 +202,6 @@ 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); } @@ -465,240 +449,144 @@ 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 { @@ -706,10 +594,6 @@ 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); } @@ -769,10 +653,6 @@ 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); } @@ -861,30 +741,18 @@ 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 { @@ -892,10 +760,6 @@ pub const HTMLLinkElement = 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_href(self: *parser.Link) ![]const u8 { return try parser.linkGetHref(self); } @@ -910,150 +774,90 @@ 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 @@ -1062,10 +866,6 @@ 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), @@ -1200,90 +1000,54 @@ 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 { @@ -1291,10 +1055,6 @@ pub const HTMLTemplateElement = 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_content(self: *parser.Template, page: *Page) !*parser.DocumentFragment { const state = try page.getOrCreateNodeState(@alignCast(@ptrCast(self))); if (state.template_content) |tc| { @@ -1310,60 +1070,36 @@ 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 { @@ -1441,16 +1177,6 @@ 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 78f57fd1..487dba00 100644 --- a/src/browser/html/window.zig +++ b/src/browser/html/window.zig @@ -33,7 +33,6 @@ const EventTarget = @import("../dom/event_target.zig").EventTarget; const MediaQueryList = @import("media_query_list.zig").MediaQueryList; const Performance = @import("../dom/performance.zig").Performance; const CSSStyleDeclaration = @import("../cssom/css_style_declaration.zig").CSSStyleDeclaration; -const CustomElementRegistry = @import("../webcomponents/custom_element_registry.zig").CustomElementRegistry; const Screen = @import("screen.zig").Screen; const Css = @import("../css/css.zig").Css; @@ -61,7 +60,6 @@ pub const Window = struct { console: Console = .{}, navigator: Navigator = .{}, performance: Performance, - custom_elements: CustomElementRegistry = .{}, screen: Screen = .{}, css: Css = .{}, @@ -169,10 +167,6 @@ pub const Window = struct { return &self.performance; } - pub fn get_customElements(self: *Window) *CustomElementRegistry { - return &self.custom_elements; - } - pub fn get_screen(self: *Window) *Screen { return &self.screen; } diff --git a/src/browser/page.zig b/src/browser/page.zig index 66b4e706..84b08b59 100644 --- a/src/browser/page.zig +++ b/src/browser/page.zig @@ -366,18 +366,6 @@ pub const Page = struct { 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/polyfill/fetch.zig b/src/browser/polyfill/fetch.zig index 04bfa65c..7b98534e 100644 --- a/src/browser/polyfill/fetch.zig +++ b/src/browser/polyfill/fetch.zig @@ -16,8 +16,6 @@ test "Browser.fetch" { var runner = try testing.jsRunner(testing.tracking_allocator, .{}); defer runner.deinit(); - try @import("polyfill.zig").Loader.load("fetch", source, runner.page.main_context); - try runner.testCases(&.{ .{ \\ var ok = false; diff --git a/src/browser/polyfill/polyfill.zig b/src/browser/polyfill/polyfill.zig index 65721958..899eaa89 100644 --- a/src/browser/polyfill/polyfill.zig +++ b/src/browser/polyfill/polyfill.zig @@ -28,19 +28,26 @@ pub const Loader = struct { done: struct { fetch: bool = false, + webcomponents: bool = false, } = .{}, - pub fn load(name: []const u8, source: []const u8, js_context: *Env.JsContext) !void { + fn load(self: *Loader, comptime name: []const u8, source: []const u8, js_context: *Env.JsContext) void { var try_catch: Env.TryCatch = undefined; try_catch.init(js_context); defer try_catch.deinit(); + self.state = .loading; + defer self.state = .empty; + + log.debug(.js, "polyfill load", .{ .name = name }); _ = js_context.exec(source, name) catch |err| { - if (try try_catch.err(js_context.call_arena)) |msg| { - log.fatal(.app, "polyfill error", .{ .name = name, .err = msg }); - } - return err; + log.fatal(.app, "polyfill error", .{ + .name = name, + .err = try_catch.err(js_context.call_arena) catch @errorName(err) orelse @errorName(err), + }); }; + + @field(self.done, name) = true; } pub fn missing(self: *Loader, name: []const u8, js_context: *Env.JsContext) bool { @@ -50,18 +57,20 @@ pub const Loader = struct { } if (!self.done.fetch and isFetch(name)) { - self.state = .loading; - defer self.state = .empty; - - const _name = "fetch"; const source = @import("fetch.zig").source; - log.debug(.polyfill, "dynamic load", .{ .property = name }); - load(_name, source, js_context) catch |err| { - log.fatal(.app, "polyfill load", .{ .name = name, .err = err }); - }; + self.load("fetch", source, js_context); - // load the polyfill once. - self.done.fetch = true; + // We return false here: We want v8 to continue the calling chain + // to finally find the polyfill we just inserted. If we want to + // return false and stops the call chain, we have to use + // `info.GetReturnValue.Set()` function, or `undefined` will be + // returned immediately. + return false; + } + + if (!self.done.webcomponents and isWebcomponents(name)) { + const source = @import("webcomponents.zig").source; + self.load("webcomponents", source, js_context); // We return false here: We want v8 to continue the calling chain // to finally find the polyfill we just inserted. If we want to @@ -88,4 +97,9 @@ pub const Loader = struct { if (std.mem.eql(u8, name, "Headers")) return true; return false; } + + fn isWebcomponents(name: []const u8) bool { + if (std.mem.eql(u8, name, "customElements")) return true; + return false; + } }; diff --git a/src/browser/polyfill/webcomponents.js b/src/browser/polyfill/webcomponents.js new file mode 100644 index 00000000..66e586ee --- /dev/null +++ b/src/browser/polyfill/webcomponents.js @@ -0,0 +1,61 @@ +/** +@license @nocompile +Copyright (c) 2018 The Polymer Project Authors. All rights reserved. +This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt +The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt +The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt +Code distributed by Google as part of the polymer project is also +subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt +*/ +(function(){/* + + Copyright (c) 2016 The Polymer Project Authors. All rights reserved. + This code may only be used under the BSD style license found at + http://polymer.github.io/LICENSE.txt The complete set of authors may be found + at http://polymer.github.io/AUTHORS.txt The complete set of contributors may + be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by + Google as part of the polymer project is also subject to an additional IP + rights grant found at http://polymer.github.io/PATENTS.txt +*/ +'use strict';var n=window.Document.prototype.createElement,p=window.Document.prototype.createElementNS,aa=window.Document.prototype.importNode,ba=window.Document.prototype.prepend,ca=window.Document.prototype.append,da=window.DocumentFragment.prototype.prepend,ea=window.DocumentFragment.prototype.append,q=window.Node.prototype.cloneNode,r=window.Node.prototype.appendChild,t=window.Node.prototype.insertBefore,u=window.Node.prototype.removeChild,v=window.Node.prototype.replaceChild,w=Object.getOwnPropertyDescriptor(window.Node.prototype, +"textContent"),y=window.Element.prototype.attachShadow,z=Object.getOwnPropertyDescriptor(window.Element.prototype,"innerHTML"),A=window.Element.prototype.getAttribute,B=window.Element.prototype.setAttribute,C=window.Element.prototype.removeAttribute,D=window.Element.prototype.toggleAttribute,E=window.Element.prototype.getAttributeNS,F=window.Element.prototype.setAttributeNS,G=window.Element.prototype.removeAttributeNS,H=window.Element.prototype.insertAdjacentElement,fa=window.Element.prototype.insertAdjacentHTML, +ha=window.Element.prototype.prepend,ia=window.Element.prototype.append,ja=window.Element.prototype.before,ka=window.Element.prototype.after,la=window.Element.prototype.replaceWith,ma=window.Element.prototype.remove,na=window.HTMLElement,I=Object.getOwnPropertyDescriptor(window.HTMLElement.prototype,"innerHTML"),oa=window.HTMLElement.prototype.insertAdjacentElement,pa=window.HTMLElement.prototype.insertAdjacentHTML;var qa=new Set;"annotation-xml color-profile font-face font-face-src font-face-uri font-face-format font-face-name missing-glyph".split(" ").forEach(function(a){return qa.add(a)});function ra(a){var b=qa.has(a);a=/^[a-z][.0-9_a-z]*-[-.0-9_a-z]*$/.test(a);return!b&&a}var sa=document.contains?document.contains.bind(document):document.documentElement.contains.bind(document.documentElement); +function J(a){var b=a.isConnected;if(void 0!==b)return b;if(sa(a))return!0;for(;a&&!(a.__CE_isImportDocument||a instanceof Document);)a=a.parentNode||(window.ShadowRoot&&a instanceof ShadowRoot?a.host:void 0);return!(!a||!(a.__CE_isImportDocument||a instanceof Document))}function K(a){var b=a.children;if(b)return Array.prototype.slice.call(b);b=[];for(a=a.firstChild;a;a=a.nextSibling)a.nodeType===Node.ELEMENT_NODE&&b.push(a);return b} +function L(a,b){for(;b&&b!==a&&!b.nextSibling;)b=b.parentNode;return b&&b!==a?b.nextSibling:null} +function M(a,b,d){for(var f=a;f;){if(f.nodeType===Node.ELEMENT_NODE){var c=f;b(c);var e=c.localName;if("link"===e&&"import"===c.getAttribute("rel")){f=c.import;void 0===d&&(d=new Set);if(f instanceof Node&&!d.has(f))for(d.add(f),f=f.firstChild;f;f=f.nextSibling)M(f,b,d);f=L(a,c);continue}else if("template"===e){f=L(a,c);continue}if(c=c.__CE_shadowRoot)for(c=c.firstChild;c;c=c.nextSibling)M(c,b,d)}f=f.firstChild?f.firstChild:L(a,f)}};function N(){var a=!(null===O||void 0===O||!O.noDocumentConstructionObserver),b=!(null===O||void 0===O||!O.shadyDomFastWalk);this.m=[];this.g=[];this.j=!1;this.shadyDomFastWalk=b;this.I=!a}function P(a,b,d,f){var c=window.ShadyDOM;if(a.shadyDomFastWalk&&c&&c.inUse){if(b.nodeType===Node.ELEMENT_NODE&&d(b),b.querySelectorAll)for(a=c.nativeMethods.querySelectorAll.call(b,"*"),b=0;bconnected" }, + }, .{}); +} diff --git a/src/browser/webcomponents/custom_element_registry.zig b/src/browser/webcomponents/custom_element_registry.zig index 3e429a67..4666a1ad 100644 --- a/src/browser/webcomponents/custom_element_registry.zig +++ b/src/browser/webcomponents/custom_element_registry.zig @@ -16,6 +16,8 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +// Currently not used. Relying on polyfill instead + const std = @import("std"); const log = @import("../../log.zig"); const v8 = @import("v8"); diff --git a/src/browser/webcomponents/webcomponents.zig b/src/browser/webcomponents/webcomponents.zig deleted file mode 100644 index 2ba2a30f..00000000 --- a/src/browser/webcomponents/webcomponents.zig +++ /dev/null @@ -1,23 +0,0 @@ -// 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, -};