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" {
const alloc = std.testing.allocator;
@@ -161,7 +162,7 @@ test "matchFirst" {
for (testcases) |tc| {
matcher.reset();
const doc = try parser.documentHTMLParseFromStr(tc.html);
const doc = try parser.documentHTMLParseFromStr(tc.html, &Elements.createElement);
defer parser.documentHTMLClose(doc) catch {};
const s = css.parse(alloc, tc.q, .{}) catch |e| {
@@ -302,7 +303,7 @@ test "matchAll" {
for (testcases) |tc| {
matcher.reset();
const doc = try parser.documentHTMLParseFromStr(tc.html);
const doc = try parser.documentHTMLParseFromStr(tc.html, &Elements.createElement);
defer parser.documentHTMLClose(doc) catch {};
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 },
.first_line, .grammar_error, .marker, .placeholder => return .{ .pseudo_element = pseudo_class },
.selection, .spelling_error => return .{ .pseudo_element = pseudo_class },
.modal => return .{ .pseudo_element = pseudo_class },
}
}

View File

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

View File

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

View File

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

View File

@@ -30,8 +30,8 @@ pub const DOMParser = struct {
// TODO: Support XML
return error.TypeError;
}
return try parser.documentHTMLParseFromStr(string);
const Elements = @import("../html/elements.zig");
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 {
return try parser.domImplementationCreateHTMLDocument(title);
const Elements = @import("../html/elements.zig");
return try parser.domImplementationCreateHTMLDocument(title, &Elements.createElement);
}
pub fn _hasFeature(_: *DOMImplementation) bool {

View File

@@ -182,7 +182,7 @@ fn writeEscapedAttributeValue(writer: anytype, value: []const u8) !void {
const testing = std.testing;
test "dump.writeHTML" {
try parser.init();
try parser.init(testing.allocator);
defer parser.deinit();
try testWriteHTML(
@@ -225,7 +225,8 @@ fn testWriteFullHTML(comptime expected: []const u8, src: []const u8) !void {
var buf = std.ArrayListUnmanaged(u8){};
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 {};
const doc = parser.documentHTMLToDocument(doc_html);

View File

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

View File

@@ -16,7 +16,6 @@
// 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 parser = @import("../netsurf.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 Node = @import("../dom/node.zig").Node;
const Element = @import("../dom/element.zig").Element;
const State = @import("../State.zig");
const CSSStyleDeclaration = @import("../cssom/css_style_declaration.zig").CSSStyleDeclaration;
@@ -113,10 +113,6 @@ pub const HTMLElement = struct {
pub const prototype = *Element;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
pub fn get_style(e: *parser.ElementHTML, page: *Page) !*CSSStyleDeclaration {
const state = try page.getOrCreateNodeState(@ptrCast(e));
return &state.style;
@@ -179,10 +175,6 @@ pub const HTMLMediaElement = struct {
pub const Self = parser.MediaElement;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
// HTML elements
@@ -192,10 +184,6 @@ pub const HTMLUnknownElement = struct {
pub const Self = parser.Unknown;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
// https://html.spec.whatwg.org/#the-a-element
@@ -204,10 +192,6 @@ pub const HTMLAnchorElement = struct {
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
pub fn get_target(self: *parser.Anchor) ![]const u8 {
return try parser.anchorGetTarget(self);
}
@@ -445,240 +429,144 @@ pub const HTMLAppletElement = struct {
pub const Self = parser.Applet;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLAreaElement = struct {
pub const Self = parser.Area;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLAudioElement = struct {
pub const Self = parser.Audio;
pub const prototype = *HTMLMediaElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLBRElement = struct {
pub const Self = parser.BR;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLBaseElement = struct {
pub const Self = parser.Base;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLBodyElement = struct {
pub const Self = parser.Body;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLButtonElement = struct {
pub const Self = parser.Button;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLCanvasElement = struct {
pub const Self = parser.Canvas;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLDListElement = struct {
pub const Self = parser.DList;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLDataElement = struct {
pub const Self = parser.Data;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLDataListElement = struct {
pub const Self = parser.DataList;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLDialogElement = struct {
pub const Self = parser.Dialog;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLDirectoryElement = struct {
pub const Self = parser.Directory;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLDivElement = struct {
pub const Self = parser.Div;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLEmbedElement = struct {
pub const Self = parser.Embed;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLFieldSetElement = struct {
pub const Self = parser.FieldSet;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLFontElement = struct {
pub const Self = parser.Font;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLFrameElement = struct {
pub const Self = parser.Frame;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLFrameSetElement = struct {
pub const Self = parser.FrameSet;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLHRElement = struct {
pub const Self = parser.HR;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLHeadElement = struct {
pub const Self = parser.Head;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLHeadingElement = struct {
pub const Self = parser.Heading;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLHtmlElement = struct {
pub const Self = parser.Html;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLIFrameElement = struct {
pub const Self = parser.IFrame;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLImageElement = struct {
@@ -686,10 +574,6 @@ pub const HTMLImageElement = struct {
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
pub fn get_alt(self: *parser.Image) ![]const u8 {
return try parser.imageGetAlt(self);
}
@@ -730,7 +614,6 @@ pub const HTMLImageElement = struct {
pub const Factory = struct {
pub const js_name = "Image";
pub const subtype = .node;
pub const js_legacy_factory = true;
pub const prototype = *HTMLImageElement;
@@ -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 Self = parser.Input;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
base: parser.ElementHTML,
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 {
@@ -824,10 +777,26 @@ pub const HTMLInputElement = struct {
try parser.inputSetSrc(self, new_src);
}
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 {
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 {
return try parser.inputGetValue(self);
@@ -841,190 +810,114 @@ pub const HTMLLIElement = struct {
pub const Self = parser.LI;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLLabelElement = struct {
pub const Self = parser.Label;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLLegendElement = struct {
pub const Self = parser.Legend;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLLinkElement = struct {
pub const Self = parser.Link;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLMapElement = struct {
pub const Self = parser.Map;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLMetaElement = struct {
pub const Self = parser.Meta;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLMeterElement = struct {
pub const Self = parser.Meter;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLModElement = struct {
pub const Self = parser.Mod;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLOListElement = struct {
pub const Self = parser.OList;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLObjectElement = struct {
pub const Self = parser.Object;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLOptGroupElement = struct {
pub const Self = parser.OptGroup;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLOptionElement = struct {
pub const Self = parser.Option;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLOutputElement = struct {
pub const Self = parser.Output;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLParagraphElement = struct {
pub const Self = parser.Paragraph;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLParamElement = struct {
pub const Self = parser.Param;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLPictureElement = struct {
pub const Self = parser.Picture;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLPreElement = struct {
pub const Self = parser.Pre;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLProgressElement = struct {
pub const Self = parser.Progress;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLQuoteElement = struct {
pub const Self = parser.Quote;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
// https://html.spec.whatwg.org/#the-script-element
@@ -1033,10 +926,6 @@ pub const HTMLScriptElement = struct {
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
pub fn get_src(self: *parser.Script) !?[]const u8 {
return try parser.elementGetAttribute(
parser.scriptToElt(self),
@@ -1171,166 +1060,101 @@ pub const HTMLSourceElement = struct {
pub const Self = parser.Source;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLSpanElement = struct {
pub const Self = parser.Span;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLStyleElement = struct {
pub const Self = parser.Style;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLTableElement = struct {
pub const Self = parser.Table;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLTableCaptionElement = struct {
pub const Self = parser.TableCaption;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLTableCellElement = struct {
pub const Self = parser.TableCell;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLTableColElement = struct {
pub const Self = parser.TableCol;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLTableRowElement = struct {
pub const Self = parser.TableRow;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLTableSectionElement = struct {
pub const Self = parser.TableSection;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLTemplateElement = struct {
pub const Self = parser.Template;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLTextAreaElement = struct {
pub const Self = parser.TextArea;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLTimeElement = struct {
pub const Self = parser.Time;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLTitleElement = struct {
pub const Self = parser.Title;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLTrackElement = struct {
pub const Self = parser.Track;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLUListElement = struct {
pub const Self = parser.UList;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub const HTMLVideoElement = struct {
pub const Self = parser.Video;
pub const prototype = *HTMLElement;
pub const subtype = .node;
pub fn constructor(page: *Page, js_this: Env.JsThis) !*parser.Element {
return constructHtmlElement(page, js_this);
}
};
pub fn toInterface(comptime T: type, e: *parser.Element) !T {
const elem: *align(@alignOf(*parser.Element)) parser.Element = @alignCast(e);
const tag = try parser.elementHTMLGetTagType(@as(*parser.ElementHTML, @ptrCast(elem)));
return switch (tag) {
.abbr, .acronym, .address, .article, .aside, .b, .basefont, .bdi, .bdo, .bgsound, .big, .center, .cite, .code, .dd, .details, .dfn, .dt, .em, .figcaption, .figure, .footer, .header, .hgroup, .i, .isindex, .keygen, .kbd, .main, .mark, .marquee, .menu, .menuitem, .nav, .nobr, .noframes, .noscript, .rp, .rt, .ruby, .s, .samp, .section, .small, .spacer, .strike, .strong, .sub, .summary, .sup, .tt, .u, .wbr, ._var => .{ .HTMLElement = @as(*parser.ElementHTML, @ptrCast(elem)) },
.a => .{ .HTMLAnchorElement = @as(*parser.Anchor, @ptrCast(elem)) },
@@ -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");
test "Browser.HTML.Element" {
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 Performance = @import("performance.zig").Performance;
const CSSStyleDeclaration = @import("../cssom/css_style_declaration.zig").CSSStyleDeclaration;
const CustomElementRegistry = @import("../webcomponents/custom_element_registry.zig").CustomElementRegistry;
const storage = @import("../storage/storage.zig");
@@ -59,11 +58,11 @@ pub const Window = struct {
console: Console = .{},
navigator: Navigator = .{},
performance: Performance,
custom_elements: CustomElementRegistry = .{},
pub fn create(target: ?[]const u8, navigator: ?Navigator) !Window {
var fbs = std.io.fixedBufferStream("");
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);
try parser.documentSetDocumentURI(doc, "about:blank");
@@ -165,10 +164,6 @@ pub const Window = struct {
return &self.performance;
}
pub fn get_customElements(self: *Window) *CustomElementRegistry {
return &self.custom_elements;
}
pub fn _requestAnimationFrame(self: *Window, cbk: Function, page: *Page) !u32 {
return self.createTimeout(cbk, 5, page, .{ .animation_frame = true });
}
@@ -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
try runner.testCases(&.{
.{
\\ let start = 0;
\\ let start;
\\ function step(timestamp) {
\\ if (start === undefined) {
\\ start = timestamp;
\\ }
\\ const elapsed = timestamp - start;
\\ if (elapsed < 2000) {
\\ requestAnimationFrame(step);
\\ }
\\ }
,
null,
},
.{ "requestAnimationFrame(step);", null }, // returned id is checked in the next test
.{ " start > 0", "true" },
}, .{});
// cancelAnimationFrame should be able to cancel a request with the given id

View File

@@ -17,8 +17,9 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const Allocator = std.mem.Allocator;
const c = @cImport({
pub const c = @cImport({
@cInclude("dom/dom.h");
@cInclude("core/pi.h");
@cInclude("dom/bindings/hubbub/parser.h");
@@ -32,11 +33,13 @@ const c = @cImport({
});
const mimalloc = @import("mimalloc.zig");
pub var ARENA: ?Allocator = null;
// init initializes netsurf lib.
// init starts a mimalloc heap arena for the netsurf session. The caller must
// call deinit() to free the arena memory.
pub fn init() !void {
pub fn init(allocator: Allocator) !void {
ARENA = allocator;
try mimalloc.create();
}
@@ -49,6 +52,7 @@ pub fn deinit() void {
c.lwc_deinit_strings();
mimalloc.destroy();
ARENA = null;
}
// Vtable
@@ -1357,6 +1361,10 @@ pub inline fn nodeToElement(node: *Node) *Element {
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.
pub inline fn nodeToDocument(node: *Node) *Document {
return @as(*Document, @ptrCast(node));
@@ -1989,8 +1997,10 @@ pub inline fn domImplementationCreateDocumentType(
return dt.?;
}
pub inline fn domImplementationCreateHTMLDocument(title: ?[]const u8) !*DocumentHTML {
const doc_html = try documentCreateDocument(title);
pub const CreateElementFn = ?*const fn ([*c]c.dom_html_element_create_params, [*c][*c]ElementHTML) callconv(.c) c.dom_exception;
pub inline fn domImplementationCreateHTMLDocument(title: ?[]const u8, create_element: CreateElementFn) !*DocumentHTML {
const doc_html = try documentCreateDocument(title, create_element);
const doc = documentHTMLToDocument(doc_html);
// add hierarchy: html, head, body.
@@ -2071,7 +2081,7 @@ pub inline fn documentSetInputEncoding(doc: *Document, enc: []const u8) !void {
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;
const err = c.dom_implementation_create_document(
c.DOM_IMPLEMENTATION_HTML,
@@ -2085,6 +2095,9 @@ pub inline fn documentCreateDocument(title: ?[]const u8) !*DocumentHTML {
try DOMErr(err);
const doc_html = @as(*DocumentHTML, @ptrCast(doc.?));
if (title) |t| try documentHTMLSetTitle(doc_html, t);
doc_html.create_element_external = create_element;
return doc_html;
}
@@ -2189,12 +2202,12 @@ pub inline fn documentCreateAttributeNS(doc: *Document, ns: []const u8, qname: [
return attr.?;
}
pub fn documentSetElementAddedCallback(
pub fn documentSetScriptAddedCallback(
doc: *Document,
ctx: *anyopaque,
callback: c.dom_element_added_callback,
callback: c.dom_script_added_callback,
) void {
c._dom_document_set_element_added_callback(doc, ctx, callback);
c._dom_document_set_script_added_callback(doc, ctx, callback);
}
// DocumentHTML
@@ -2248,24 +2261,26 @@ fn parserErr(err: HubbubErr) ParserError!void {
// documentHTMLParseFromStr parses the given HTML string.
// 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);
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 doc: ?*c.dom_document = undefined;
var err: c.hubbub_error = undefined;
var params = parseParams(enc);
err = c.dom_hubbub_parser_create(&params, &parser, &doc);
const result = @as(*DocumentHTML, @ptrCast(doc.?));
result.create_element_external = create_element;
try parserErr(err);
defer c.dom_hubbub_parser_destroy(parser);
try parseData(parser.?, reader);
return @as(*DocumentHTML, @ptrCast(doc.?));
return result;
}
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 {
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);
// inject the URL to the document including the fragment.
@@ -297,17 +298,16 @@ pub const Page = struct {
self.window.setStorageShelf(
try self.session.storage_shed.getOrPut(try self.origin(self.arena)),
);
// we want to be notified of any dynamically added script tags
// so that we can load the script. Or dynamically added custom elements
// for their lifecycle callbacks.
parser.documentSetElementAddedCallback(doc, self, elementAddedCallback);
}
fn processHTMLDoc(self: *Page) !void {
const html_doc = self.window.document;
const doc = parser.documentHTMLToDocument(html_doc);
// we want to be notified of any dynamically added script tags
// so that we can load the script
parser.documentSetScriptAddedCallback(doc, self, scriptAddedCallback);
const document_element = (try parser.documentGetDocumentElement(doc)) orelse return error.DocumentElementError;
_ = try parser.eventTargetAddEventListener(
parser.toEventTarget(parser.Element, document_element),
@@ -353,23 +353,8 @@ pub const Page = struct {
continue;
}
const current = next.?;
const e = parser.nodeToElement(current);
const e = parser.nodeToElement(next.?);
const tag = try parser.elementHTMLGetTagType(@as(*parser.ElementHTML, @ptrCast(e)));
// if (tag == .undef) {
// const tag_name = try parser.nodeLocalName(@ptrCast(e));
// const custom_elements = &self.window.custom_elements;
// if (custom_elements._get(tag_name)) |construct| {
// try construct.printFunc();
// // This is just here for testing for now.
// // var result: Env.Function.Result = undefined;
// // _ = try construct.newInstance(*parser.Element, &result);
// log.info(.browser, "Registered WebComponent Found", .{ .element_name = tag_name });
// }
// }
if (tag != .script) {
// ignore non-js script.
continue;
@@ -818,43 +803,6 @@ pub const Page = struct {
}
return null;
}
fn elementAdded(self: *Page, element: *parser.Element) !void {
if (self.delayed_navigation) {
// if we're planning on navigating to another page, we can skip whatever
// this is.
return;
}
switch (try parser.elementHTMLGetTagType(@as(*parser.ElementHTML, @ptrCast(element)))) {
.script => {
var script = Script.init(element, self) catch |err| {
log.warn(.browser, "script added", .{ .err = err });
return;
} orelse return;
_ = self.evalScript(&script);
},
.undef => {
// a custom element
const js_obj = self.main_context.getJsObject(element) orelse return;
// @memory
// getFunction, and more generally Env.Function always create
// a Persisted Object. But, in cases like this, we don't need
// it to persist.
const func = (try js_obj.getFunction("connectedCallback")) orelse return;
var result: Env.Function.Result = undefined;
func.tryCallWithThis(void, js_obj, .{}, &result) catch {
log.warn(.user_script, "connected callback", .{
.err = result.exception,
.stack = result.stack,
});
};
},
else => {},
}
}
};
const DelayedNavigation = struct {
@@ -1128,22 +1076,24 @@ fn timestamp() u32 {
return @intCast(ts.sec);
}
// A callback from libdom whenever an html element is added to the DOM.
// Note that "added" could mean that it was removed from one parent and re-added
// to another, which is how MOST APIs implement "move" (corrently so).
//
// The only API which seems to actual "move" is Element.moveBefore, which we
// don't currently implement, but should support in general, and should handle
// specifically here.
// A callback from libdom whenever a script tag is added to the DOM.
// 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`
// 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
// 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.?));
self.elementAdded(element.?) catch |err| {
log.warn(.browser, "element added callback", .{ .err = err });
};
if (self.delayed_navigation) {
// 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 {
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;
_ = page_arena.reset(.{ .retain_with_limit = 1 * 1024 * 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);
const page = &self.page.?;
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
fn collectForm(form: *parser.Form, submitter_: ?*parser.ElementHTML, page: *Page) !kv.List {
const arena = page.arena;
// Don't use libdom's formGetCollection (aka dom_html_form_element_get_elements)
// It doesn't work with dynamically added elements, because their form
// property doesn't get set. We should fix that.
// However, even once fixed, there are other form-collection features we
// probably want to implement (like disabled fieldsets), so we might want
// to stick with our own walker even if fix libdom to properly support
// dynamically added elements.
const node_list = try @import("../dom/css.zig").querySelectorAll(arena, @alignCast(@ptrCast(form)), "input,select,button,textarea");
const nodes = node_list.nodes.items;
const collection = try parser.formGetCollection(form);
const len = try parser.htmlCollectionGetLength(collection);
var entries: kv.List = .{};
try entries.ensureTotalCapacity(arena, nodes.len);
try entries.ensureTotalCapacity(arena, len);
var submitter_included = false;
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);
// must have a name
@@ -188,7 +181,10 @@ fn collectForm(form: *parser.Form, submitter_: ?*parser.ElementHTML, page: *Page
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=image name=i1 value=i1-v>
\\ </form>
\\ <input type=text name=abc value=123 form=form1>
});
defer runner.deinit();
@@ -361,8 +356,6 @@ test "Browser.FormData" {
try runner.testCases(&.{
.{ "let form1 = document.getElementById('form1')", null },
.{ "let input = document.createElement('input');", null },
.{ "input.name = 'dyn'; input.value= 'dyn-v'; form1.appendChild(input);", null },
.{ "let submit1 = document.getElementById('s1')", null },
.{ "let f2 = new FormData(form1, submit1)", null },
.{ "acc = '';", null },
@@ -385,7 +378,6 @@ test "Browser.FormData" {
\\mlt-2=water
\\mlt-2=tea
\\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);
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 = {} };
return;
};

View File

@@ -23,6 +23,7 @@ const json = std.json;
const log = @import("../log.zig");
const App = @import("../app.zig").App;
const Env = @import("../browser/env.zig").Env;
const asUint = @import("../str/parser.zig").asUint;
const Browser = @import("../browser/browser.zig").Browser;
const Session = @import("../browser/session.zig").Session;
const Page = @import("../browser/page.zig").Page;
@@ -181,41 +182,41 @@ pub fn CDPT(comptime TypeProvider: type) type {
switch (domain.len) {
3 => switch (@as(u24, @bitCast(domain[0..3].*))) {
asUint(u24, "DOM") => return @import("domains/dom.zig").processMessage(command),
asUint(u24, "Log") => return @import("domains/log.zig").processMessage(command),
asUint(u24, "CSS") => return @import("domains/css.zig").processMessage(command),
asUint("DOM") => return @import("domains/dom.zig").processMessage(command),
asUint("Log") => return @import("domains/log.zig").processMessage(command),
asUint("CSS") => return @import("domains/css.zig").processMessage(command),
else => {},
},
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 => {},
},
5 => switch (@as(u40, @bitCast(domain[0..5].*))) {
asUint(u40, "Fetch") => return @import("domains/fetch.zig").processMessage(command),
asUint(u40, "Input") => return @import("domains/input.zig").processMessage(command),
asUint("Fetch") => return @import("domains/fetch.zig").processMessage(command),
asUint("Input") => return @import("domains/input.zig").processMessage(command),
else => {},
},
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 => {},
},
7 => switch (@as(u56, @bitCast(domain[0..7].*))) {
asUint(u56, "Browser") => return @import("domains/browser.zig").processMessage(command),
asUint(u56, "Runtime") => return @import("domains/runtime.zig").processMessage(command),
asUint(u56, "Network") => return @import("domains/network.zig").processMessage(command),
asUint("Browser") => return @import("domains/browser.zig").processMessage(command),
asUint("Runtime") => return @import("domains/runtime.zig").processMessage(command),
asUint("Network") => return @import("domains/network.zig").processMessage(command),
else => {},
},
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 => {},
},
9 => switch (@as(u72, @bitCast(domain[0..9].*))) {
asUint(u72, "Emulation") => return @import("domains/emulation.zig").processMessage(command),
asUint(u72, "Inspector") => return @import("domains/inspector.zig").processMessage(command),
asUint("Emulation") => return @import("domains/emulation.zig").processMessage(command),
asUint("Inspector") => return @import("domains/inspector.zig").processMessage(command),
else => {},
},
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 => {},
@@ -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");
test "cdp: invalid json" {
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();
for (0..5) |_| {
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 {
try logLogFmtPrefix(scope, level, msg, writer);
inline for (@typeInfo(@TypeOf(data)).@"struct".fields) |f| {
const key = " " ++ f.name ++ "=";
try writer.writeAll(key);
try writeValue(.logfmt, @field(data, f.name), writer);
}
try writer.writeByte('\n');
}
fn logLogFmtPrefix(comptime scope: Scope, level: Level, comptime msg: []const u8, writer: anytype) !void {
try writer.writeAll("$time=");
try writer.print("{d}", .{timestamp()});
@@ -174,20 +164,15 @@ fn logLogFmtPrefix(comptime scope: Scope, level: Level, comptime msg: []const u8
break :blk prefix ++ "\"" ++ msg ++ "\"";
};
try writer.writeAll(full_msg);
}
fn logPretty(comptime scope: Scope, level: Level, comptime msg: []const u8, data: anytype, writer: anytype) !void {
try logPrettyPrefix(scope, level, msg, writer);
inline for (@typeInfo(@TypeOf(data)).@"struct".fields) |f| {
const key = " " ++ f.name ++ "=";
try writer.writeAll(key);
try writeValue(.pretty, @field(data, f.name), writer);
try writer.writeByte('\n');
try writeValue(.logfmt, @field(data, f.name), writer);
}
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")) {
try writer.writeAll("\x1b[0;104mWARN ");
} 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.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 {

View File

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

View File

@@ -1158,7 +1158,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
}
if (!js_value.isArray()) {
return .{ .invalid = {} };
return error.InvalidArgument;
}
// This can get tricky.
@@ -1196,14 +1196,6 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
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
// the src of the module to load.
fn resolveModuleCallback(
@@ -1265,16 +1257,6 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
exception: []const u8,
};
pub fn getName(self: *const Function, allocator: Allocator) ![]const u8 {
const name = self.func.castToFunction().getName();
return valueToString(allocator, name, self.js_context.isolate, self.js_context.v8_context);
}
pub fn setName(self: *const Function, name: []const u8) void {
const v8_name = v8.String.initUtf8(self.js_context.isolate, name);
self.func.castToFunction().setName(v8_name);
}
pub fn withThis(self: *const Function, value: anytype) !Function {
const this_obj = if (@TypeOf(value) == JsObject)
value.js_obj
@@ -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 {
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),
};
}
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
@@ -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 {
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 {
@@ -1838,7 +1784,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
// a constructor function, we'll return an error.
if (@hasDecl(Struct, "constructor") == false) {
const iso = caller.isolate;
const js_exception = iso.throwException(createException(iso, "Illegal Constructor"));
const js_exception = iso.throwException(createException(iso, "illegal constructor"));
info.getReturnValue().set(js_exception);
return;
}
@@ -1930,7 +1876,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
defer caller.deinit();
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);
};
}
@@ -1945,13 +1891,13 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
const setter_callback = v8.FunctionTemplate.initCallback(isolate, struct {
fn callback(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
std.debug.assert(info.length() == 1);
var caller = Caller(Self, State).init(info);
defer caller.deinit();
std.debug.assert(info.length() == 1);
const js_value = info.getArg(0);
const named_function = comptime NamedFunction.init(Struct, "set_" ++ name);
caller.method(Struct, named_function, info) catch |err| {
caller.setter(Struct, named_function, js_value, info) catch |err| {
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));
}
fn getter(self: *Self, comptime Struct: type, comptime named_function: NamedFunction, info: v8.FunctionCallbackInfo) !void {
const js_context = self.js_context;
const func = @field(Struct, named_function.name);
const Getter = @TypeOf(func);
if (@typeInfo(Getter).@"fn".return_type == null) {
@compileError(@typeName(Struct) ++ " has a getter without a return type: " ++ @typeName(Getter));
}
var args: ParamterTypes(Getter) = undefined;
const arg_fields = @typeInfo(@TypeOf(args)).@"struct".fields;
switch (arg_fields.len) {
0 => {}, // getters _can_ be parameterless
1, 2 => {
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(Struct), info.getThis());
comptime assertSelfReceiver(Struct, named_function);
@field(args, "0") = zig_instance;
if (comptime arg_fields.len == 2) {
comptime assertIsStateArg(Struct, named_function, 1);
@field(args, "1") = js_context.state;
}
},
else => @compileError(named_function.full_name + " has too many parmaters: " ++ @typeName(named_function.func)),
}
const res = @call(.auto, func, args);
info.getReturnValue().set(try js_context.zigValueToJs(res));
}
fn setter(self: *Self, comptime Struct: type, comptime named_function: NamedFunction, js_value: v8.Value, info: v8.FunctionCallbackInfo) !void {
const js_context = self.js_context;
const func = @field(Struct, named_function.name);
comptime assertSelfReceiver(Struct, named_function);
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(Struct), info.getThis());
const Setter = @TypeOf(func);
var args: ParamterTypes(Setter) = undefined;
const arg_fields = @typeInfo(@TypeOf(args)).@"struct".fields;
switch (arg_fields.len) {
0 => unreachable, // assertSelfReceiver make sure of this
1 => @compileError(named_function.full_name ++ " only has 1 parameter"),
2, 3 => {
@field(args, "0") = zig_instance;
@field(args, "1") = try js_context.jsValueToZig(named_function, arg_fields[1].type, js_value);
if (comptime arg_fields.len == 3) {
comptime assertIsStateArg(Struct, named_function, 2);
@field(args, "2") = js_context.state;
}
},
else => @compileError(named_function.full_name ++ " setter with more than 3 parameters, why?"),
}
if (@typeInfo(Setter).@"fn".return_type) |return_type| {
if (@typeInfo(return_type) == .error_union) {
_ = try @call(.auto, func, args);
return;
}
}
_ = @call(.auto, func, args);
}
fn getIndex(self: *Self, comptime Struct: type, comptime named_function: NamedFunction, idx: u32, info: v8.PropertyCallbackInfo) !u8 {
const js_context = self.js_context;
const func = @field(Struct, named_function.name);
@@ -2590,14 +2596,19 @@ fn Caller(comptime E: type, comptime State: type) type {
if (comptime builtin.mode == .Debug and @hasDecl(@TypeOf(info), "length")) {
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) {
error.InvalidArgument => createTypeException(isolate, "invalid argument"),
error.OutOfMemory => createException(isolate, "out of memory"),
error.IllegalConstructor => createException(isolate, "Illegal Contructor"),
else => blk: {
const func = @field(Struct, named_function.name);
const return_type = @typeInfo(@TypeOf(func)).@"fn".return_type orelse {
@@ -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
fn isErrorSetException(comptime Exception: type, err: anytype) bool {
const Entry = std.meta.Tuple(&.{ []const u8, void });
const error_set = @typeInfo(Exception.ErrorSet).error_set.?;
const entries = comptime blk: {
var kv: [error_set.len]Entry = undefined;
@@ -2711,8 +2721,8 @@ fn Caller(comptime E: type, comptime State: type) type {
// a JS argument
if (comptime isJsThis(params[params.len - 1].type.?)) {
@field(args, std.fmt.comptimePrint("{d}", .{params.len - 1 + offset})) = .{ .obj = .{
.js_context = js_context,
.js_obj = info.getThis(),
.executor = self.executor,
} };
// AND the 2nd last parameter is state
@@ -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;
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
// it'll call this function to gets its [optional] subtype - which, from V8's
// 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,
pub fn init(html: []const u8) !Document {
var arena = std.heap.ArenaAllocator.init(allocator);
parser.deinit();
try parser.init();
try parser.init(arena.allocator());
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 .{
.arena = std.heap.ArenaAllocator.init(allocator),
.arena = arena,
.doc = html_doc,
};
}