diff --git a/src/browser/css/parser.zig b/src/browser/css/parser.zig
index 70fe20a07..4bcf7f9f1 100644
--- a/src/browser/css/parser.zig
+++ b/src/browser/css/parser.zig
@@ -605,6 +605,7 @@ pub const Parser = struct {
.after, .backdrop, .before, .cue, .first_letter => return .{ .pseudo_element = pseudo_class },
.first_line, .grammar_error, .marker, .placeholder => return .{ .pseudo_element = pseudo_class },
.selection, .spelling_error => return .{ .pseudo_element = pseudo_class },
+ .modal => return .{ .pseudo_element = pseudo_class },
}
}
diff --git a/src/browser/css/selector.zig b/src/browser/css/selector.zig
index a3e372468..301af641d 100644
--- a/src/browser/css/selector.zig
+++ b/src/browser/css/selector.zig
@@ -98,6 +98,7 @@ pub const PseudoClass = enum {
placeholder,
selection,
spelling_error,
+ modal,
pub const Error = error{
InvalidPseudoClass,
@@ -154,6 +155,7 @@ pub const PseudoClass = enum {
if (std.ascii.eqlIgnoreCase(s, "placeholder")) return .placeholder;
if (std.ascii.eqlIgnoreCase(s, "selection")) return .selection;
if (std.ascii.eqlIgnoreCase(s, "spelling-error")) return .spelling_error;
+ if (std.ascii.eqlIgnoreCase(s, "modal")) return .modal;
return Error.InvalidPseudoClass;
}
};
diff --git a/src/browser/cssom/css_style_declaration.zig b/src/browser/cssom/css_style_declaration.zig
index 1e980fb24..98a9015d5 100644
--- a/src/browser/cssom/css_style_declaration.zig
+++ b/src/browser/cssom/css_style_declaration.zig
@@ -85,7 +85,7 @@ pub const CSSStyleDeclaration = struct {
return self.order.items.len;
}
- pub fn get_parentRule() ?CSSRule {
+ pub fn get_parentRule(_: *const CSSStyleDeclaration) ?CSSRule {
return null;
}
diff --git a/src/browser/dom/document.zig b/src/browser/dom/document.zig
index bc8dccbce..fe4a499ac 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 7d6627ec3..806391e5e 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 3ce70361d..948810a2a 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 02381971c..74fa28b3d 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 });
}
@@ -337,20 +343,15 @@ test "Browser.HTML.Window" {
// Note however that we in this test do not wait as the request is just send to the browser
try runner.testCases(&.{
.{
- \\ let start;
+ \\ let start = 0;
\\ function step(timestamp) {
- \\ if (start === undefined) {
- \\ start = timestamp;
- \\ }
- \\ const elapsed = timestamp - start;
- \\ if (elapsed < 2000) {
- \\ requestAnimationFrame(step);
- \\ }
+ \\ start = timestamp;
\\ }
,
null,
},
.{ "requestAnimationFrame(step);", null }, // returned id is checked in the next test
+ .{ " start > 0", "true" },
}, .{});
// cancelAnimationFrame should be able to cancel a request with the given id
diff --git a/src/browser/page.zig b/src/browser/page.zig
index 396022dba..368a3c5a7 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 000000000..3e429a67c
--- /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 000000000..2ba2a30fc
--- /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/browser/xhr/form_data.zig b/src/browser/xhr/form_data.zig
index 485ef6479..48e03ad37 100644
--- a/src/browser/xhr/form_data.zig
+++ b/src/browser/xhr/form_data.zig
@@ -115,17 +115,24 @@ const EntryIterable = iterator.Iterable(kv.EntryIterator, "FormDataEntryIterator
// TODO: handle disabled fieldsets
fn collectForm(form: *parser.Form, submitter_: ?*parser.ElementHTML, page: *Page) !kv.List {
const arena = page.arena;
- const collection = try parser.formGetCollection(form);
- const len = try parser.htmlCollectionGetLength(collection);
+
+ // Don't use libdom's formGetCollection (aka dom_html_form_element_get_elements)
+ // It doesn't work with dynamically added elements, because their form
+ // property doesn't get set. We should fix that.
+ // However, even once fixed, there are other form-collection features we
+ // probably want to implement (like disabled fieldsets), so we might want
+ // to stick with our own walker even if fix libdom to properly support
+ // dynamically added elements.
+ const node_list = try @import("../dom/css.zig").querySelectorAll(arena, @alignCast(@ptrCast(form)), "input,select,button,textarea");
+ const nodes = node_list.nodes.items;
var entries: kv.List = .{};
- try entries.ensureTotalCapacity(arena, len);
+ try entries.ensureTotalCapacity(arena, nodes.len);
var submitter_included = false;
const submitter_name_ = try getSubmitterName(submitter_);
- for (0..len) |i| {
- const node = try parser.htmlCollectionItem(collection, @intCast(i));
+ for (nodes) |node| {
const element = parser.nodeToElement(node);
// must have a name
@@ -181,10 +188,7 @@ fn collectForm(form: *parser.Form, submitter_: ?*parser.ElementHTML, page: *Page
submitter_included = true;
}
},
- else => {
- log.warn(.web_api, "unsupported form element", .{ .tag = @tagName(tag) });
- continue;
- },
+ else => unreachable,
}
}
@@ -297,6 +301,7 @@ test "Browser.FormData" {
\\
\\
\\
+ \\
});
defer runner.deinit();
@@ -356,6 +361,8 @@ test "Browser.FormData" {
try runner.testCases(&.{
.{ "let form1 = document.getElementById('form1')", null },
+ .{ "let input = document.createElement('input');", null },
+ .{ "input.name = 'dyn'; input.value= 'dyn-v'; form1.appendChild(input);", null },
.{ "let submit1 = document.getElementById('s1')", null },
.{ "let f2 = new FormData(form1, submit1)", null },
.{ "acc = '';", null },
@@ -378,6 +385,7 @@ test "Browser.FormData" {
\\mlt-2=water
\\mlt-2=tea
\\s1=s1-v
+ \\dyn=dyn-v
},
}, .{});
}
diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig
index 3b0005f4a..30b05499d 100644
--- a/src/cdp/cdp.zig
+++ b/src/cdp/cdp.zig
@@ -23,7 +23,6 @@ const json = std.json;
const log = @import("../log.zig");
const App = @import("../app.zig").App;
const Env = @import("../browser/env.zig").Env;
-const asUint = @import("../str/parser.zig").asUint;
const Browser = @import("../browser/browser.zig").Browser;
const Session = @import("../browser/session.zig").Session;
const Page = @import("../browser/page.zig").Page;
@@ -182,41 +181,41 @@ pub fn CDPT(comptime TypeProvider: type) type {
switch (domain.len) {
3 => switch (@as(u24, @bitCast(domain[0..3].*))) {
- asUint("DOM") => return @import("domains/dom.zig").processMessage(command),
- asUint("Log") => return @import("domains/log.zig").processMessage(command),
- asUint("CSS") => return @import("domains/css.zig").processMessage(command),
+ asUint(u24, "DOM") => return @import("domains/dom.zig").processMessage(command),
+ asUint(u24, "Log") => return @import("domains/log.zig").processMessage(command),
+ asUint(u24, "CSS") => return @import("domains/css.zig").processMessage(command),
else => {},
},
4 => switch (@as(u32, @bitCast(domain[0..4].*))) {
- asUint("Page") => return @import("domains/page.zig").processMessage(command),
+ asUint(u32, "Page") => return @import("domains/page.zig").processMessage(command),
else => {},
},
5 => switch (@as(u40, @bitCast(domain[0..5].*))) {
- asUint("Fetch") => return @import("domains/fetch.zig").processMessage(command),
- asUint("Input") => return @import("domains/input.zig").processMessage(command),
+ asUint(u40, "Fetch") => return @import("domains/fetch.zig").processMessage(command),
+ asUint(u40, "Input") => return @import("domains/input.zig").processMessage(command),
else => {},
},
6 => switch (@as(u48, @bitCast(domain[0..6].*))) {
- asUint("Target") => return @import("domains/target.zig").processMessage(command),
+ asUint(u48, "Target") => return @import("domains/target.zig").processMessage(command),
else => {},
},
7 => switch (@as(u56, @bitCast(domain[0..7].*))) {
- asUint("Browser") => return @import("domains/browser.zig").processMessage(command),
- asUint("Runtime") => return @import("domains/runtime.zig").processMessage(command),
- asUint("Network") => return @import("domains/network.zig").processMessage(command),
+ asUint(u56, "Browser") => return @import("domains/browser.zig").processMessage(command),
+ asUint(u56, "Runtime") => return @import("domains/runtime.zig").processMessage(command),
+ asUint(u56, "Network") => return @import("domains/network.zig").processMessage(command),
else => {},
},
8 => switch (@as(u64, @bitCast(domain[0..8].*))) {
- asUint("Security") => return @import("domains/security.zig").processMessage(command),
+ asUint(u64, "Security") => return @import("domains/security.zig").processMessage(command),
else => {},
},
9 => switch (@as(u72, @bitCast(domain[0..9].*))) {
- asUint("Emulation") => return @import("domains/emulation.zig").processMessage(command),
- asUint("Inspector") => return @import("domains/inspector.zig").processMessage(command),
+ asUint(u72, "Emulation") => return @import("domains/emulation.zig").processMessage(command),
+ asUint(u72, "Inspector") => return @import("domains/inspector.zig").processMessage(command),
else => {},
},
11 => switch (@as(u88, @bitCast(domain[0..11].*))) {
- asUint("Performance") => return @import("domains/performance.zig").processMessage(command),
+ asUint(u88, "Performance") => return @import("domains/performance.zig").processMessage(command),
else => {},
},
else => {},
@@ -696,6 +695,10 @@ const InputParams = struct {
}
};
+fn asUint(comptime T: type, comptime string: []const u8) T {
+ return @bitCast(string[0..string.len].*);
+}
+
const testing = @import("testing.zig");
test "cdp: invalid json" {
var ctx = testing.context();
diff --git a/src/http/client.zig b/src/http/client.zig
index c6cf71c02..385e8e0ad 100644
--- a/src/http/client.zig
+++ b/src/http/client.zig
@@ -3170,7 +3170,7 @@ test "HttpClient: async tls no body" {
}
}
-test "HttpClient: async tls with body x" {
+test "HttpClient: async tls with body" {
defer testing.reset();
for (0..5) |_| {
var client = try testClient();
diff --git a/src/log.zig b/src/log.zig
index 93df4c481..e00621ee7 100644
--- a/src/log.zig
+++ b/src/log.zig
@@ -146,6 +146,16 @@ fn logTo(comptime scope: Scope, level: Level, comptime msg: []const u8, data: an
}
fn logLogfmt(comptime scope: Scope, level: Level, comptime msg: []const u8, data: anytype, writer: anytype) !void {
+ try logLogFmtPrefix(scope, level, msg, writer);
+ inline for (@typeInfo(@TypeOf(data)).@"struct".fields) |f| {
+ const key = " " ++ f.name ++ "=";
+ try writer.writeAll(key);
+ try writeValue(.logfmt, @field(data, f.name), writer);
+ }
+ try writer.writeByte('\n');
+}
+
+fn logLogFmtPrefix(comptime scope: Scope, level: Level, comptime msg: []const u8, writer: anytype) !void {
try writer.writeAll("$time=");
try writer.print("{d}", .{timestamp()});
@@ -164,15 +174,20 @@ fn logLogfmt(comptime scope: Scope, level: Level, comptime msg: []const u8, data
break :blk prefix ++ "\"" ++ msg ++ "\"";
};
try writer.writeAll(full_msg);
+}
+
+fn logPretty(comptime scope: Scope, level: Level, comptime msg: []const u8, data: anytype, writer: anytype) !void {
+ try logPrettyPrefix(scope, level, msg, writer);
inline for (@typeInfo(@TypeOf(data)).@"struct".fields) |f| {
- const key = " " ++ f.name ++ "=";
+ const key = " " ++ f.name ++ " = ";
try writer.writeAll(key);
- try writeValue(.logfmt, @field(data, f.name), writer);
+ try writeValue(.pretty, @field(data, f.name), writer);
+ try writer.writeByte('\n');
}
try writer.writeByte('\n');
}
-fn logPretty(comptime scope: Scope, level: Level, comptime msg: []const u8, data: anytype, writer: anytype) !void {
+fn logPrettyPrefix(comptime scope: Scope, level: Level, comptime msg: []const u8, writer: anytype) !void {
if (scope == .console and level == .fatal and comptime std.mem.eql(u8, msg, "lightpanda")) {
try writer.writeAll("\x1b[0;104mWARN ");
} else {
@@ -201,14 +216,6 @@ fn logPretty(comptime scope: Scope, level: Level, comptime msg: []const u8, data
try writer.print(" \x1b[0m[+{d}ms]", .{elapsed()});
try writer.writeByte('\n');
}
-
- inline for (@typeInfo(@TypeOf(data)).@"struct".fields) |f| {
- const key = " " ++ f.name ++ " = ";
- try writer.writeAll(key);
- try writeValue(.pretty, @field(data, f.name), writer);
- try writer.writeByte('\n');
- }
- try writer.writeByte('\n');
}
pub fn writeValue(comptime format: Format, value: anytype, writer: anytype) !void {
diff --git a/src/runtime/js.zig b/src/runtime/js.zig
index 86619c6a4..083b86e3b 100644
--- a/src/runtime/js.zig
+++ b/src/runtime/js.zig
@@ -1158,7 +1158,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
}
if (!js_value.isArray()) {
- return error.InvalidArgument;
+ return .{.invalid = {}};
}
// This can get tricky.
@@ -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;
}
@@ -1876,7 +1922,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
defer caller.deinit();
const named_function = comptime NamedFunction.init(Struct, "get_" ++ name);
- caller.getter(Struct, named_function, info) catch |err| {
+ caller.method(Struct, named_function, info) catch |err| {
caller.handleError(Struct, named_function, err, info);
};
}
@@ -1891,13 +1937,13 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
const setter_callback = v8.FunctionTemplate.initCallback(isolate, struct {
fn callback(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
+ std.debug.assert(info.length() == 1);
+
var caller = Caller(Self, State).init(info);
defer caller.deinit();
- std.debug.assert(info.length() == 1);
- const js_value = info.getArg(0);
const named_function = comptime NamedFunction.init(Struct, "set_" ++ name);
- caller.setter(Struct, named_function, js_value, info) catch |err| {
+ caller.method(Struct, named_function, info) catch |err| {
caller.handleError(Struct, named_function, err, info);
};
}
@@ -2424,66 +2470,6 @@ fn Caller(comptime E: type, comptime State: type) type {
info.getReturnValue().set(try js_context.zigValueToJs(res));
}
- fn getter(self: *Self, comptime Struct: type, comptime named_function: NamedFunction, info: v8.FunctionCallbackInfo) !void {
- const js_context = self.js_context;
- const func = @field(Struct, named_function.name);
- const Getter = @TypeOf(func);
- if (@typeInfo(Getter).@"fn".return_type == null) {
- @compileError(@typeName(Struct) ++ " has a getter without a return type: " ++ @typeName(Getter));
- }
-
- var args: ParamterTypes(Getter) = undefined;
- const arg_fields = @typeInfo(@TypeOf(args)).@"struct".fields;
- switch (arg_fields.len) {
- 0 => {}, // getters _can_ be parameterless
- 1, 2 => {
- const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(Struct), info.getThis());
- comptime assertSelfReceiver(Struct, named_function);
- @field(args, "0") = zig_instance;
- if (comptime arg_fields.len == 2) {
- comptime assertIsStateArg(Struct, named_function, 1);
- @field(args, "1") = js_context.state;
- }
- },
- else => @compileError(named_function.full_name + " has too many parmaters: " ++ @typeName(named_function.func)),
- }
- const res = @call(.auto, func, args);
- info.getReturnValue().set(try js_context.zigValueToJs(res));
- }
-
- fn setter(self: *Self, comptime Struct: type, comptime named_function: NamedFunction, js_value: v8.Value, info: v8.FunctionCallbackInfo) !void {
- const js_context = self.js_context;
- const func = @field(Struct, named_function.name);
- comptime assertSelfReceiver(Struct, named_function);
-
- const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(Struct), info.getThis());
-
- const Setter = @TypeOf(func);
- var args: ParamterTypes(Setter) = undefined;
- const arg_fields = @typeInfo(@TypeOf(args)).@"struct".fields;
- switch (arg_fields.len) {
- 0 => unreachable, // assertSelfReceiver make sure of this
- 1 => @compileError(named_function.full_name ++ " only has 1 parameter"),
- 2, 3 => {
- @field(args, "0") = zig_instance;
- @field(args, "1") = try js_context.jsValueToZig(named_function, arg_fields[1].type, js_value);
- if (comptime arg_fields.len == 3) {
- comptime assertIsStateArg(Struct, named_function, 2);
- @field(args, "2") = js_context.state;
- }
- },
- else => @compileError(named_function.full_name ++ " setter with more than 3 parameters, why?"),
- }
-
- if (@typeInfo(Setter).@"fn".return_type) |return_type| {
- if (@typeInfo(return_type) == .error_union) {
- _ = try @call(.auto, func, args);
- return;
- }
- }
- _ = @call(.auto, func, args);
- }
-
fn getIndex(self: *Self, comptime Struct: type, comptime named_function: NamedFunction, idx: u32, info: v8.PropertyCallbackInfo) !u8 {
const js_context = self.js_context;
const func = @field(Struct, named_function.name);
@@ -2596,19 +2582,14 @@ fn Caller(comptime E: type, comptime State: type) type {
if (comptime builtin.mode == .Debug and @hasDecl(@TypeOf(info), "length")) {
if (log.enabled(.js, .warn)) {
- const args_dump = self.serializeFunctionArgs(info) catch "failed to serialize args";
- log.warn(.js, "function call error", .{
- .name = named_function.full_name,
- .err = err,
- .args = args_dump,
- .stack = stackForLogs(self.call_arena, isolate) catch |err1| @errorName(err1),
- });
+ logFunctionCallError(self.call_arena, self.isolate, self.v8_context, err, named_function.full_name, info);
}
}
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 {
@@ -2670,6 +2651,7 @@ fn Caller(comptime E: type, comptime State: type) type {
// Does the error we want to return belong to the custom exeception's ErrorSet
fn isErrorSetException(comptime Exception: type, err: anytype) bool {
const Entry = std.meta.Tuple(&.{ []const u8, void });
+
const error_set = @typeInfo(Exception.ErrorSet).error_set.?;
const entries = comptime blk: {
var kv: [error_set.len]Entry = undefined;
@@ -2721,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
@@ -2808,28 +2790,6 @@ fn Caller(comptime E: type, comptime State: type) type {
const Const_State = if (ti == .pointer) *const ti.pointer.child else State;
return T == State or T == Const_State;
}
-
- fn serializeFunctionArgs(self: *const Self, info: anytype) ![]const u8 {
- const isolate = self.isolate;
- const v8_context = self.v8_context;
- const arena = self.call_arena;
- const separator = log.separator();
- const js_parameter_count = info.length();
-
- var arr: std.ArrayListUnmanaged(u8) = .{};
- for (0..js_parameter_count) |i| {
- const js_value = info.getArg(@intCast(i));
- const value_string = try valueToDetailString(arena, js_value, isolate, v8_context);
- const value_type = try jsStringToZig(arena, try js_value.typeOf(isolate), isolate);
- try std.fmt.format(arr.writer(arena), "{s}{d}: {s} ({s})", .{
- separator,
- i + 1,
- value_string,
- value_type,
- });
- }
- return arr.items;
- }
};
}
@@ -3270,6 +3230,37 @@ const NamedFunction = struct {
}
};
+// This is extracted to speed up compilation. When left inlined in handleError,
+// this can add as much as 10 seconds of compilation time.
+fn logFunctionCallError(arena: Allocator, isolate: v8.Isolate, context: v8.Context, err: anyerror, function_name: []const u8, info: v8.FunctionCallbackInfo) void {
+ const args_dump = serializeFunctionArgs(arena, isolate, context, info) catch "failed to serialize args";
+ log.warn(.js, "function call error", .{
+ .name = function_name,
+ .err = err,
+ .args = args_dump,
+ .stack = stackForLogs(arena, isolate) catch |err1| @errorName(err1),
+ });
+}
+
+fn serializeFunctionArgs(arena: Allocator, isolate: v8.Isolate, context: v8.Context, info: v8.FunctionCallbackInfo) ![]const u8 {
+ const separator = log.separator();
+ const js_parameter_count = info.length();
+
+ var arr: std.ArrayListUnmanaged(u8) = .{};
+ for (0..js_parameter_count) |i| {
+ const js_value = info.getArg(@intCast(i));
+ const value_string = try valueToDetailString(arena, js_value, isolate, context);
+ const value_type = try jsStringToZig(arena, try js_value.typeOf(isolate), isolate);
+ try std.fmt.format(arr.writer(arena), "{s}{d}: {s} ({s})", .{
+ separator,
+ i + 1,
+ value_string,
+ value_type,
+ });
+ }
+ return arr.items;
+}
+
// This is called from V8. Whenever the v8 inspector has to describe a value
// it'll call this function to gets its [optional] subtype - which, from V8's
// point of view, is an arbitrary string.
diff --git a/src/str/parser.zig b/src/str/parser.zig
deleted file mode 100644
index b2429f818..000000000
--- a/src/str/parser.zig
+++ /dev/null
@@ -1,125 +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 .
-
-// some utils to parser strings.
-const std = @import("std");
-
-pub const Reader = struct {
- pos: usize = 0,
- data: []const u8,
-
- pub fn until(self: *Reader, c: u8) []const u8 {
- const pos = self.pos;
- const data = self.data;
-
- const index = std.mem.indexOfScalarPos(u8, data, pos, c) orelse data.len;
- self.pos = index;
- return data[pos..index];
- }
-
- pub fn tail(self: *Reader) []const u8 {
- const pos = self.pos;
- const data = self.data;
- if (pos > data.len) {
- return "";
- }
- self.pos = data.len;
- return data[pos..];
- }
-
- pub fn skip(self: *Reader) bool {
- const pos = self.pos;
- if (pos >= self.data.len) {
- return false;
- }
- self.pos = pos + 1;
- return true;
- }
-};
-
-// converts a comptime-known string (i.e. null terminated) to an uint
-pub fn asUint(comptime string: anytype) AsUintReturn(string) {
- const byteLength = @bitSizeOf(@TypeOf(string.*)) / 8 - 1;
- const expectedType = *const [byteLength:0]u8;
- if (@TypeOf(string) != expectedType) {
- @compileError("expected : " ++ @typeName(expectedType) ++
- ", got: " ++ @typeName(@TypeOf(string)));
- }
-
- return @bitCast(@as(*const [byteLength]u8, string).*);
-}
-
-fn AsUintReturn(comptime string: anytype) type {
- return @Type(.{
- .int = .{
- .bits = @bitSizeOf(@TypeOf(string.*)) - 8, // (- 8) to exclude sentinel 0
- .signedness = .unsigned,
- },
- });
-}
-
-const testing = std.testing;
-test "parser.Reader: skip" {
- var r = Reader{ .data = "foo" };
- try testing.expectEqual(true, r.skip());
- try testing.expectEqual(true, r.skip());
- try testing.expectEqual(true, r.skip());
- try testing.expectEqual(false, r.skip());
- try testing.expectEqual(false, r.skip());
-}
-
-test "parser.Reader: tail" {
- var r = Reader{ .data = "foo" };
- try testing.expectEqualStrings("foo", r.tail());
- try testing.expectEqualStrings("", r.tail());
- try testing.expectEqualStrings("", r.tail());
-}
-
-test "parser.Reader: until" {
- var r = Reader{ .data = "foo.bar.baz" };
- try testing.expectEqualStrings("foo", r.until('.'));
- _ = r.skip();
- try testing.expectEqualStrings("bar", r.until('.'));
- _ = r.skip();
- try testing.expectEqualStrings("baz", r.until('.'));
-
- r = Reader{ .data = "foo" };
- try testing.expectEqualStrings("foo", r.until('.'));
- try testing.expectEqualStrings("", r.tail());
-
- r = Reader{ .data = "" };
- try testing.expectEqualStrings("", r.until('.'));
- try testing.expectEqualStrings("", r.tail());
-}
-
-test "parser: asUint" {
- const ASCII_x = @as(u8, @bitCast([1]u8{'x'}));
- const ASCII_ab = @as(u16, @bitCast([2]u8{ 'a', 'b' }));
- const ASCII_xyz = @as(u24, @bitCast([3]u8{ 'x', 'y', 'z' }));
- const ASCII_abcd = @as(u32, @bitCast([4]u8{ 'a', 'b', 'c', 'd' }));
-
- try testing.expectEqual(ASCII_x, asUint("x"));
- try testing.expectEqual(ASCII_ab, asUint("ab"));
- try testing.expectEqual(ASCII_xyz, asUint("xyz"));
- try testing.expectEqual(ASCII_abcd, asUint("abcd"));
-
- try testing.expectEqual(u8, @TypeOf(asUint("x")));
- try testing.expectEqual(u16, @TypeOf(asUint("ab")));
- try testing.expectEqual(u24, @TypeOf(asUint("xyz")));
- try testing.expectEqual(u32, @TypeOf(asUint("abcd")));
-}