Compare commits

..

3 Commits

Author SHA1 Message Date
sjorsdonkers
6ec0d0b84c HtmlInputElement as Zig native 2025-06-16 13:29:28 +02:00
sjorsdonkers
3544e98871 create_element_external WIP 2025-06-16 13:29:27 +02:00
sjorsdonkers
446e5b2ddd basic idea 2025-06-16 13:29:27 +02:00
26 changed files with 434 additions and 691 deletions

View File

@@ -43,6 +43,7 @@ const Matcher = struct {
} }
}; };
const Elements = @import("../html/elements.zig");
test "matchFirst" { test "matchFirst" {
const alloc = std.testing.allocator; const alloc = std.testing.allocator;
@@ -161,7 +162,7 @@ test "matchFirst" {
for (testcases) |tc| { for (testcases) |tc| {
matcher.reset(); matcher.reset();
const doc = try parser.documentHTMLParseFromStr(tc.html); const doc = try parser.documentHTMLParseFromStr(tc.html, &Elements.createElement);
defer parser.documentHTMLClose(doc) catch {}; defer parser.documentHTMLClose(doc) catch {};
const s = css.parse(alloc, tc.q, .{}) catch |e| { const s = css.parse(alloc, tc.q, .{}) catch |e| {
@@ -302,7 +303,7 @@ test "matchAll" {
for (testcases) |tc| { for (testcases) |tc| {
matcher.reset(); matcher.reset();
const doc = try parser.documentHTMLParseFromStr(tc.html); const doc = try parser.documentHTMLParseFromStr(tc.html, &Elements.createElement);
defer parser.documentHTMLClose(doc) catch {}; defer parser.documentHTMLClose(doc) catch {};
const s = css.parse(alloc, tc.q, .{}) catch |e| { const s = css.parse(alloc, tc.q, .{}) catch |e| {

View File

@@ -605,7 +605,6 @@ pub const Parser = struct {
.after, .backdrop, .before, .cue, .first_letter => return .{ .pseudo_element = pseudo_class }, .after, .backdrop, .before, .cue, .first_letter => return .{ .pseudo_element = pseudo_class },
.first_line, .grammar_error, .marker, .placeholder => return .{ .pseudo_element = pseudo_class }, .first_line, .grammar_error, .marker, .placeholder => return .{ .pseudo_element = pseudo_class },
.selection, .spelling_error => return .{ .pseudo_element = pseudo_class }, .selection, .spelling_error => return .{ .pseudo_element = pseudo_class },
.modal => return .{ .pseudo_element = pseudo_class },
} }
} }

View File

@@ -98,7 +98,6 @@ pub const PseudoClass = enum {
placeholder, placeholder,
selection, selection,
spelling_error, spelling_error,
modal,
pub const Error = error{ pub const Error = error{
InvalidPseudoClass, InvalidPseudoClass,
@@ -155,7 +154,6 @@ pub const PseudoClass = enum {
if (std.ascii.eqlIgnoreCase(s, "placeholder")) return .placeholder; if (std.ascii.eqlIgnoreCase(s, "placeholder")) return .placeholder;
if (std.ascii.eqlIgnoreCase(s, "selection")) return .selection; if (std.ascii.eqlIgnoreCase(s, "selection")) return .selection;
if (std.ascii.eqlIgnoreCase(s, "spelling-error")) return .spelling_error; if (std.ascii.eqlIgnoreCase(s, "spelling-error")) return .spelling_error;
if (std.ascii.eqlIgnoreCase(s, "modal")) return .modal;
return Error.InvalidPseudoClass; return Error.InvalidPseudoClass;
} }
}; };

View File

@@ -85,7 +85,7 @@ pub const CSSStyleDeclaration = struct {
return self.order.items.len; return self.order.items.len;
} }
pub fn get_parentRule(_: *const CSSStyleDeclaration) ?CSSRule { pub fn get_parentRule() ?CSSRule {
return null; return null;
} }

View File

@@ -18,7 +18,6 @@
const std = @import("std"); const std = @import("std");
const log = @import("../../log.zig");
const parser = @import("../netsurf.zig"); const parser = @import("../netsurf.zig");
const Page = @import("../page.zig").Page; const Page = @import("../page.zig").Page;
@@ -31,6 +30,7 @@ const css = @import("css.zig");
const Element = @import("element.zig").Element; const Element = @import("element.zig").Element;
const ElementUnion = @import("element.zig").Union; const ElementUnion = @import("element.zig").Union;
const Elements = @import("../html/elements.zig");
const TreeWalker = @import("tree_walker.zig").TreeWalker; const TreeWalker = @import("tree_walker.zig").TreeWalker;
const Env = @import("../env.zig").Env; const Env = @import("../env.zig").Env;
@@ -46,6 +46,7 @@ pub const Document = struct {
pub fn constructor(page: *const Page) !*parser.DocumentHTML { pub fn constructor(page: *const Page) !*parser.DocumentHTML {
const doc = try parser.documentCreateDocument( const doc = try parser.documentCreateDocument(
try parser.documentHTMLGetTitle(page.window.document), try parser.documentHTMLGetTitle(page.window.document),
&Elements.createElement,
); );
// we have to work w/ document instead of html document. // we have to work w/ document instead of html document.
@@ -121,18 +122,9 @@ pub const Document = struct {
return try Element.toInterface(e); return try Element.toInterface(e);
} }
const CreateElementResult = union(enum) { pub fn _createElement(self: *parser.Document, tag_name: []const u8) !ElementUnion {
element: ElementUnion,
custom: Env.JsObject,
};
pub fn _createElement(self: *parser.Document, tag_name: []const u8, page: *Page) !CreateElementResult {
if (try page.window.custom_elements.newInstance(tag_name)) |ce| {
return .{ .custom = ce };
}
const e = try parser.documentCreateElement(self, tag_name); const e = try parser.documentCreateElement(self, tag_name);
return .{ .element = try Element.toInterface(e) }; return try Element.toInterface(e);
} }
pub fn _createElementNS(self: *parser.Document, ns: []const u8, tag_name: []const u8) !ElementUnion { pub fn _createElementNS(self: *parser.Document, ns: []const u8, tag_name: []const u8) !ElementUnion {

View File

@@ -30,8 +30,8 @@ pub const DOMParser = struct {
// TODO: Support XML // TODO: Support XML
return error.TypeError; return error.TypeError;
} }
const Elements = @import("../html/elements.zig");
return try parser.documentHTMLParseFromStr(string); return try parser.documentHTMLParseFromStr(string, &Elements.createElement);
} }
}; };

View File

@@ -42,7 +42,8 @@ pub const DOMImplementation = struct {
} }
pub fn _createHTMLDocument(_: *DOMImplementation, title: ?[]const u8) !*parser.DocumentHTML { pub fn _createHTMLDocument(_: *DOMImplementation, title: ?[]const u8) !*parser.DocumentHTML {
return try parser.domImplementationCreateHTMLDocument(title); const Elements = @import("../html/elements.zig");
return try parser.domImplementationCreateHTMLDocument(title, &Elements.createElement);
} }
pub fn _hasFeature(_: *DOMImplementation) bool { pub fn _hasFeature(_: *DOMImplementation) bool {

View File

@@ -182,7 +182,7 @@ fn writeEscapedAttributeValue(writer: anytype, value: []const u8) !void {
const testing = std.testing; const testing = std.testing;
test "dump.writeHTML" { test "dump.writeHTML" {
try parser.init(); try parser.init(testing.allocator);
defer parser.deinit(); defer parser.deinit();
try testWriteHTML( try testWriteHTML(
@@ -225,7 +225,8 @@ fn testWriteFullHTML(comptime expected: []const u8, src: []const u8) !void {
var buf = std.ArrayListUnmanaged(u8){}; var buf = std.ArrayListUnmanaged(u8){};
defer buf.deinit(testing.allocator); defer buf.deinit(testing.allocator);
const doc_html = try parser.documentHTMLParseFromStr(src); const Elements = @import("html/elements.zig");
const doc_html = try parser.documentHTMLParseFromStr(src, &Elements.createElement);
defer parser.documentHTMLClose(doc_html) catch {}; defer parser.documentHTMLClose(doc_html) catch {};
const doc = parser.documentHTMLToDocument(doc_html); const doc = parser.documentHTMLToDocument(doc_html);

View File

@@ -33,7 +33,6 @@ const WebApis = struct {
@import("xhr/xhr.zig").Interfaces, @import("xhr/xhr.zig").Interfaces,
@import("xhr/form_data.zig").Interfaces, @import("xhr/form_data.zig").Interfaces,
@import("xmlserializer/xmlserializer.zig").Interfaces, @import("xmlserializer/xmlserializer.zig").Interfaces,
@import("webcomponents/webcomponents.zig").Interfaces,
}); });
}; };

View File

@@ -16,7 +16,6 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const log = @import("../../log.zig");
const parser = @import("../netsurf.zig"); const parser = @import("../netsurf.zig");
const generate = @import("../../runtime/generate.zig"); const generate = @import("../../runtime/generate.zig");
@@ -27,6 +26,7 @@ const urlStitch = @import("../../url.zig").URL.stitch;
const URL = @import("../url/url.zig").URL; const URL = @import("../url/url.zig").URL;
const Node = @import("../dom/node.zig").Node; const Node = @import("../dom/node.zig").Node;
const Element = @import("../dom/element.zig").Element; const Element = @import("../dom/element.zig").Element;
const State = @import("../State.zig");
const CSSStyleDeclaration = @import("../cssom/css_style_declaration.zig").CSSStyleDeclaration; const CSSStyleDeclaration = @import("../cssom/css_style_declaration.zig").CSSStyleDeclaration;
@@ -113,10 +113,6 @@ pub const HTMLElement = struct {
pub const prototype = *Element; pub const prototype = *Element;
pub const subtype = .node; 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 { pub fn get_style(e: *parser.ElementHTML, page: *Page) !*CSSStyleDeclaration {
const state = try page.getOrCreateNodeState(@ptrCast(e)); const state = try page.getOrCreateNodeState(@ptrCast(e));
return &state.style; return &state.style;
@@ -179,10 +175,6 @@ pub const HTMLMediaElement = struct {
pub const Self = parser.MediaElement; pub const Self = parser.MediaElement;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
}; };
// HTML elements // HTML elements
@@ -192,10 +184,6 @@ pub const HTMLUnknownElement = struct {
pub const Self = parser.Unknown; pub const Self = parser.Unknown;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 // https://html.spec.whatwg.org/#the-a-element
@@ -204,10 +192,6 @@ pub const HTMLAnchorElement = struct {
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 { pub fn get_target(self: *parser.Anchor) ![]const u8 {
return try parser.anchorGetTarget(self); return try parser.anchorGetTarget(self);
} }
@@ -445,240 +429,144 @@ pub const HTMLAppletElement = struct {
pub const Self = parser.Applet; pub const Self = parser.Applet;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLAreaElement = struct {
pub const Self = parser.Area; pub const Self = parser.Area;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLAudioElement = struct {
pub const Self = parser.Audio; pub const Self = parser.Audio;
pub const prototype = *HTMLMediaElement; pub const prototype = *HTMLMediaElement;
pub const subtype = .node; 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 HTMLBRElement = struct {
pub const Self = parser.BR; pub const Self = parser.BR;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLBaseElement = struct {
pub const Self = parser.Base; pub const Self = parser.Base;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLBodyElement = struct {
pub const Self = parser.Body; pub const Self = parser.Body;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLButtonElement = struct {
pub const Self = parser.Button; pub const Self = parser.Button;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLCanvasElement = struct {
pub const Self = parser.Canvas; pub const Self = parser.Canvas;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLDListElement = struct {
pub const Self = parser.DList; pub const Self = parser.DList;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLDataElement = struct {
pub const Self = parser.Data; pub const Self = parser.Data;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLDataListElement = struct {
pub const Self = parser.DataList; pub const Self = parser.DataList;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLDialogElement = struct {
pub const Self = parser.Dialog; pub const Self = parser.Dialog;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLDirectoryElement = struct {
pub const Self = parser.Directory; pub const Self = parser.Directory;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLDivElement = struct {
pub const Self = parser.Div; pub const Self = parser.Div;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLEmbedElement = struct {
pub const Self = parser.Embed; pub const Self = parser.Embed;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLFieldSetElement = struct {
pub const Self = parser.FieldSet; pub const Self = parser.FieldSet;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLFontElement = struct {
pub const Self = parser.Font; pub const Self = parser.Font;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLFrameElement = struct {
pub const Self = parser.Frame; pub const Self = parser.Frame;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLFrameSetElement = struct {
pub const Self = parser.FrameSet; pub const Self = parser.FrameSet;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLHRElement = struct {
pub const Self = parser.HR; pub const Self = parser.HR;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLHeadElement = struct {
pub const Self = parser.Head; pub const Self = parser.Head;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLHeadingElement = struct {
pub const Self = parser.Heading; pub const Self = parser.Heading;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLHtmlElement = struct {
pub const Self = parser.Html; pub const Self = parser.Html;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLIFrameElement = struct {
pub const Self = parser.IFrame; pub const Self = parser.IFrame;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
}; };
pub const HTMLImageElement = struct { pub const HTMLImageElement = struct {
@@ -686,10 +574,6 @@ pub const HTMLImageElement = struct {
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 { pub fn get_alt(self: *parser.Image) ![]const u8 {
return try parser.imageGetAlt(self); return try parser.imageGetAlt(self);
} }
@@ -730,7 +614,6 @@ pub const HTMLImageElement = struct {
pub const Factory = struct { pub const Factory = struct {
pub const js_name = "Image"; pub const js_name = "Image";
pub const subtype = .node; pub const subtype = .node;
pub const js_legacy_factory = true; pub const js_legacy_factory = true;
pub const prototype = *HTMLImageElement; pub const prototype = *HTMLImageElement;
@@ -744,13 +627,83 @@ pub const HTMLImageElement = struct {
}; };
}; };
pub fn createElement(params: [*c]parser.c.dom_html_element_create_params, elem: [*c][*c]parser.ElementHTML) callconv(.c) parser.c.dom_exception {
const p: *parser.c.dom_html_element_create_params = @ptrCast(params);
switch (p.type) {
parser.c.DOM_HTML_ELEMENT_TYPE_INPUT => {
return HTMLInputElement.dom_create(params, elem);
},
else => return parser.c.DOM_NO_ERR,
}
}
var input_protected_vtable: parser.c.dom_element_protected_vtable = .{
.base = .{
.destroy = HTMLInputElement.node_destroy,
.copy = HTMLInputElement.node_copy,
},
.dom_element_parse_attribute = HTMLInputElement.element_parse_attribute,
};
pub const HTMLInputElement = struct { pub const HTMLInputElement = struct {
pub const Self = parser.Input; pub const Self = parser.Input;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element { base: parser.ElementHTML,
return constructHtmlElement(page, js_this);
type: []const u8 = "text",
pub fn dom_create(params: *parser.c.dom_html_element_create_params, output: *?*parser.ElementHTML) parser.c.dom_exception {
var self = parser.ARENA.?.create(HTMLInputElement) catch return parser.c.DOM_NO_MEM_ERR;
output.* = &self.base; // Self can be recovered using @fieldParentPtr
self.base.base.base.base.vtable = &parser.c._dom_html_element_vtable; // TODO replace get/setAttribute
self.base.base.base.vtable = &input_protected_vtable;
return self.dom_initialise(params);
}
// Initialise is separated from create such that the leaf type sets the vtable, then calls all the way up the protochain to init
pub fn dom_initialise(self: *HTMLInputElement, params: *parser.c.dom_html_element_create_params) parser.c.dom_exception {
return parser.c._dom_html_element_initialise(params, &self.base);
}
// This should always be the same and we should not have cleanup for new zig implementation, hopefully
pub fn node_destroy(node: [*c]parser.Node) callconv(.c) void {
const elem = parser.nodeToHtmlElement(node);
parser.c._dom_html_element_finalise(elem);
}
pub fn node_copy(old: [*c]parser.Node, new: [*c][*c]parser.Node) callconv(.c) parser.c.dom_exception {
const old_elem = parser.nodeToHtmlElement(old);
const self = @as(*HTMLInputElement, @fieldParentPtr("base", old_elem));
var copy = parser.ARENA.?.create(HTMLInputElement) catch return parser.c.DOM_NO_MEM_ERR;
copy.type = self.type;
const err = parser.c._dom_html_element_copy_internal(old_elem, &copy.base);
if (err != parser.c.DOM_NO_ERR) {
return err;
}
new.* = @ptrCast(copy);
return parser.c.DOM_NO_ERR;
}
// fn ([*c]cimport.struct_dom_element, [*c]cimport.struct_dom_string, [*c]cimport.struct_dom_string, [*c][*c]cimport.struct_dom_string) callconv(.c) c_uint
pub fn element_parse_attribute(self: [*c]parser.Element, name: [*c]parser.c.dom_string, value: [*c]parser.c.dom_string, parsed: [*c][*c]parser.c.dom_string) callconv(.c) parser.c.dom_exception {
_ = name;
_ = self;
parsed.* = value;
_ = parser.c.dom_string_ref(value);
// TODO actual implementation
// Probably should not use this and instead override the getAttribute setAttribute Element methods directly, perhaps other related functions.
// handle defaultValue likes
// Call setter or store in general attribute store
// increment domstring ref?
return parser.c.DOM_NO_ERR;
} }
pub fn get_defaultValue(self: *parser.Input) ![]const u8 { pub fn get_defaultValue(self: *parser.Input) ![]const u8 {
@@ -824,10 +777,26 @@ pub const HTMLInputElement = struct {
try parser.inputSetSrc(self, new_src); try parser.inputSetSrc(self, new_src);
} }
pub fn get_type(self: *parser.Input) ![]const u8 { pub fn get_type(self: *parser.Input) ![]const u8 {
return try parser.inputGetType(self); const elem = parser.nodeToHtmlElement(@alignCast(@ptrCast(self)));
const input = @as(*HTMLInputElement, @fieldParentPtr("base", elem));
return input.type;
} }
pub fn set_type(self: *parser.Input, type_: []const u8) !void { pub fn set_type(self: *parser.Input, type_: []const u8) !void {
try parser.inputSetType(self, type_); const elem = parser.nodeToHtmlElement(@alignCast(@ptrCast(self)));
const input = @as(*HTMLInputElement, @fieldParentPtr("base", elem));
const possible_values = [_][]const u8{ "text", "search", "tel", "url", "email", "password", "date", "month", "week", "time", "datetime-local", "number", "range", "color", "checkbox", "radio", "file", "hidden", "image", "button", "submit", "reset" };
var found = false;
for (possible_values) |item| {
if (std.mem.eql(u8, type_, item)) {
found = true;
break;
}
}
input.type = if (found) type_ else "text";
// TODO DOM events
} }
pub fn get_value(self: *parser.Input) ![]const u8 { pub fn get_value(self: *parser.Input) ![]const u8 {
return try parser.inputGetValue(self); return try parser.inputGetValue(self);
@@ -841,190 +810,114 @@ pub const HTMLLIElement = struct {
pub const Self = parser.LI; pub const Self = parser.LI;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLLabelElement = struct {
pub const Self = parser.Label; pub const Self = parser.Label;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLLegendElement = struct {
pub const Self = parser.Legend; pub const Self = parser.Legend;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLLinkElement = struct {
pub const Self = parser.Link; pub const Self = parser.Link;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLMapElement = struct {
pub const Self = parser.Map; pub const Self = parser.Map;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLMetaElement = struct {
pub const Self = parser.Meta; pub const Self = parser.Meta;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLMeterElement = struct {
pub const Self = parser.Meter; pub const Self = parser.Meter;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLModElement = struct {
pub const Self = parser.Mod; pub const Self = parser.Mod;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLOListElement = struct {
pub const Self = parser.OList; pub const Self = parser.OList;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLObjectElement = struct {
pub const Self = parser.Object; pub const Self = parser.Object;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLOptGroupElement = struct {
pub const Self = parser.OptGroup; pub const Self = parser.OptGroup;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLOptionElement = struct {
pub const Self = parser.Option; pub const Self = parser.Option;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLOutputElement = struct {
pub const Self = parser.Output; pub const Self = parser.Output;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLParagraphElement = struct {
pub const Self = parser.Paragraph; pub const Self = parser.Paragraph;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLParamElement = struct {
pub const Self = parser.Param; pub const Self = parser.Param;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLPictureElement = struct {
pub const Self = parser.Picture; pub const Self = parser.Picture;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLPreElement = struct {
pub const Self = parser.Pre; pub const Self = parser.Pre;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLProgressElement = struct {
pub const Self = parser.Progress; pub const Self = parser.Progress;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLQuoteElement = struct {
pub const Self = parser.Quote; pub const Self = parser.Quote;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 // https://html.spec.whatwg.org/#the-script-element
@@ -1033,10 +926,6 @@ pub const HTMLScriptElement = struct {
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 { pub fn get_src(self: *parser.Script) !?[]const u8 {
return try parser.elementGetAttribute( return try parser.elementGetAttribute(
parser.scriptToElt(self), parser.scriptToElt(self),
@@ -1171,166 +1060,101 @@ pub const HTMLSourceElement = struct {
pub const Self = parser.Source; pub const Self = parser.Source;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLSpanElement = struct {
pub const Self = parser.Span; pub const Self = parser.Span;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLStyleElement = struct {
pub const Self = parser.Style; pub const Self = parser.Style;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLTableElement = struct {
pub const Self = parser.Table; pub const Self = parser.Table;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLTableCaptionElement = struct {
pub const Self = parser.TableCaption; pub const Self = parser.TableCaption;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLTableCellElement = struct {
pub const Self = parser.TableCell; pub const Self = parser.TableCell;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLTableColElement = struct {
pub const Self = parser.TableCol; pub const Self = parser.TableCol;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLTableRowElement = struct {
pub const Self = parser.TableRow; pub const Self = parser.TableRow;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLTableSectionElement = struct {
pub const Self = parser.TableSection; pub const Self = parser.TableSection;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLTemplateElement = struct {
pub const Self = parser.Template; pub const Self = parser.Template;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLTextAreaElement = struct {
pub const Self = parser.TextArea; pub const Self = parser.TextArea;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLTimeElement = struct {
pub const Self = parser.Time; pub const Self = parser.Time;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLTitleElement = struct {
pub const Self = parser.Title; pub const Self = parser.Title;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLTrackElement = struct {
pub const Self = parser.Track; pub const Self = parser.Track;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLUListElement = struct {
pub const Self = parser.UList; pub const Self = parser.UList;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 HTMLVideoElement = struct {
pub const Self = parser.Video; pub const Self = parser.Video;
pub const prototype = *HTMLElement; pub const prototype = *HTMLElement;
pub const subtype = .node; 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 { pub fn toInterface(comptime T: type, e: *parser.Element) !T {
const elem: *align(@alignOf(*parser.Element)) parser.Element = @alignCast(e); const elem: *align(@alignOf(*parser.Element)) parser.Element = @alignCast(e);
const tag = try parser.elementHTMLGetTagType(@as(*parser.ElementHTML, @ptrCast(elem))); const tag = try parser.elementHTMLGetTagType(@as(*parser.ElementHTML, @ptrCast(elem)));
return switch (tag) { 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)) }, .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)) }, .a => .{ .HTMLAnchorElement = @as(*parser.Anchor, @ptrCast(elem)) },
@@ -1402,16 +1226,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"); const testing = @import("../../testing.zig");
test "Browser.HTML.Element" { test "Browser.HTML.Element" {
var runner = try testing.jsRunner(testing.tracking_allocator, .{}); var runner = try testing.jsRunner(testing.tracking_allocator, .{});

View File

@@ -33,7 +33,6 @@ const EventTarget = @import("../dom/event_target.zig").EventTarget;
const MediaQueryList = @import("media_query_list.zig").MediaQueryList; const MediaQueryList = @import("media_query_list.zig").MediaQueryList;
const Performance = @import("performance.zig").Performance; const Performance = @import("performance.zig").Performance;
const CSSStyleDeclaration = @import("../cssom/css_style_declaration.zig").CSSStyleDeclaration; const CSSStyleDeclaration = @import("../cssom/css_style_declaration.zig").CSSStyleDeclaration;
const CustomElementRegistry = @import("../webcomponents/custom_element_registry.zig").CustomElementRegistry;
const storage = @import("../storage/storage.zig"); const storage = @import("../storage/storage.zig");
@@ -59,11 +58,11 @@ pub const Window = struct {
console: Console = .{}, console: Console = .{},
navigator: Navigator = .{}, navigator: Navigator = .{},
performance: Performance, performance: Performance,
custom_elements: CustomElementRegistry = .{},
pub fn create(target: ?[]const u8, navigator: ?Navigator) !Window { pub fn create(target: ?[]const u8, navigator: ?Navigator) !Window {
var fbs = std.io.fixedBufferStream(""); var fbs = std.io.fixedBufferStream("");
const html_doc = try parser.documentHTMLParse(fbs.reader(), "utf-8"); const Elements = @import("../html/elements.zig");
const html_doc = try parser.documentHTMLParse(fbs.reader(), "utf-8", &Elements.createElement);
const doc = parser.documentHTMLToDocument(html_doc); const doc = parser.documentHTMLToDocument(html_doc);
try parser.documentSetDocumentURI(doc, "about:blank"); try parser.documentSetDocumentURI(doc, "about:blank");
@@ -165,10 +164,6 @@ pub const Window = struct {
return &self.performance; return &self.performance;
} }
pub fn get_customElements(self: *Window) *CustomElementRegistry {
return &self.custom_elements;
}
pub fn _requestAnimationFrame(self: *Window, cbk: Function, page: *Page) !u32 { pub fn _requestAnimationFrame(self: *Window, cbk: Function, page: *Page) !u32 {
return self.createTimeout(cbk, 5, page, .{ .animation_frame = true }); return self.createTimeout(cbk, 5, page, .{ .animation_frame = true });
} }
@@ -343,15 +338,20 @@ test "Browser.HTML.Window" {
// Note however that we in this test do not wait as the request is just send to the browser // Note however that we in this test do not wait as the request is just send to the browser
try runner.testCases(&.{ try runner.testCases(&.{
.{ .{
\\ let start = 0; \\ let start;
\\ function step(timestamp) { \\ function step(timestamp) {
\\ start = timestamp; \\ if (start === undefined) {
\\ start = timestamp;
\\ }
\\ const elapsed = timestamp - start;
\\ if (elapsed < 2000) {
\\ requestAnimationFrame(step);
\\ }
\\ } \\ }
, ,
null, null,
}, },
.{ "requestAnimationFrame(step);", null }, // returned id is checked in the next test .{ "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 // cancelAnimationFrame should be able to cancel a request with the given id

View File

@@ -17,8 +17,9 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const Allocator = std.mem.Allocator;
const c = @cImport({ pub const c = @cImport({
@cInclude("dom/dom.h"); @cInclude("dom/dom.h");
@cInclude("core/pi.h"); @cInclude("core/pi.h");
@cInclude("dom/bindings/hubbub/parser.h"); @cInclude("dom/bindings/hubbub/parser.h");
@@ -32,11 +33,13 @@ const c = @cImport({
}); });
const mimalloc = @import("mimalloc.zig"); const mimalloc = @import("mimalloc.zig");
pub var ARENA: ?Allocator = null;
// init initializes netsurf lib. // init initializes netsurf lib.
// init starts a mimalloc heap arena for the netsurf session. The caller must // init starts a mimalloc heap arena for the netsurf session. The caller must
// call deinit() to free the arena memory. // call deinit() to free the arena memory.
pub fn init() !void { pub fn init(allocator: Allocator) !void {
ARENA = allocator;
try mimalloc.create(); try mimalloc.create();
} }
@@ -49,6 +52,7 @@ pub fn deinit() void {
c.lwc_deinit_strings(); c.lwc_deinit_strings();
mimalloc.destroy(); mimalloc.destroy();
ARENA = null;
} }
// Vtable // Vtable
@@ -1357,6 +1361,10 @@ pub inline fn nodeToElement(node: *Node) *Element {
return @as(*Element, @ptrCast(node)); return @as(*Element, @ptrCast(node));
} }
pub inline fn nodeToHtmlElement(node: *Node) *ElementHTML {
return @as(*ElementHTML, @alignCast(@ptrCast(node)));
}
// nodeToDocument is an helper to convert a node to an document. // nodeToDocument is an helper to convert a node to an document.
pub inline fn nodeToDocument(node: *Node) *Document { pub inline fn nodeToDocument(node: *Node) *Document {
return @as(*Document, @ptrCast(node)); return @as(*Document, @ptrCast(node));
@@ -1989,8 +1997,10 @@ pub inline fn domImplementationCreateDocumentType(
return dt.?; return dt.?;
} }
pub inline fn domImplementationCreateHTMLDocument(title: ?[]const u8) !*DocumentHTML { pub const CreateElementFn = ?*const fn ([*c]c.dom_html_element_create_params, [*c][*c]ElementHTML) callconv(.c) c.dom_exception;
const doc_html = try documentCreateDocument(title);
pub inline fn domImplementationCreateHTMLDocument(title: ?[]const u8, create_element: CreateElementFn) !*DocumentHTML {
const doc_html = try documentCreateDocument(title, create_element);
const doc = documentHTMLToDocument(doc_html); const doc = documentHTMLToDocument(doc_html);
// add hierarchy: html, head, body. // add hierarchy: html, head, body.
@@ -2071,7 +2081,7 @@ pub inline fn documentSetInputEncoding(doc: *Document, enc: []const u8) !void {
try DOMErr(err); try DOMErr(err);
} }
pub inline fn documentCreateDocument(title: ?[]const u8) !*DocumentHTML { pub inline fn documentCreateDocument(title: ?[]const u8, create_element: CreateElementFn) !*DocumentHTML {
var doc: ?*Document = undefined; var doc: ?*Document = undefined;
const err = c.dom_implementation_create_document( const err = c.dom_implementation_create_document(
c.DOM_IMPLEMENTATION_HTML, c.DOM_IMPLEMENTATION_HTML,
@@ -2085,6 +2095,9 @@ pub inline fn documentCreateDocument(title: ?[]const u8) !*DocumentHTML {
try DOMErr(err); try DOMErr(err);
const doc_html = @as(*DocumentHTML, @ptrCast(doc.?)); const doc_html = @as(*DocumentHTML, @ptrCast(doc.?));
if (title) |t| try documentHTMLSetTitle(doc_html, t); if (title) |t| try documentHTMLSetTitle(doc_html, t);
doc_html.create_element_external = create_element;
return doc_html; return doc_html;
} }
@@ -2189,12 +2202,12 @@ pub inline fn documentCreateAttributeNS(doc: *Document, ns: []const u8, qname: [
return attr.?; return attr.?;
} }
pub fn documentSetElementAddedCallback( pub fn documentSetScriptAddedCallback(
doc: *Document, doc: *Document,
ctx: *anyopaque, ctx: *anyopaque,
callback: c.dom_element_added_callback, callback: c.dom_script_added_callback,
) void { ) void {
c._dom_document_set_element_added_callback(doc, ctx, callback); c._dom_document_set_script_added_callback(doc, ctx, callback);
} }
// DocumentHTML // DocumentHTML
@@ -2248,24 +2261,26 @@ fn parserErr(err: HubbubErr) ParserError!void {
// documentHTMLParseFromStr parses the given HTML string. // documentHTMLParseFromStr parses the given HTML string.
// The caller is responsible for closing the document. // The caller is responsible for closing the document.
pub fn documentHTMLParseFromStr(str: []const u8) !*DocumentHTML { pub fn documentHTMLParseFromStr(str: []const u8, create_element: CreateElementFn) !*DocumentHTML {
var fbs = std.io.fixedBufferStream(str); var fbs = std.io.fixedBufferStream(str);
return try documentHTMLParse(fbs.reader(), "UTF-8"); return try documentHTMLParse(fbs.reader(), "UTF-8", create_element);
} }
pub fn documentHTMLParse(reader: anytype, enc: ?[:0]const u8) !*DocumentHTML { pub fn documentHTMLParse(reader: anytype, enc: ?[:0]const u8, create_element: CreateElementFn) !*DocumentHTML {
var parser: ?*c.dom_hubbub_parser = undefined; var parser: ?*c.dom_hubbub_parser = undefined;
var doc: ?*c.dom_document = undefined; var doc: ?*c.dom_document = undefined;
var err: c.hubbub_error = undefined; var err: c.hubbub_error = undefined;
var params = parseParams(enc); var params = parseParams(enc);
err = c.dom_hubbub_parser_create(&params, &parser, &doc); err = c.dom_hubbub_parser_create(&params, &parser, &doc);
const result = @as(*DocumentHTML, @ptrCast(doc.?));
result.create_element_external = create_element;
try parserErr(err); try parserErr(err);
defer c.dom_hubbub_parser_destroy(parser); defer c.dom_hubbub_parser_destroy(parser);
try parseData(parser.?, reader); try parseData(parser.?, reader);
return @as(*DocumentHTML, @ptrCast(doc.?)); return result;
} }
pub fn documentParseFragmentFromStr(self: *Document, str: []const u8) !*DocumentFragment { pub fn documentParseFragmentFromStr(self: *Document, str: []const u8) !*DocumentFragment {

View File

@@ -286,7 +286,8 @@ pub const Page = struct {
pub fn loadHTMLDoc(self: *Page, reader: anytype, charset: []const u8) !void { pub fn loadHTMLDoc(self: *Page, reader: anytype, charset: []const u8) !void {
const ccharset = try self.arena.dupeZ(u8, charset); const ccharset = try self.arena.dupeZ(u8, charset);
const html_doc = try parser.documentHTMLParse(reader, ccharset); const Elements = @import("html/elements.zig");
const html_doc = try parser.documentHTMLParse(reader, ccharset, &Elements.createElement);
const doc = parser.documentHTMLToDocument(html_doc); const doc = parser.documentHTMLToDocument(html_doc);
// inject the URL to the document including the fragment. // inject the URL to the document including the fragment.
@@ -297,17 +298,16 @@ pub const Page = struct {
self.window.setStorageShelf( self.window.setStorageShelf(
try self.session.storage_shed.getOrPut(try self.origin(self.arena)), try self.session.storage_shed.getOrPut(try self.origin(self.arena)),
); );
// we want to be notified of any dynamically added script tags
// so that we can load the script. Or dynamically added custom elements
// for their lifecycle callbacks.
parser.documentSetElementAddedCallback(doc, self, elementAddedCallback);
} }
fn processHTMLDoc(self: *Page) !void { fn processHTMLDoc(self: *Page) !void {
const html_doc = self.window.document; const html_doc = self.window.document;
const doc = parser.documentHTMLToDocument(html_doc); const doc = parser.documentHTMLToDocument(html_doc);
// we want to be notified of any dynamically added script tags
// so that we can load the script
parser.documentSetScriptAddedCallback(doc, self, scriptAddedCallback);
const document_element = (try parser.documentGetDocumentElement(doc)) orelse return error.DocumentElementError; const document_element = (try parser.documentGetDocumentElement(doc)) orelse return error.DocumentElementError;
_ = try parser.eventTargetAddEventListener( _ = try parser.eventTargetAddEventListener(
parser.toEventTarget(parser.Element, document_element), parser.toEventTarget(parser.Element, document_element),
@@ -353,23 +353,8 @@ pub const Page = struct {
continue; continue;
} }
const current = next.?; const e = parser.nodeToElement(next.?);
const e = parser.nodeToElement(current);
const tag = try parser.elementHTMLGetTagType(@as(*parser.ElementHTML, @ptrCast(e))); 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) { if (tag != .script) {
// ignore non-js script. // ignore non-js script.
continue; continue;
@@ -818,43 +803,6 @@ pub const Page = struct {
} }
return null; return null;
} }
fn elementAdded(self: *Page, element: *parser.Element) !void {
if (self.delayed_navigation) {
// if we're planning on navigating to another page, we can skip whatever
// this is.
return;
}
switch (try parser.elementHTMLGetTagType(@as(*parser.ElementHTML, @ptrCast(element)))) {
.script => {
var script = Script.init(element, self) catch |err| {
log.warn(.browser, "script added", .{ .err = err });
return;
} orelse return;
_ = self.evalScript(&script);
},
.undef => {
// a custom element
const js_obj = self.main_context.getJsObject(element) orelse return;
// @memory
// getFunction, and more generally Env.Function always create
// a Persisted Object. But, in cases like this, we don't need
// it to persist.
const func = (try js_obj.getFunction("connectedCallback")) orelse return;
var result: Env.Function.Result = undefined;
func.tryCallWithThis(void, js_obj, .{}, &result) catch {
log.warn(.user_script, "connected callback", .{
.err = result.exception,
.stack = result.stack,
});
};
},
else => {},
}
}
}; };
const DelayedNavigation = struct { const DelayedNavigation = struct {
@@ -1128,22 +1076,24 @@ fn timestamp() u32 {
return @intCast(ts.sec); return @intCast(ts.sec);
} }
// A callback from libdom whenever an html element is added to the DOM. // A callback from libdom whenever a script tag is added to the DOM.
// Note that "added" could mean that it was removed from one parent and re-added
// to another, which is how MOST APIs implement "move" (corrently so).
//
// The only API which seems to actual "move" is Element.moveBefore, which we
// don't currently implement, but should support in general, and should handle
// specifically here.
// element is guaranteed to be a script element. // element is guaranteed to be a script element.
// The script tag might not have a src. It might have any attribute, like // The script tag might not have a src. It might be any attribute, like
// `nomodule`, `defer` and `async`. `Script.init` will return null on `nomodule` // `nomodule`, `defer` and `async`. `Script.init` will return null on `nomodule`
// so that's handled. And because we're only executing the inline <script> tags // so that's handled. And because we're only executing the inline <script> tags
// after the document is loaded, it's ok to execute any async and defer scripts // after the document is loaded, it's ok to execute any async and defer scripts
// immediately. // immediately.
pub export fn elementAddedCallback(ctx: ?*anyopaque, element: ?*parser.Element) callconv(.C) void { pub export fn scriptAddedCallback(ctx: ?*anyopaque, element: ?*parser.Element) callconv(.C) void {
const self: *Page = @alignCast(@ptrCast(ctx.?)); const self: *Page = @alignCast(@ptrCast(ctx.?));
self.elementAdded(element.?) catch |err| { if (self.delayed_navigation) {
log.warn(.browser, "element added callback", .{ .err = err }); // if we're planning on navigating to another page, don't run this script
}; return;
}
var script = Script.init(element.?, self) catch |err| {
log.warn(.browser, "script added init error", .{ .err = err });
return;
} orelse return;
_ = self.evalScript(&script);
} }

View File

@@ -85,14 +85,14 @@ pub const Session = struct {
pub fn createPage(self: *Session) !*Page { pub fn createPage(self: *Session) !*Page {
std.debug.assert(self.page == null); std.debug.assert(self.page == null);
// Start netsurf memory arena.
// We need to init this early as JS event handlers may be registered through Runtime.evaluate before the first html doc is loaded
try parser.init();
const page_arena = &self.browser.page_arena; const page_arena = &self.browser.page_arena;
_ = page_arena.reset(.{ .retain_with_limit = 1 * 1024 * 1024 }); _ = page_arena.reset(.{ .retain_with_limit = 1 * 1024 * 1024 });
_ = self.browser.state_pool.reset(.{ .retain_with_limit = 4 * 1024 }); _ = self.browser.state_pool.reset(.{ .retain_with_limit = 4 * 1024 });
// Start netsurf memory arena.
// We need to init this early as JS event handlers may be registered through Runtime.evaluate before the first html doc is loaded
try parser.init(page_arena.allocator());
self.page = @as(Page, undefined); self.page = @as(Page, undefined);
const page = &self.page.?; const page = &self.page.?;
try Page.init(page, page_arena.allocator(), self); try Page.init(page, page_arena.allocator(), self);

View File

@@ -1,115 +0,0 @@
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// 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 <https://www.gnu.org/licenses/>.
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;
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);
}
pub fn newInstance(self: *const CustomElementRegistry, tag_name: []const u8) !?Env.JsObject {
const func = self.lookup.get(tag_name) orelse return null;
var result: Env.Function.Result = undefined;
const js_obj = func.newInstance(&result) catch |err| {
log.fatal(.user_script, "newInstance error", .{
.err = result.exception,
.stack = result.stack,
.tag_name = tag_name,
.source = "createElement",
});
return err;
};
// This is associated with an HTML element, which, at the very least
// is going to be libdom node. It will outlive this call, and thus needs
// to be persisted.
return try js_obj.persist();
}
};
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 = 'created';
\\ }
\\
\\ connectedCallback() {
\\ this.textContent = 'connected';
\\ }
\\ }
,
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", "created" },
.{ "document.getElementsByTagName('body')[0].append(el)", null },
.{ "el.textContent", "connected" },
// Create element via HTML parsing
// .{ "document.body.innerHTML = '<my-element></my-element>'", "undefined" },
// .{ "let parsed = document.querySelector('my-element')", "undefined" },
// .{ "parsed instanceof MyElement", "true" },
// .{ "parsed.textContent", "Hello World" },
}, .{});
}

View File

@@ -1,23 +0,0 @@
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// 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 <https://www.gnu.org/licenses/>.
const CustomElementRegistry = @import("custom_element_registry.zig").CustomElementRegistry;
pub const Interfaces = .{
CustomElementRegistry,
};

View File

@@ -115,24 +115,17 @@ const EntryIterable = iterator.Iterable(kv.EntryIterator, "FormDataEntryIterator
// TODO: handle disabled fieldsets // TODO: handle disabled fieldsets
fn collectForm(form: *parser.Form, submitter_: ?*parser.ElementHTML, page: *Page) !kv.List { fn collectForm(form: *parser.Form, submitter_: ?*parser.ElementHTML, page: *Page) !kv.List {
const arena = page.arena; const arena = page.arena;
const collection = try parser.formGetCollection(form);
// Don't use libdom's formGetCollection (aka dom_html_form_element_get_elements) const len = try parser.htmlCollectionGetLength(collection);
// 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 = .{}; var entries: kv.List = .{};
try entries.ensureTotalCapacity(arena, nodes.len); try entries.ensureTotalCapacity(arena, len);
var submitter_included = false; var submitter_included = false;
const submitter_name_ = try getSubmitterName(submitter_); const submitter_name_ = try getSubmitterName(submitter_);
for (nodes) |node| { for (0..len) |i| {
const node = try parser.htmlCollectionItem(collection, @intCast(i));
const element = parser.nodeToElement(node); const element = parser.nodeToElement(node);
// must have a name // must have a name
@@ -188,7 +181,10 @@ fn collectForm(form: *parser.Form, submitter_: ?*parser.ElementHTML, page: *Page
submitter_included = true; submitter_included = true;
} }
}, },
else => unreachable, else => {
log.warn(.web_api, "unsupported form element", .{ .tag = @tagName(tag) });
continue;
},
} }
} }
@@ -301,7 +297,6 @@ test "Browser.FormData" {
\\ <input type=submit name=s2 value=s2-v> \\ <input type=submit name=s2 value=s2-v>
\\ <input type=image name=i1 value=i1-v> \\ <input type=image name=i1 value=i1-v>
\\ </form> \\ </form>
\\ <input type=text name=abc value=123 form=form1>
}); });
defer runner.deinit(); defer runner.deinit();
@@ -361,8 +356,6 @@ test "Browser.FormData" {
try runner.testCases(&.{ try runner.testCases(&.{
.{ "let form1 = document.getElementById('form1')", null }, .{ "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 submit1 = document.getElementById('s1')", null },
.{ "let f2 = new FormData(form1, submit1)", null }, .{ "let f2 = new FormData(form1, submit1)", null },
.{ "acc = '';", null }, .{ "acc = '';", null },
@@ -385,7 +378,6 @@ test "Browser.FormData" {
\\mlt-2=water \\mlt-2=water
\\mlt-2=tea \\mlt-2=tea
\\s1=s1-v \\s1=s1-v
\\dyn=dyn-v
}, },
}, .{}); }, .{});
} }

View File

@@ -756,7 +756,8 @@ pub const XMLHttpRequest = struct {
} }
var fbs = std.io.fixedBufferStream(self.response_bytes.items); var fbs = std.io.fixedBufferStream(self.response_bytes.items);
const doc = parser.documentHTMLParse(fbs.reader(), ccharset) catch { const Elements = @import("../html/elements.zig");
const doc = parser.documentHTMLParse(fbs.reader(), ccharset, &Elements.createElement) catch {
self.response_obj = .{ .Failure = {} }; self.response_obj = .{ .Failure = {} };
return; return;
}; };

View File

@@ -23,6 +23,7 @@ const json = std.json;
const log = @import("../log.zig"); const log = @import("../log.zig");
const App = @import("../app.zig").App; const App = @import("../app.zig").App;
const Env = @import("../browser/env.zig").Env; const Env = @import("../browser/env.zig").Env;
const asUint = @import("../str/parser.zig").asUint;
const Browser = @import("../browser/browser.zig").Browser; const Browser = @import("../browser/browser.zig").Browser;
const Session = @import("../browser/session.zig").Session; const Session = @import("../browser/session.zig").Session;
const Page = @import("../browser/page.zig").Page; const Page = @import("../browser/page.zig").Page;
@@ -181,41 +182,41 @@ pub fn CDPT(comptime TypeProvider: type) type {
switch (domain.len) { switch (domain.len) {
3 => switch (@as(u24, @bitCast(domain[0..3].*))) { 3 => switch (@as(u24, @bitCast(domain[0..3].*))) {
asUint(u24, "DOM") => return @import("domains/dom.zig").processMessage(command), asUint("DOM") => return @import("domains/dom.zig").processMessage(command),
asUint(u24, "Log") => return @import("domains/log.zig").processMessage(command), asUint("Log") => return @import("domains/log.zig").processMessage(command),
asUint(u24, "CSS") => return @import("domains/css.zig").processMessage(command), asUint("CSS") => return @import("domains/css.zig").processMessage(command),
else => {}, else => {},
}, },
4 => switch (@as(u32, @bitCast(domain[0..4].*))) { 4 => switch (@as(u32, @bitCast(domain[0..4].*))) {
asUint(u32, "Page") => return @import("domains/page.zig").processMessage(command), asUint("Page") => return @import("domains/page.zig").processMessage(command),
else => {}, else => {},
}, },
5 => switch (@as(u40, @bitCast(domain[0..5].*))) { 5 => switch (@as(u40, @bitCast(domain[0..5].*))) {
asUint(u40, "Fetch") => return @import("domains/fetch.zig").processMessage(command), asUint("Fetch") => return @import("domains/fetch.zig").processMessage(command),
asUint(u40, "Input") => return @import("domains/input.zig").processMessage(command), asUint("Input") => return @import("domains/input.zig").processMessage(command),
else => {}, else => {},
}, },
6 => switch (@as(u48, @bitCast(domain[0..6].*))) { 6 => switch (@as(u48, @bitCast(domain[0..6].*))) {
asUint(u48, "Target") => return @import("domains/target.zig").processMessage(command), asUint("Target") => return @import("domains/target.zig").processMessage(command),
else => {}, else => {},
}, },
7 => switch (@as(u56, @bitCast(domain[0..7].*))) { 7 => switch (@as(u56, @bitCast(domain[0..7].*))) {
asUint(u56, "Browser") => return @import("domains/browser.zig").processMessage(command), asUint("Browser") => return @import("domains/browser.zig").processMessage(command),
asUint(u56, "Runtime") => return @import("domains/runtime.zig").processMessage(command), asUint("Runtime") => return @import("domains/runtime.zig").processMessage(command),
asUint(u56, "Network") => return @import("domains/network.zig").processMessage(command), asUint("Network") => return @import("domains/network.zig").processMessage(command),
else => {}, else => {},
}, },
8 => switch (@as(u64, @bitCast(domain[0..8].*))) { 8 => switch (@as(u64, @bitCast(domain[0..8].*))) {
asUint(u64, "Security") => return @import("domains/security.zig").processMessage(command), asUint("Security") => return @import("domains/security.zig").processMessage(command),
else => {}, else => {},
}, },
9 => switch (@as(u72, @bitCast(domain[0..9].*))) { 9 => switch (@as(u72, @bitCast(domain[0..9].*))) {
asUint(u72, "Emulation") => return @import("domains/emulation.zig").processMessage(command), asUint("Emulation") => return @import("domains/emulation.zig").processMessage(command),
asUint(u72, "Inspector") => return @import("domains/inspector.zig").processMessage(command), asUint("Inspector") => return @import("domains/inspector.zig").processMessage(command),
else => {}, else => {},
}, },
11 => switch (@as(u88, @bitCast(domain[0..11].*))) { 11 => switch (@as(u88, @bitCast(domain[0..11].*))) {
asUint(u88, "Performance") => return @import("domains/performance.zig").processMessage(command), asUint("Performance") => return @import("domains/performance.zig").processMessage(command),
else => {}, else => {},
}, },
else => {}, else => {},
@@ -695,10 +696,6 @@ const InputParams = struct {
} }
}; };
fn asUint(comptime T: type, comptime string: []const u8) T {
return @bitCast(string[0..string.len].*);
}
const testing = @import("testing.zig"); const testing = @import("testing.zig");
test "cdp: invalid json" { test "cdp: invalid json" {
var ctx = testing.context(); var ctx = testing.context();

View File

@@ -3170,7 +3170,7 @@ test "HttpClient: async tls no body" {
} }
} }
test "HttpClient: async tls with body" { test "HttpClient: async tls with body x" {
defer testing.reset(); defer testing.reset();
for (0..5) |_| { for (0..5) |_| {
var client = try testClient(); var client = try testClient();

View File

@@ -146,16 +146,6 @@ 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 { 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.writeAll("$time=");
try writer.print("{d}", .{timestamp()}); try writer.print("{d}", .{timestamp()});
@@ -174,20 +164,15 @@ fn logLogFmtPrefix(comptime scope: Scope, level: Level, comptime msg: []const u8
break :blk prefix ++ "\"" ++ msg ++ "\""; break :blk prefix ++ "\"" ++ msg ++ "\"";
}; };
try writer.writeAll(full_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| { inline for (@typeInfo(@TypeOf(data)).@"struct".fields) |f| {
const key = " " ++ f.name ++ " = "; const key = " " ++ f.name ++ "=";
try writer.writeAll(key); try writer.writeAll(key);
try writeValue(.pretty, @field(data, f.name), writer); try writeValue(.logfmt, @field(data, f.name), writer);
try writer.writeByte('\n');
} }
try writer.writeByte('\n'); try writer.writeByte('\n');
} }
fn logPrettyPrefix(comptime scope: Scope, level: Level, comptime msg: []const u8, writer: anytype) !void { fn logPretty(comptime scope: Scope, level: Level, comptime msg: []const u8, data: anytype, writer: anytype) !void {
if (scope == .console and level == .fatal and comptime std.mem.eql(u8, msg, "lightpanda")) { if (scope == .console and level == .fatal and comptime std.mem.eql(u8, msg, "lightpanda")) {
try writer.writeAll("\x1b[0;104mWARN "); try writer.writeAll("\x1b[0;104mWARN ");
} else { } else {
@@ -216,6 +201,14 @@ fn logPrettyPrefix(comptime scope: Scope, level: Level, comptime msg: []const u8
try writer.print(" \x1b[0m[+{d}ms]", .{elapsed()}); try writer.print(" \x1b[0m[+{d}ms]", .{elapsed()});
try writer.writeByte('\n'); 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 { pub fn writeValue(comptime format: Format, value: anytype, writer: anytype) !void {

View File

@@ -522,7 +522,7 @@ test {
var test_wg: std.Thread.WaitGroup = .{}; var test_wg: std.Thread.WaitGroup = .{};
test "tests:beforeAll" { test "tests:beforeAll" {
try parser.init(); try parser.init(std.testing.allocator);
log.opts.level = .err; log.opts.level = .err;
log.opts.format = .logfmt; log.opts.format = .logfmt;

View File

@@ -1158,7 +1158,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
} }
if (!js_value.isArray()) { if (!js_value.isArray()) {
return .{ .invalid = {} }; return error.InvalidArgument;
} }
// This can get tricky. // This can get tricky.
@@ -1196,14 +1196,6 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
return .{ .invalid = {} }; return .{ .invalid = {} };
} }
pub fn getJsObject(self: *JsContext, zig_value: *anyopaque) ?JsObject {
const po = self.identity_map.get(@intFromPtr(zig_value)) orelse return null;
return .{
.js_context = self,
.js_obj = po.castToObject(),
};
}
// Callback from V8, asking us to load a module. The "specifier" is // Callback from V8, asking us to load a module. The "specifier" is
// the src of the module to load. // the src of the module to load.
fn resolveModuleCallback( fn resolveModuleCallback(
@@ -1265,16 +1257,6 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
exception: []const u8, 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 { pub fn withThis(self: *const Function, value: anytype) !Function {
const this_obj = if (@TypeOf(value) == JsObject) const this_obj = if (@TypeOf(value) == JsObject)
value.js_obj value.js_obj
@@ -1289,33 +1271,6 @@ 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 { pub fn call(self: *const Function, comptime T: type, args: anytype) !T {
return self.callWithThis(T, self.getThis(), args); return self.callWithThis(T, self.getThis(), args);
} }
@@ -1495,11 +1450,6 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
.js_obj = array.castTo(v8.Object), .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 // This only exists so that we know whether a function wants the opaque
@@ -1522,10 +1472,6 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
pub fn set(self: JsThis, key: []const u8, value: anytype, opts: JsObject.SetOpts) !void { pub fn set(self: JsThis, key: []const u8, value: anytype, opts: JsObject.SetOpts) !void {
return self.obj.set(key, value, opts); 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 { pub const TryCatch = struct {
@@ -1838,7 +1784,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
// a constructor function, we'll return an error. // a constructor function, we'll return an error.
if (@hasDecl(Struct, "constructor") == false) { if (@hasDecl(Struct, "constructor") == false) {
const iso = caller.isolate; 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); info.getReturnValue().set(js_exception);
return; return;
} }
@@ -1930,7 +1876,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
defer caller.deinit(); defer caller.deinit();
const named_function = comptime NamedFunction.init(Struct, "get_" ++ name); const named_function = comptime NamedFunction.init(Struct, "get_" ++ name);
caller.method(Struct, named_function, info) catch |err| { caller.getter(Struct, named_function, info) catch |err| {
caller.handleError(Struct, named_function, err, info); caller.handleError(Struct, named_function, err, info);
}; };
} }
@@ -1945,13 +1891,13 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
const setter_callback = v8.FunctionTemplate.initCallback(isolate, struct { const setter_callback = v8.FunctionTemplate.initCallback(isolate, struct {
fn callback(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void { fn callback(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
const info = v8.FunctionCallbackInfo.initFromV8(raw_info); const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
std.debug.assert(info.length() == 1);
var caller = Caller(Self, State).init(info); var caller = Caller(Self, State).init(info);
defer caller.deinit(); defer caller.deinit();
std.debug.assert(info.length() == 1);
const js_value = info.getArg(0);
const named_function = comptime NamedFunction.init(Struct, "set_" ++ name); const named_function = comptime NamedFunction.init(Struct, "set_" ++ name);
caller.method(Struct, named_function, info) catch |err| { caller.setter(Struct, named_function, js_value, info) catch |err| {
caller.handleError(Struct, named_function, err, info); caller.handleError(Struct, named_function, err, info);
}; };
} }
@@ -2478,6 +2424,66 @@ fn Caller(comptime E: type, comptime State: type) type {
info.getReturnValue().set(try js_context.zigValueToJs(res)); 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 { fn getIndex(self: *Self, comptime Struct: type, comptime named_function: NamedFunction, idx: u32, info: v8.PropertyCallbackInfo) !u8 {
const js_context = self.js_context; const js_context = self.js_context;
const func = @field(Struct, named_function.name); const func = @field(Struct, named_function.name);
@@ -2590,14 +2596,19 @@ fn Caller(comptime E: type, comptime State: type) type {
if (comptime builtin.mode == .Debug and @hasDecl(@TypeOf(info), "length")) { if (comptime builtin.mode == .Debug and @hasDecl(@TypeOf(info), "length")) {
if (log.enabled(.js, .warn)) { if (log.enabled(.js, .warn)) {
logFunctionCallError(self.call_arena, self.isolate, self.v8_context, err, named_function.full_name, info); 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),
});
} }
} }
var js_err: ?v8.Value = switch (err) { var js_err: ?v8.Value = switch (err) {
error.InvalidArgument => createTypeException(isolate, "invalid argument"), error.InvalidArgument => createTypeException(isolate, "invalid argument"),
error.OutOfMemory => createException(isolate, "out of memory"), error.OutOfMemory => createException(isolate, "out of memory"),
error.IllegalConstructor => createException(isolate, "Illegal Contructor"),
else => blk: { else => blk: {
const func = @field(Struct, named_function.name); const func = @field(Struct, named_function.name);
const return_type = @typeInfo(@TypeOf(func)).@"fn".return_type orelse { const return_type = @typeInfo(@TypeOf(func)).@"fn".return_type orelse {
@@ -2659,7 +2670,6 @@ fn Caller(comptime E: type, comptime State: type) type {
// Does the error we want to return belong to the custom exeception's ErrorSet // Does the error we want to return belong to the custom exeception's ErrorSet
fn isErrorSetException(comptime Exception: type, err: anytype) bool { fn isErrorSetException(comptime Exception: type, err: anytype) bool {
const Entry = std.meta.Tuple(&.{ []const u8, void }); const Entry = std.meta.Tuple(&.{ []const u8, void });
const error_set = @typeInfo(Exception.ErrorSet).error_set.?; const error_set = @typeInfo(Exception.ErrorSet).error_set.?;
const entries = comptime blk: { const entries = comptime blk: {
var kv: [error_set.len]Entry = undefined; var kv: [error_set.len]Entry = undefined;
@@ -2711,8 +2721,8 @@ fn Caller(comptime E: type, comptime State: type) type {
// a JS argument // a JS argument
if (comptime isJsThis(params[params.len - 1].type.?)) { if (comptime isJsThis(params[params.len - 1].type.?)) {
@field(args, std.fmt.comptimePrint("{d}", .{params.len - 1 + offset})) = .{ .obj = .{ @field(args, std.fmt.comptimePrint("{d}", .{params.len - 1 + offset})) = .{ .obj = .{
.js_context = js_context,
.js_obj = info.getThis(), .js_obj = info.getThis(),
.executor = self.executor,
} }; } };
// AND the 2nd last parameter is state // AND the 2nd last parameter is state
@@ -2798,6 +2808,28 @@ fn Caller(comptime E: type, comptime State: type) type {
const Const_State = if (ti == .pointer) *const ti.pointer.child else State; const Const_State = if (ti == .pointer) *const ti.pointer.child else State;
return T == State or T == Const_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;
}
}; };
} }
@@ -3238,37 +3270,6 @@ 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 // 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 // it'll call this function to gets its [optional] subtype - which, from V8's
// point of view, is an arbitrary string. // point of view, is an arbitrary string.

125
src/str/parser.zig Normal file
View File

@@ -0,0 +1,125 @@
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// 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 <https://www.gnu.org/licenses/>.
// 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")));
}

View File

@@ -211,14 +211,16 @@ pub const Document = struct {
arena: std.heap.ArenaAllocator, arena: std.heap.ArenaAllocator,
pub fn init(html: []const u8) !Document { pub fn init(html: []const u8) !Document {
var arena = std.heap.ArenaAllocator.init(allocator);
parser.deinit(); parser.deinit();
try parser.init(); try parser.init(arena.allocator());
var fbs = std.io.fixedBufferStream(html); var fbs = std.io.fixedBufferStream(html);
const html_doc = try parser.documentHTMLParse(fbs.reader(), "utf-8"); const Elements = @import("browser/html/elements.zig");
const html_doc = try parser.documentHTMLParse(fbs.reader(), "utf-8", &Elements.createElement);
return .{ return .{
.arena = std.heap.ArenaAllocator.init(allocator), .arena = arena,
.doc = html_doc, .doc = html_doc,
}; };
} }