mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-28 15:40:04 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ec0d0b84c | ||
|
|
3544e98871 | ||
|
|
446e5b2ddd | ||
|
|
faebabe3c7 | ||
|
|
02c510b07f | ||
|
|
63541970eb | ||
|
|
a8a5605fe1 | ||
|
|
0c0ddc10ee | ||
|
|
9bd5ff69ef | ||
|
|
eadf351e82 | ||
|
|
e3afa294af | ||
|
|
582894cdc3 | ||
|
|
2788c36ca6 | ||
|
|
872a9d393d | ||
|
|
b1ca242d89 | ||
|
|
97c769e805 | ||
|
|
0de33b36f8 | ||
|
|
cf39bdc7f7 | ||
|
|
34b49498c9 | ||
|
|
3a4bd00020 | ||
|
|
effd07d8c0 | ||
|
|
d9ce89ab31 | ||
|
|
5483c52227 | ||
|
|
2b48902f1b |
@@ -13,8 +13,8 @@
|
|||||||
.hash = "tigerbeetle_io-0.0.0-ViLgxpyRBAB5BMfIcj3KMXfbJzwARs9uSl8aRy2OXULd",
|
.hash = "tigerbeetle_io-0.0.0-ViLgxpyRBAB5BMfIcj3KMXfbJzwARs9uSl8aRy2OXULd",
|
||||||
},
|
},
|
||||||
.v8 = .{
|
.v8 = .{
|
||||||
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/faab44996a5cb74c71592bda404208fde4bf2e63.tar.gz",
|
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/1d25fcf3ced688adca3c7a95a138771e4ebba692.tar.gz",
|
||||||
.hash = "v8-0.0.0-xddH6xWyAwB_NFICSO4Q3O-c7gDKnYiwky5FhQzTZMIr",
|
.hash = "v8-0.0.0-xddH61eyAwDICIkLAkfQcxsX4TMCKY80QiSUgNBQqx-u",
|
||||||
},
|
},
|
||||||
//.v8 = .{ .path = "../zig-v8-fork" },
|
//.v8 = .{ .path = "../zig-v8-fork" },
|
||||||
//.tigerbeetle_io = .{ .path = "../tigerbeetle-io" },
|
//.tigerbeetle_io = .{ .path = "../tigerbeetle-io" },
|
||||||
|
|||||||
@@ -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| {
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ pub const CharacterData = struct {
|
|||||||
// netsurf's CharacterData (text, comment) doesn't implement the
|
// netsurf's CharacterData (text, comment) doesn't implement the
|
||||||
// dom_node_get_attributes and thus will crash if we try to call nodeIsEqualNode.
|
// dom_node_get_attributes and thus will crash if we try to call nodeIsEqualNode.
|
||||||
pub fn _isEqualNode(self: *parser.CharacterData, other_node: *parser.Node) !bool {
|
pub fn _isEqualNode(self: *parser.CharacterData, other_node: *parser.Node) !bool {
|
||||||
if (try parser.nodeType(@ptrCast(self)) != try parser.nodeType(other_node)) {
|
if (try parser.nodeType(@alignCast(@ptrCast(self))) != try parser.nodeType(other_node)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,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;
|
||||||
@@ -45,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.
|
||||||
@@ -243,17 +245,23 @@ pub const Document = struct {
|
|||||||
return try TreeWalker.init(root, what_to_show, filter);
|
return try TreeWalker.init(root, what_to_show, filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_activeElement(self: *parser.Document, page: *Page) !?ElementUnion {
|
pub fn getActiveElement(self: *parser.Document, page: *Page) !?*parser.Element {
|
||||||
const state = try page.getOrCreateNodeState(@ptrCast(self));
|
if (page.getNodeState(@alignCast(@ptrCast(self)))) |state| {
|
||||||
if (state.active_element) |ae| {
|
if (state.active_element) |ae| {
|
||||||
return try Element.toInterface(ae);
|
return ae;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (try parser.documentHTMLBody(page.window.document)) |body| {
|
if (try parser.documentHTMLBody(page.window.document)) |body| {
|
||||||
return try Element.toInterface(@ptrCast(body));
|
return @alignCast(@ptrCast(body));
|
||||||
}
|
}
|
||||||
|
|
||||||
return get_documentElement(self);
|
return try parser.documentGetDocumentElement(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_activeElement(self: *parser.Document, page: *Page) !?ElementUnion {
|
||||||
|
const ae = (try getActiveElement(self, page)) orelse return null;
|
||||||
|
return try Element.toInterface(ae);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: some elements can't be focused, like if they're disabled
|
// TODO: some elements can't be focused, like if they're disabled
|
||||||
@@ -261,7 +269,7 @@ pub const Document = struct {
|
|||||||
// we could look for the "disabled" attribute, but that's only meaningful
|
// we could look for the "disabled" attribute, but that's only meaningful
|
||||||
// on certain types, and libdom's vtable doesn't seem to expose this.
|
// on certain types, and libdom's vtable doesn't seem to expose this.
|
||||||
pub fn setFocus(self: *parser.Document, e: *parser.ElementHTML, page: *Page) !void {
|
pub fn setFocus(self: *parser.Document, e: *parser.ElementHTML, page: *Page) !void {
|
||||||
const state = try page.getOrCreateNodeState(@ptrCast(self));
|
const state = try page.getOrCreateNodeState(@alignCast(@ptrCast(self)));
|
||||||
state.active_element = @ptrCast(e);
|
state.active_element = @ptrCast(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ pub const MutationObserver = struct {
|
|||||||
cbk: Env.Function,
|
cbk: Env.Function,
|
||||||
arena: Allocator,
|
arena: Allocator,
|
||||||
|
|
||||||
// List of records which were observed. When the scopeEnds, we need to
|
// List of records which were observed. When the call scope ends, we need to
|
||||||
// execute our callback with it.
|
// execute our callback with it.
|
||||||
observed: std.ArrayListUnmanaged(*MutationRecord),
|
observed: std.ArrayListUnmanaged(*MutationRecord),
|
||||||
|
|
||||||
|
|||||||
@@ -496,7 +496,7 @@ pub const Node = struct {
|
|||||||
fn toNode(self: NodeOrText, doc: *parser.Document) !*parser.Node {
|
fn toNode(self: NodeOrText, doc: *parser.Document) !*parser.Node {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
.node => |n| n,
|
.node => |n| n,
|
||||||
.text => |txt| @ptrCast(try parser.documentCreateTextNode(doc, txt)),
|
.text => |txt| @alignCast(@ptrCast(try parser.documentCreateTextNode(doc, txt))),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ pub const HTMLDocument = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_readyState(self: *parser.DocumentHTML, page: *Page) ![]const u8 {
|
pub fn get_readyState(self: *parser.DocumentHTML, page: *Page) ![]const u8 {
|
||||||
const state = try page.getOrCreateNodeState(@ptrCast(self));
|
const state = try page.getOrCreateNodeState(@alignCast(@ptrCast(self)));
|
||||||
return @tagName(state.ready_state);
|
return @tagName(state.ready_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +263,7 @@ pub const HTMLDocument = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn documentIsLoaded(self: *parser.DocumentHTML, page: *Page) !void {
|
pub fn documentIsLoaded(self: *parser.DocumentHTML, page: *Page) !void {
|
||||||
const state = try page.getOrCreateNodeState(@ptrCast(self));
|
const state = try page.getOrCreateNodeState(@alignCast(@ptrCast(self)));
|
||||||
state.ready_state = .interactive;
|
state.ready_state = .interactive;
|
||||||
|
|
||||||
const evt = try parser.eventCreate();
|
const evt = try parser.eventCreate();
|
||||||
@@ -278,7 +278,7 @@ pub const HTMLDocument = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn documentIsComplete(self: *parser.DocumentHTML, page: *Page) !void {
|
pub fn documentIsComplete(self: *parser.DocumentHTML, page: *Page) !void {
|
||||||
const state = try page.getOrCreateNodeState(@ptrCast(self));
|
const state = try page.getOrCreateNodeState(@alignCast(@ptrCast(self)));
|
||||||
state.ready_state = .complete;
|
state.ready_state = .complete;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,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;
|
||||||
|
|
||||||
@@ -133,7 +134,7 @@ pub const HTMLElement = struct {
|
|||||||
try Node.removeChildren(n);
|
try Node.removeChildren(n);
|
||||||
|
|
||||||
// attach the text node.
|
// attach the text node.
|
||||||
_ = try parser.nodeAppendChild(n, @as(*parser.Node, @ptrCast(t)));
|
_ = try parser.nodeAppendChild(n, @as(*parser.Node, @alignCast(@ptrCast(t))));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _click(e: *parser.ElementHTML) !void {
|
pub fn _click(e: *parser.ElementHTML) !void {
|
||||||
@@ -245,7 +246,7 @@ pub const HTMLAnchorElement = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline fn url(self: *parser.Anchor, page: *Page) !URL {
|
inline fn url(self: *parser.Anchor, page: *Page) !URL {
|
||||||
return URL.constructor(.{ .element = @ptrCast(self) }, null, page); // TODO inject base url
|
return URL.constructor(.{ .element = @alignCast(@ptrCast(self)) }, null, page); // TODO inject base url
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO return a disposable string
|
// TODO return a disposable string
|
||||||
@@ -626,11 +627,85 @@ 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;
|
||||||
|
|
||||||
|
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, ©.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 {
|
||||||
return try parser.inputGetDefaultValue(self);
|
return try parser.inputGetDefaultValue(self);
|
||||||
}
|
}
|
||||||
@@ -702,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);
|
||||||
@@ -945,22 +1036,22 @@ pub const HTMLScriptElement = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_onload(self: *parser.Script, page: *Page) !?Env.Function {
|
pub fn get_onload(self: *parser.Script, page: *Page) !?Env.Function {
|
||||||
const state = page.getNodeState(@ptrCast(self)) orelse return null;
|
const state = page.getNodeState(@alignCast(@ptrCast(self))) orelse return null;
|
||||||
return state.onload;
|
return state.onload;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_onload(self: *parser.Script, function: ?Env.Function, page: *Page) !void {
|
pub fn set_onload(self: *parser.Script, function: ?Env.Function, page: *Page) !void {
|
||||||
const state = try page.getOrCreateNodeState(@ptrCast(self));
|
const state = try page.getOrCreateNodeState(@alignCast(@ptrCast(self)));
|
||||||
state.onload = function;
|
state.onload = function;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_onerror(self: *parser.Script, page: *Page) !?Env.Function {
|
pub fn get_onerror(self: *parser.Script, page: *Page) !?Env.Function {
|
||||||
const state = page.getNodeState(@ptrCast(self)) orelse return null;
|
const state = page.getNodeState(@alignCast(@ptrCast(self))) orelse return null;
|
||||||
return state.onerror;
|
return state.onerror;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_onerror(self: *parser.Script, function: ?Env.Function, page: *Page) !void {
|
pub fn set_onerror(self: *parser.Script, function: ?Env.Function, page: *Page) !void {
|
||||||
const state = try page.getOrCreateNodeState(@ptrCast(self));
|
const state = try page.getOrCreateNodeState(@alignCast(@ptrCast(self)));
|
||||||
state.onerror = function;
|
state.onerror = function;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1369,9 +1460,7 @@ test "Browser.HTML.HtmlInputElement.propeties.form" {
|
|||||||
|
|
||||||
try runner.testCases(&.{
|
try runner.testCases(&.{
|
||||||
.{ "let elem_input = document.querySelector('input')", null },
|
.{ "let elem_input = document.querySelector('input')", null },
|
||||||
}, .{});
|
.{ "elem_input.form", "[object HTMLFormElement]" }, // Initial value
|
||||||
try runner.testCases(&.{.{ "elem_input.form", "[object HTMLFormElement]" }}, .{}); // Initial value
|
|
||||||
try runner.testCases(&.{
|
|
||||||
.{ "elem_input.form = 'foo'", null },
|
.{ "elem_input.form = 'foo'", null },
|
||||||
.{ "elem_input.form", "[object HTMLFormElement]" }, // Invalid
|
.{ "elem_input.form", "[object HTMLFormElement]" }, // Invalid
|
||||||
}, .{});
|
}, .{});
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ pub const HTMLSelectElement = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_selectedIndex(select: *parser.Select, page: *Page) !i32 {
|
pub fn get_selectedIndex(select: *parser.Select, page: *Page) !i32 {
|
||||||
const state = try page.getOrCreateNodeState(@ptrCast(select));
|
const state = try page.getOrCreateNodeState(@alignCast(@ptrCast(select)));
|
||||||
const selected_index = try parser.selectGetSelectedIndex(select);
|
const selected_index = try parser.selectGetSelectedIndex(select);
|
||||||
|
|
||||||
// See the explicit_index_set field documentation
|
// See the explicit_index_set field documentation
|
||||||
@@ -75,7 +75,7 @@ pub const HTMLSelectElement = struct {
|
|||||||
// Libdom's dom_html_select_select_set_selected_index will crash if index
|
// Libdom's dom_html_select_select_set_selected_index will crash if index
|
||||||
// is out of range, and it doesn't properly unset options
|
// is out of range, and it doesn't properly unset options
|
||||||
pub fn set_selectedIndex(select: *parser.Select, index: i32, page: *Page) !void {
|
pub fn set_selectedIndex(select: *parser.Select, index: i32, page: *Page) !void {
|
||||||
var state = try page.getOrCreateNodeState(@ptrCast(select));
|
var state = try page.getOrCreateNodeState(@alignCast(@ptrCast(select)));
|
||||||
state.explicit_index_set = true;
|
state.explicit_index_set = true;
|
||||||
|
|
||||||
const options = try parser.selectGetOptions(select);
|
const options = try parser.selectGetOptions(select);
|
||||||
|
|||||||
@@ -61,7 +61,8 @@ pub const Window = struct {
|
|||||||
|
|
||||||
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");
|
||||||
|
|
||||||
|
|||||||
@@ -17,23 +17,29 @@
|
|||||||
// 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");
|
||||||
@cInclude("events/event_target.h");
|
@cInclude("events/event_target.h");
|
||||||
@cInclude("events/event.h");
|
@cInclude("events/event.h");
|
||||||
@cInclude("events/mouse_event.h");
|
@cInclude("events/mouse_event.h");
|
||||||
|
@cInclude("events/keyboard_event.h");
|
||||||
@cInclude("utils/validate.h");
|
@cInclude("utils/validate.h");
|
||||||
|
@cInclude("html/html_element.h");
|
||||||
|
@cInclude("html/html_document.h");
|
||||||
});
|
});
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +52,7 @@ pub fn deinit() void {
|
|||||||
c.lwc_deinit_strings();
|
c.lwc_deinit_strings();
|
||||||
|
|
||||||
mimalloc.destroy();
|
mimalloc.destroy();
|
||||||
|
ARENA = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vtable
|
// Vtable
|
||||||
@@ -550,7 +557,7 @@ pub fn mutationEventRelatedNode(evt: *MutationEvent) !?*Node {
|
|||||||
const err = c._dom_mutation_event_get_related_node(evt, &n);
|
const err = c._dom_mutation_event_get_related_node(evt, &n);
|
||||||
try DOMErr(err);
|
try DOMErr(err);
|
||||||
if (n == null) return null;
|
if (n == null) return null;
|
||||||
return @as(*Node, @ptrCast(n));
|
return @as(*Node, @alignCast(@ptrCast(n)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// EventListener
|
// EventListener
|
||||||
@@ -565,7 +572,7 @@ fn eventListenerGetData(lst: *EventListener) ?*anyopaque {
|
|||||||
pub const EventTarget = c.dom_event_target;
|
pub const EventTarget = c.dom_event_target;
|
||||||
|
|
||||||
pub fn eventTargetToNode(et: *EventTarget) *Node {
|
pub fn eventTargetToNode(et: *EventTarget) *Node {
|
||||||
return @as(*Node, @ptrCast(et));
|
return @as(*Node, @alignCast(@ptrCast(et)));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eventTargetVtable(et: *EventTarget) c.dom_event_target_vtable {
|
fn eventTargetVtable(et: *EventTarget) c.dom_event_target_vtable {
|
||||||
@@ -862,6 +869,59 @@ pub fn mouseEventDefaultPrevented(evt: *MouseEvent) !bool {
|
|||||||
return eventDefaultPrevented(@ptrCast(evt));
|
return eventDefaultPrevented(@ptrCast(evt));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KeyboardEvent
|
||||||
|
|
||||||
|
pub const KeyboardEvent = c.dom_keyboard_event;
|
||||||
|
|
||||||
|
pub fn keyboardEventCreate() !*KeyboardEvent {
|
||||||
|
var evt: ?*KeyboardEvent = undefined;
|
||||||
|
const err = c._dom_keyboard_event_create(&evt);
|
||||||
|
try DOMErr(err);
|
||||||
|
return evt.?;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn keyboardEventDestroy(evt: *KeyboardEvent) void {
|
||||||
|
c._dom_keyboard_event_destroy(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
const KeyboardEventOpts = struct {
|
||||||
|
key: []const u8,
|
||||||
|
code: []const u8,
|
||||||
|
bubbles: bool = false,
|
||||||
|
cancelable: bool = false,
|
||||||
|
ctrl: bool = false,
|
||||||
|
alt: bool = false,
|
||||||
|
shift: bool = false,
|
||||||
|
meta: bool = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn keyboardEventInit(evt: *KeyboardEvent, typ: []const u8, opts: KeyboardEventOpts) !void {
|
||||||
|
const s = try strFromData(typ);
|
||||||
|
const err = c._dom_keyboard_event_init(
|
||||||
|
evt,
|
||||||
|
s,
|
||||||
|
opts.bubbles,
|
||||||
|
opts.cancelable,
|
||||||
|
null, // dom_abstract_view* ?
|
||||||
|
try strFromData(opts.key),
|
||||||
|
try strFromData(opts.code),
|
||||||
|
0, // location 0 == standard
|
||||||
|
opts.ctrl,
|
||||||
|
opts.shift,
|
||||||
|
opts.alt,
|
||||||
|
opts.meta,
|
||||||
|
false, // repease
|
||||||
|
false, // is_composiom
|
||||||
|
);
|
||||||
|
try DOMErr(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn keyboardEventGetKey(evt: *KeyboardEvent) ![]const u8 {
|
||||||
|
var s: ?*String = undefined;
|
||||||
|
_ = c._dom_keyboard_event_get_key(evt, &s);
|
||||||
|
return strToData(s.?);
|
||||||
|
}
|
||||||
|
|
||||||
// NodeType
|
// NodeType
|
||||||
|
|
||||||
pub const NodeType = enum(u4) {
|
pub const NodeType = enum(u4) {
|
||||||
@@ -894,7 +954,7 @@ pub fn nodeListItem(nodeList: *NodeList, index: u32) !?*Node {
|
|||||||
const err = c._dom_nodelist_item(nodeList, index, &n);
|
const err = c._dom_nodelist_item(nodeList, index, &n);
|
||||||
try DOMErr(err);
|
try DOMErr(err);
|
||||||
if (n == null) return null;
|
if (n == null) return null;
|
||||||
return @as(*Node, @ptrCast(n));
|
return @as(*Node, @alignCast(@ptrCast(n)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeExternal is the libdom public representation of a Node.
|
// NodeExternal is the libdom public representation of a Node.
|
||||||
@@ -1301,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));
|
||||||
@@ -1323,7 +1387,7 @@ fn characterDataVtable(data: *CharacterData) c.dom_characterdata_vtable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn characterDataToNode(cdata: *CharacterData) *Node {
|
pub inline fn characterDataToNode(cdata: *CharacterData) *Node {
|
||||||
return @as(*Node, @ptrCast(cdata));
|
return @as(*Node, @alignCast(@ptrCast(cdata)));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn characterDataData(cdata: *CharacterData) ![]const u8 {
|
pub fn characterDataData(cdata: *CharacterData) ![]const u8 {
|
||||||
@@ -1408,7 +1472,7 @@ pub const ProcessingInstruction = c.dom_processing_instruction;
|
|||||||
|
|
||||||
// processingInstructionToNode is an helper to convert an ProcessingInstruction to a node.
|
// processingInstructionToNode is an helper to convert an ProcessingInstruction to a node.
|
||||||
pub inline fn processingInstructionToNode(pi: *ProcessingInstruction) *Node {
|
pub inline fn processingInstructionToNode(pi: *ProcessingInstruction) *Node {
|
||||||
return @as(*Node, @ptrCast(pi));
|
return @as(*Node, @alignCast(@ptrCast(pi)));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn processInstructionCopy(pi: *ProcessingInstruction) !*ProcessingInstruction {
|
pub fn processInstructionCopy(pi: *ProcessingInstruction) !*ProcessingInstruction {
|
||||||
@@ -1463,7 +1527,7 @@ pub fn attributeGetOwnerElement(a: *Attribute) !?*Element {
|
|||||||
|
|
||||||
// attributeToNode is an helper to convert an attribute to a node.
|
// attributeToNode is an helper to convert an attribute to a node.
|
||||||
pub inline fn attributeToNode(a: *Attribute) *Node {
|
pub inline fn attributeToNode(a: *Attribute) *Node {
|
||||||
return @as(*Node, @ptrCast(a));
|
return @as(*Node, @alignCast(@ptrCast(a)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Element
|
// Element
|
||||||
@@ -1601,7 +1665,7 @@ pub fn elementHasClass(elem: *Element, class: []const u8) !bool {
|
|||||||
|
|
||||||
// elementToNode is an helper to convert an element to a node.
|
// elementToNode is an helper to convert an element to a node.
|
||||||
pub inline fn elementToNode(e: *Element) *Node {
|
pub inline fn elementToNode(e: *Element) *Node {
|
||||||
return @as(*Node, @ptrCast(e));
|
return @as(*Node, @alignCast(@ptrCast(e)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TokenList
|
// TokenList
|
||||||
@@ -1685,14 +1749,14 @@ pub fn elementHTMLGetTagType(elem_html: *ElementHTML) !Tag {
|
|||||||
|
|
||||||
// scriptToElt is an helper to convert an script to an element.
|
// scriptToElt is an helper to convert an script to an element.
|
||||||
pub inline fn scriptToElt(s: *Script) *Element {
|
pub inline fn scriptToElt(s: *Script) *Element {
|
||||||
return @as(*Element, @ptrCast(s));
|
return @as(*Element, @alignCast(@ptrCast(s)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTMLAnchorElement
|
// HTMLAnchorElement
|
||||||
|
|
||||||
// anchorToNode is an helper to convert an anchor to a node.
|
// anchorToNode is an helper to convert an anchor to a node.
|
||||||
pub inline fn anchorToNode(a: *Anchor) *Node {
|
pub inline fn anchorToNode(a: *Anchor) *Node {
|
||||||
return @as(*Node, @ptrCast(a));
|
return @as(*Node, @alignCast(@ptrCast(a)));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn anchorGetTarget(a: *Anchor) ![]const u8 {
|
pub fn anchorGetTarget(a: *Anchor) ![]const u8 {
|
||||||
@@ -1837,7 +1901,7 @@ pub const OptionCollection = c.dom_html_options_collection;
|
|||||||
pub const DocumentFragment = c.dom_document_fragment;
|
pub const DocumentFragment = c.dom_document_fragment;
|
||||||
|
|
||||||
pub inline fn documentFragmentToNode(doc: *DocumentFragment) *Node {
|
pub inline fn documentFragmentToNode(doc: *DocumentFragment) *Node {
|
||||||
return @as(*Node, @ptrCast(doc));
|
return @as(*Node, @alignCast(@ptrCast(doc)));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn documentFragmentBodyChildren(doc: *DocumentFragment) !?*NodeList {
|
pub fn documentFragmentBodyChildren(doc: *DocumentFragment) !?*NodeList {
|
||||||
@@ -1933,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.
|
||||||
@@ -1947,7 +2013,7 @@ pub inline fn domImplementationCreateHTMLDocument(title: ?[]const u8) !*Document
|
|||||||
if (title) |t| {
|
if (title) |t| {
|
||||||
const htitle = try documentCreateElement(doc, "title");
|
const htitle = try documentCreateElement(doc, "title");
|
||||||
const txt = try documentCreateTextNode(doc, t);
|
const txt = try documentCreateTextNode(doc, t);
|
||||||
_ = try nodeAppendChild(elementToNode(htitle), @as(*Node, @ptrCast(txt)));
|
_ = try nodeAppendChild(elementToNode(htitle), @as(*Node, @alignCast(@ptrCast(txt))));
|
||||||
_ = try nodeAppendChild(elementToNode(head), elementToNode(htitle));
|
_ = try nodeAppendChild(elementToNode(head), elementToNode(htitle));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1965,7 +2031,7 @@ fn documentVtable(doc: *Document) c.dom_document_vtable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn documentToNode(doc: *Document) *Node {
|
pub inline fn documentToNode(doc: *Document) *Node {
|
||||||
return @as(*Node, @ptrCast(doc));
|
return @as(*Node, @alignCast(@ptrCast(doc)));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn documentGetElementById(doc: *Document, id: []const u8) !?*Element {
|
pub inline fn documentGetElementById(doc: *Document, id: []const u8) !?*Element {
|
||||||
@@ -2015,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,
|
||||||
@@ -2029,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2103,7 +2172,7 @@ pub inline fn documentImportNode(doc: *Document, node: *Node, deep: bool) !*Node
|
|||||||
const nodeext = toNodeExternal(Node, node);
|
const nodeext = toNodeExternal(Node, node);
|
||||||
const err = documentVtable(doc).dom_document_import_node.?(doc, nodeext, deep, &res);
|
const err = documentVtable(doc).dom_document_import_node.?(doc, nodeext, deep, &res);
|
||||||
try DOMErr(err);
|
try DOMErr(err);
|
||||||
return @as(*Node, @ptrCast(res));
|
return @as(*Node, @alignCast(@ptrCast(res)));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn documentAdoptNode(doc: *Document, node: *Node) !*Node {
|
pub inline fn documentAdoptNode(doc: *Document, node: *Node) !*Node {
|
||||||
@@ -2111,7 +2180,7 @@ pub inline fn documentAdoptNode(doc: *Document, node: *Node) !*Node {
|
|||||||
const nodeext = toNodeExternal(Node, node);
|
const nodeext = toNodeExternal(Node, node);
|
||||||
const err = documentVtable(doc).dom_document_adopt_node.?(doc, nodeext, &res);
|
const err = documentVtable(doc).dom_document_adopt_node.?(doc, nodeext, &res);
|
||||||
try DOMErr(err);
|
try DOMErr(err);
|
||||||
return @as(*Node, @ptrCast(res));
|
return @as(*Node, @alignCast(@ptrCast(res)));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn documentCreateAttribute(doc: *Document, name: []const u8) !*Attribute {
|
pub inline fn documentCreateAttribute(doc: *Document, name: []const u8) !*Attribute {
|
||||||
@@ -2146,7 +2215,7 @@ pub const DocumentHTML = c.dom_html_document;
|
|||||||
|
|
||||||
// documentHTMLToNode is an helper to convert a documentHTML to an node.
|
// documentHTMLToNode is an helper to convert a documentHTML to an node.
|
||||||
pub inline fn documentHTMLToNode(doc: *DocumentHTML) *Node {
|
pub inline fn documentHTMLToNode(doc: *DocumentHTML) *Node {
|
||||||
return @as(*Node, @ptrCast(doc));
|
return @as(*Node, @alignCast(@ptrCast(doc)));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn documentHTMLVtable(doc_html: *DocumentHTML) c.dom_html_document_vtable {
|
fn documentHTMLVtable(doc_html: *DocumentHTML) c.dom_html_document_vtable {
|
||||||
@@ -2192,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(¶ms, &parser, &doc);
|
err = c.dom_hubbub_parser_create(¶ms, &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 {
|
||||||
@@ -2291,7 +2362,7 @@ pub inline fn documentHTMLBody(doc_html: *DocumentHTML) !?*Body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn bodyToElement(body: *Body) *Element {
|
pub inline fn bodyToElement(body: *Body) *Element {
|
||||||
return @as(*Element, @ptrCast(body));
|
return @as(*Element, @alignCast(@ptrCast(body)));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn documentHTMLSetBody(doc_html: *DocumentHTML, elt: ?*ElementHTML) !void {
|
pub inline fn documentHTMLSetBody(doc_html: *DocumentHTML, elt: ?*ElementHTML) !void {
|
||||||
@@ -2330,7 +2401,7 @@ pub inline fn documentHTMLSetTitle(doc: *DocumentHTML, v: []const u8) !void {
|
|||||||
|
|
||||||
pub fn documentHTMLSetCurrentScript(doc: *DocumentHTML, script: ?*Script) !void {
|
pub fn documentHTMLSetCurrentScript(doc: *DocumentHTML, script: ?*Script) !void {
|
||||||
var s: ?*ElementHTML = null;
|
var s: ?*ElementHTML = null;
|
||||||
if (script != null) s = @ptrCast(script.?);
|
if (script != null) s = @alignCast(@ptrCast(script.?));
|
||||||
const err = documentHTMLVtable(doc).set_current_script.?(doc, s);
|
const err = documentHTMLVtable(doc).set_current_script.?(doc, s);
|
||||||
try DOMErr(err);
|
try DOMErr(err);
|
||||||
}
|
}
|
||||||
@@ -2391,6 +2462,11 @@ pub fn textareaGetValue(textarea: *TextArea) ![]const u8 {
|
|||||||
return strToData(s);
|
return strToData(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn textareaSetValue(textarea: *TextArea, value: []const u8) !void {
|
||||||
|
const err = c.dom_html_text_area_element_set_value(textarea, try strFromData(value));
|
||||||
|
try DOMErr(err);
|
||||||
|
}
|
||||||
|
|
||||||
// Select
|
// Select
|
||||||
pub fn selectGetOptions(select: *Select) !*OptionCollection {
|
pub fn selectGetOptions(select: *Select) !*OptionCollection {
|
||||||
var collection: ?*OptionCollection = null;
|
var collection: ?*OptionCollection = null;
|
||||||
@@ -2759,7 +2835,7 @@ pub fn inputSetType(input: *Input, type_: []const u8) !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const new_type = if (found) type_ else "text";
|
const new_type = if (found) type_ else "text";
|
||||||
try elementSetAttribute(@ptrCast(input), "type", new_type);
|
try elementSetAttribute(@alignCast(@ptrCast(input)), "type", new_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inputGetValue(input: *Input) ![]const u8 {
|
pub fn inputGetValue(input: *Input) ![]const u8 {
|
||||||
@@ -2773,3 +2849,11 @@ pub fn inputSetValue(input: *Input, value: []const u8) !void {
|
|||||||
const err = c.dom_html_input_element_set_value(input, try strFromData(value));
|
const err = c.dom_html_input_element_set_value(input, try strFromData(value));
|
||||||
try DOMErr(err);
|
try DOMErr(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn buttonGetType(button: *Button) ![]const u8 {
|
||||||
|
var s_: ?*String = null;
|
||||||
|
const err = c.dom_html_button_element_get_type(button, &s_);
|
||||||
|
try DOMErr(err);
|
||||||
|
const s = s_ orelse return "button";
|
||||||
|
return strToData(s);
|
||||||
|
}
|
||||||
|
|||||||
@@ -80,11 +80,12 @@ pub const Page = struct {
|
|||||||
|
|
||||||
microtask_node: Loop.CallbackNode,
|
microtask_node: Loop.CallbackNode,
|
||||||
|
|
||||||
|
keydown_event_node: parser.EventNode,
|
||||||
window_clicked_event_node: parser.EventNode,
|
window_clicked_event_node: parser.EventNode,
|
||||||
|
|
||||||
// Our JavaScript context for this specific page. This is what we use to
|
// Our JavaScript context for this specific page. This is what we use to
|
||||||
// execute any JavaScript
|
// execute any JavaScript
|
||||||
scope: *Env.Scope,
|
main_context: *Env.JsContext,
|
||||||
|
|
||||||
// List of modules currently fetched/loaded.
|
// List of modules currently fetched/loaded.
|
||||||
module_map: std.StringHashMapUnmanaged([]const u8),
|
module_map: std.StringHashMapUnmanaged([]const u8),
|
||||||
@@ -112,17 +113,18 @@ pub const Page = struct {
|
|||||||
.state_pool = &browser.state_pool,
|
.state_pool = &browser.state_pool,
|
||||||
.cookie_jar = &session.cookie_jar,
|
.cookie_jar = &session.cookie_jar,
|
||||||
.microtask_node = .{ .func = microtaskCallback },
|
.microtask_node = .{ .func = microtaskCallback },
|
||||||
|
.keydown_event_node = .{ .func = keydownCallback },
|
||||||
.window_clicked_event_node = .{ .func = windowClicked },
|
.window_clicked_event_node = .{ .func = windowClicked },
|
||||||
.request_factory = browser.http_client.requestFactory(.{
|
.request_factory = browser.http_client.requestFactory(.{
|
||||||
.notification = browser.notification,
|
.notification = browser.notification,
|
||||||
}),
|
}),
|
||||||
.scope = undefined,
|
.main_context = undefined,
|
||||||
.module_map = .empty,
|
.module_map = .empty,
|
||||||
};
|
};
|
||||||
self.scope = try session.executor.startScope(&self.window, self, self, true);
|
self.main_context = try session.executor.createJsContext(&self.window, self, self, true);
|
||||||
|
|
||||||
// load polyfills
|
// load polyfills
|
||||||
try polyfill.load(self.arena, self.scope);
|
try polyfill.load(self.arena, self.main_context);
|
||||||
|
|
||||||
_ = try session.browser.app.loop.timeout(1 * std.time.ns_per_ms, &self.microtask_node);
|
_ = try session.browser.app.loop.timeout(1 * std.time.ns_per_ms, &self.microtask_node);
|
||||||
}
|
}
|
||||||
@@ -164,7 +166,7 @@ pub const Page = struct {
|
|||||||
|
|
||||||
pub fn wait(self: *Page) !void {
|
pub fn wait(self: *Page) !void {
|
||||||
var try_catch: Env.TryCatch = undefined;
|
var try_catch: Env.TryCatch = undefined;
|
||||||
try_catch.init(self.scope);
|
try_catch.init(self.main_context);
|
||||||
defer try_catch.deinit();
|
defer try_catch.deinit();
|
||||||
|
|
||||||
try self.session.browser.app.loop.run();
|
try self.session.browser.app.loop.run();
|
||||||
@@ -190,7 +192,12 @@ pub const Page = struct {
|
|||||||
const session = self.session;
|
const session = self.session;
|
||||||
const notification = session.browser.notification;
|
const notification = session.browser.notification;
|
||||||
|
|
||||||
log.debug(.http, "navigate", .{ .url = request_url, .reason = opts.reason });
|
log.debug(.http, "navigate", .{
|
||||||
|
.url = request_url,
|
||||||
|
.method = opts.method,
|
||||||
|
.reason = opts.reason,
|
||||||
|
.body = opts.body != null,
|
||||||
|
});
|
||||||
|
|
||||||
// if the url is about:blank, nothing to do.
|
// if the url is about:blank, nothing to do.
|
||||||
if (std.mem.eql(u8, "about:blank", request_url.raw)) {
|
if (std.mem.eql(u8, "about:blank", request_url.raw)) {
|
||||||
@@ -247,6 +254,8 @@ pub const Page = struct {
|
|||||||
.content_type = content_type,
|
.content_type = content_type,
|
||||||
.charset = mime.charset,
|
.charset = mime.charset,
|
||||||
.url = request_url,
|
.url = request_url,
|
||||||
|
.method = opts.method,
|
||||||
|
.reason = opts.reason,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!mime.isHTML()) {
|
if (!mime.isHTML()) {
|
||||||
@@ -277,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.
|
||||||
@@ -305,6 +315,12 @@ pub const Page = struct {
|
|||||||
&self.window_clicked_event_node,
|
&self.window_clicked_event_node,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
_ = try parser.eventTargetAddEventListener(
|
||||||
|
parser.toEventTarget(parser.Element, document_element),
|
||||||
|
"keydown",
|
||||||
|
&self.keydown_event_node,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/#read-html
|
// https://html.spec.whatwg.org/#read-html
|
||||||
|
|
||||||
@@ -373,11 +389,15 @@ pub const Page = struct {
|
|||||||
// > immediately before the browser continues to parse the
|
// > immediately before the browser continues to parse the
|
||||||
// > page.
|
// > page.
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#notes
|
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#notes
|
||||||
self.evalScript(&script);
|
if (self.evalScript(&script) == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (defer_scripts.items) |*script| {
|
for (defer_scripts.items) |*script| {
|
||||||
self.evalScript(script);
|
if (self.evalScript(script) == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// dispatch DOMContentLoaded before the transition to "complete",
|
// dispatch DOMContentLoaded before the transition to "complete",
|
||||||
// at the point where all subresources apart from async script elements
|
// at the point where all subresources apart from async script elements
|
||||||
@@ -387,7 +407,9 @@ pub const Page = struct {
|
|||||||
|
|
||||||
// eval async scripts.
|
// eval async scripts.
|
||||||
for (async_scripts.items) |*script| {
|
for (async_scripts.items) |*script| {
|
||||||
self.evalScript(script);
|
if (self.evalScript(script) == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try HTMLDocument.documentIsComplete(html_doc, self);
|
try HTMLDocument.documentIsComplete(html_doc, self);
|
||||||
@@ -404,10 +426,13 @@ pub const Page = struct {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn evalScript(self: *Page, script: *const Script) void {
|
fn evalScript(self: *Page, script: *const Script) bool {
|
||||||
self.tryEvalScript(script) catch |err| {
|
self.tryEvalScript(script) catch |err| switch (err) {
|
||||||
log.err(.js, "eval script error", .{ .err = err, .src = script.src });
|
error.JsErr => {}, // already been logged with detail
|
||||||
|
error.Terminated => return false,
|
||||||
|
else => log.err(.js, "eval script error", .{ .err = err, .src = script.src }),
|
||||||
};
|
};
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// evalScript evaluates the src in priority.
|
// evalScript evaluates the src in priority.
|
||||||
@@ -421,29 +446,26 @@ pub const Page = struct {
|
|||||||
log.err(.browser, "clear document script", .{ .err = err });
|
log.err(.browser, "clear document script", .{ .err = err });
|
||||||
};
|
};
|
||||||
|
|
||||||
const src = script.src orelse {
|
var script_source: ?[]const u8 = null;
|
||||||
|
if (script.src) |src| {
|
||||||
|
self.current_script = script;
|
||||||
|
defer self.current_script = null;
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-classic-script
|
||||||
|
script_source = (try self.fetchData(src, null)) orelse {
|
||||||
|
// TODO If el's result is null, then fire an event named error at
|
||||||
|
// el, and return
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
} else {
|
||||||
// source is inline
|
// source is inline
|
||||||
// TODO handle charset attribute
|
// TODO handle charset attribute
|
||||||
if (try parser.nodeTextContent(parser.elementToNode(script.element))) |text| {
|
script_source = try parser.nodeTextContent(parser.elementToNode(script.element));
|
||||||
try script.eval(self, text);
|
}
|
||||||
}
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.current_script = script;
|
if (script_source) |ss| {
|
||||||
defer self.current_script = null;
|
try script.eval(self, ss);
|
||||||
|
}
|
||||||
// https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-classic-script
|
|
||||||
const body = (try self.fetchData(src, null)) orelse {
|
|
||||||
// TODO If el's result is null, then fire an event named error at
|
|
||||||
// el, and return
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
script.eval(self, body) catch |err| switch (err) {
|
|
||||||
error.JsErr => {}, // nothing to do here.
|
|
||||||
else => return err,
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO If el's from an external file is true, then fire an event
|
// TODO If el's from an external file is true, then fire an event
|
||||||
// named load at el.
|
// named load at el.
|
||||||
@@ -572,14 +594,14 @@ pub const Page = struct {
|
|||||||
},
|
},
|
||||||
.input => {
|
.input => {
|
||||||
const element: *parser.Element = @ptrCast(node);
|
const element: *parser.Element = @ptrCast(node);
|
||||||
const input_type = (try parser.elementGetAttribute(element, "type")) orelse return;
|
const input_type = try parser.inputGetType(@ptrCast(element));
|
||||||
if (std.ascii.eqlIgnoreCase(input_type, "submit")) {
|
if (std.ascii.eqlIgnoreCase(input_type, "submit")) {
|
||||||
return self.elementSubmitForm(element);
|
return self.elementSubmitForm(element);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.button => {
|
.button => {
|
||||||
const element: *parser.Element = @ptrCast(node);
|
const element: *parser.Element = @ptrCast(node);
|
||||||
const button_type = (try parser.elementGetAttribute(element, "type")) orelse return;
|
const button_type = try parser.buttonGetType(@ptrCast(element));
|
||||||
if (std.ascii.eqlIgnoreCase(button_type, "submit")) {
|
if (std.ascii.eqlIgnoreCase(button_type, "submit")) {
|
||||||
return self.elementSubmitForm(element);
|
return self.elementSubmitForm(element);
|
||||||
}
|
}
|
||||||
@@ -593,18 +615,111 @@ pub const Page = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const KeyboardEvent = struct {
|
||||||
|
type: Type,
|
||||||
|
key: []const u8,
|
||||||
|
code: []const u8,
|
||||||
|
alt: bool,
|
||||||
|
ctrl: bool,
|
||||||
|
meta: bool,
|
||||||
|
shift: bool,
|
||||||
|
|
||||||
|
const Type = enum {
|
||||||
|
keydown,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn keyboardEvent(self: *Page, kbe: KeyboardEvent) !void {
|
||||||
|
if (kbe.type != .keydown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Document = @import("dom/document.zig").Document;
|
||||||
|
const element = (try Document.getActiveElement(@ptrCast(self.window.document), self)) orelse return;
|
||||||
|
|
||||||
|
const event = try parser.keyboardEventCreate();
|
||||||
|
defer parser.keyboardEventDestroy(event);
|
||||||
|
try parser.keyboardEventInit(event, "keydown", .{
|
||||||
|
.bubbles = true,
|
||||||
|
.cancelable = true,
|
||||||
|
.key = kbe.key,
|
||||||
|
.code = kbe.code,
|
||||||
|
.alt = kbe.alt,
|
||||||
|
.ctrl = kbe.ctrl,
|
||||||
|
.meta = kbe.meta,
|
||||||
|
.shift = kbe.shift,
|
||||||
|
});
|
||||||
|
_ = try parser.elementDispatchEvent(element, @ptrCast(event));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn keydownCallback(node: *parser.EventNode, event: *parser.Event) void {
|
||||||
|
const self: *Page = @fieldParentPtr("keydown_event_node", node);
|
||||||
|
self._keydownCallback(event) catch |err| {
|
||||||
|
log.err(.browser, "keydown handler error", .{ .err = err });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _keydownCallback(self: *Page, event: *parser.Event) !void {
|
||||||
|
const target = (try parser.eventTarget(event)) orelse return;
|
||||||
|
const node = parser.eventTargetToNode(target);
|
||||||
|
const tag = (try parser.nodeHTMLGetTagType(node)) orelse return;
|
||||||
|
|
||||||
|
const kbe: *parser.KeyboardEvent = @ptrCast(event);
|
||||||
|
var new_key = try parser.keyboardEventGetKey(kbe);
|
||||||
|
if (std.mem.eql(u8, new_key, "Dead")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (tag) {
|
||||||
|
.input => {
|
||||||
|
const element: *parser.Element = @ptrCast(node);
|
||||||
|
const input_type = try parser.inputGetType(@ptrCast(element));
|
||||||
|
if (std.mem.eql(u8, input_type, "text")) {
|
||||||
|
if (std.mem.eql(u8, new_key, "Enter")) {
|
||||||
|
const form = (try self.formForElement(element)) orelse return;
|
||||||
|
return self.submitForm(@ptrCast(form), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = try parser.inputGetValue(@ptrCast(element));
|
||||||
|
const new_value = try std.mem.concat(self.arena, u8, &.{ value, new_key });
|
||||||
|
try parser.inputSetValue(@ptrCast(element), new_value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.textarea => {
|
||||||
|
const value = try parser.textareaGetValue(@ptrCast(node));
|
||||||
|
if (std.mem.eql(u8, new_key, "Enter")) {
|
||||||
|
new_key = "\n";
|
||||||
|
}
|
||||||
|
const new_value = try std.mem.concat(self.arena, u8, &.{ value, new_key });
|
||||||
|
try parser.textareaSetValue(@ptrCast(node), new_value);
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We cannot navigate immediately as navigating will delete the DOM tree,
|
||||||
|
// which holds this event's node.
|
||||||
// As such we schedule the function to be called as soon as possible.
|
// As such we schedule the function to be called as soon as possible.
|
||||||
// The page.arena is safe to use here, but the transfer_arena exists
|
// The page.arena is safe to use here, but the transfer_arena exists
|
||||||
// specifically for this type of lifetime.
|
// specifically for this type of lifetime.
|
||||||
pub fn navigateFromWebAPI(self: *Page, url: []const u8, opts: NavigateOpts) !void {
|
pub fn navigateFromWebAPI(self: *Page, url: []const u8, opts: NavigateOpts) !void {
|
||||||
|
log.debug(.browser, "delayed navigation", .{
|
||||||
|
.url = url,
|
||||||
|
.reason = opts.reason,
|
||||||
|
});
|
||||||
self.delayed_navigation = true;
|
self.delayed_navigation = true;
|
||||||
const arena = self.session.transfer_arena;
|
|
||||||
|
const session = self.session;
|
||||||
|
const arena = session.transfer_arena;
|
||||||
const navi = try arena.create(DelayedNavigation);
|
const navi = try arena.create(DelayedNavigation);
|
||||||
navi.* = .{
|
navi.* = .{
|
||||||
.opts = opts,
|
.opts = opts,
|
||||||
.session = self.session,
|
.session = session,
|
||||||
.url = try arena.dupe(u8, url),
|
.url = try self.url.resolve(arena, url),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// In v8, this throws an exception which JS code cannot catch.
|
||||||
|
session.executor.terminateExecution();
|
||||||
_ = try self.loop.timeout(0, &navi.navigate_node);
|
_ = try self.loop.timeout(0, &navi.navigate_node);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -633,13 +748,13 @@ pub const Page = struct {
|
|||||||
const transfer_arena = self.session.transfer_arena;
|
const transfer_arena = self.session.transfer_arena;
|
||||||
var form_data = try FormData.fromForm(form, submitter, self);
|
var form_data = try FormData.fromForm(form, submitter, self);
|
||||||
|
|
||||||
const encoding = try parser.elementGetAttribute(@ptrCast(form), "enctype");
|
const encoding = try parser.elementGetAttribute(@alignCast(@ptrCast(form)), "enctype");
|
||||||
|
|
||||||
var buf: std.ArrayListUnmanaged(u8) = .empty;
|
var buf: std.ArrayListUnmanaged(u8) = .empty;
|
||||||
try form_data.write(encoding, buf.writer(transfer_arena));
|
try form_data.write(encoding, buf.writer(transfer_arena));
|
||||||
|
|
||||||
const method = try parser.elementGetAttribute(@ptrCast(form), "method") orelse "";
|
const method = try parser.elementGetAttribute(@alignCast(@ptrCast(form)), "method") orelse "";
|
||||||
var action = try parser.elementGetAttribute(@ptrCast(form), "action") orelse self.url.raw;
|
var action = try parser.elementGetAttribute(@alignCast(@ptrCast(form)), "action") orelse self.url.raw;
|
||||||
|
|
||||||
var opts = NavigateOpts{
|
var opts = NavigateOpts{
|
||||||
.reason = .form,
|
.reason = .form,
|
||||||
@@ -650,7 +765,6 @@ pub const Page = struct {
|
|||||||
} else {
|
} else {
|
||||||
action = try URL.concatQueryString(transfer_arena, action, buf.items);
|
action = try URL.concatQueryString(transfer_arena, action, buf.items);
|
||||||
}
|
}
|
||||||
|
|
||||||
try self.navigateFromWebAPI(action, opts);
|
try self.navigateFromWebAPI(action, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -685,22 +799,89 @@ pub const Page = struct {
|
|||||||
|
|
||||||
pub fn stackTrace(self: *Page) !?[]const u8 {
|
pub fn stackTrace(self: *Page) !?[]const u8 {
|
||||||
if (comptime builtin.mode == .Debug) {
|
if (comptime builtin.mode == .Debug) {
|
||||||
return self.scope.stackTrace();
|
return self.main_context.stackTrace();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const DelayedNavigation = struct {
|
const DelayedNavigation = struct {
|
||||||
url: []const u8,
|
url: URL,
|
||||||
session: *Session,
|
session: *Session,
|
||||||
opts: NavigateOpts,
|
opts: NavigateOpts,
|
||||||
|
initial: bool = true,
|
||||||
navigate_node: Loop.CallbackNode = .{ .func = delayNavigate },
|
navigate_node: Loop.CallbackNode = .{ .func = delayNavigate },
|
||||||
|
|
||||||
|
// Navigation is blocking, which is problem because it can seize up
|
||||||
|
// the loop and deadlock. We can only safely try to navigate to a
|
||||||
|
// new page when we're sure there's at least 1 free slot in the
|
||||||
|
// http client. We handle this in two phases:
|
||||||
|
//
|
||||||
|
// In the first phase, when self.initial == true, we'll shutdown the page
|
||||||
|
// and create a new one. The shutdown is important, because it resets the
|
||||||
|
// loop ctx_id and removes the JsContext. Removing the context calls our XHR
|
||||||
|
// destructors which aborts requests. This is necessary to make sure our
|
||||||
|
// [blocking] navigate won't block.
|
||||||
|
//
|
||||||
|
// In the 2nd phase, we wait until there's a free http slot so that our
|
||||||
|
// navigate definetly won't block (which could deadlock the system if there
|
||||||
|
// are still pending async requests, which we've seen happen, even after
|
||||||
|
// an abort).
|
||||||
fn delayNavigate(node: *Loop.CallbackNode, repeat_delay: *?u63) void {
|
fn delayNavigate(node: *Loop.CallbackNode, repeat_delay: *?u63) void {
|
||||||
_ = repeat_delay;
|
|
||||||
const self: *DelayedNavigation = @fieldParentPtr("navigate_node", node);
|
const self: *DelayedNavigation = @fieldParentPtr("navigate_node", node);
|
||||||
self.session.pageNavigate(self.url, self.opts) catch |err| {
|
|
||||||
|
const session = self.session;
|
||||||
|
const initial = self.initial;
|
||||||
|
|
||||||
|
if (initial) {
|
||||||
|
// Prior to schedule this task, we terminated excution to stop
|
||||||
|
// the running script. If we don't resume it before doing a shutdown
|
||||||
|
// we'll get an error.
|
||||||
|
session.executor.resumeExecution();
|
||||||
|
|
||||||
|
session.removePage();
|
||||||
|
_ = session.createPage() catch |err| {
|
||||||
|
log.err(.browser, "delayed navigation page error", .{
|
||||||
|
.err = err,
|
||||||
|
.url = self.url,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.initial = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.browser.http_client.freeSlotCount() == 0) {
|
||||||
|
log.debug(.browser, "delayed navigate waiting", .{});
|
||||||
|
const delay = 0 * std.time.ns_per_ms;
|
||||||
|
|
||||||
|
// If this isn't the initial check, we can safely re-use the timer
|
||||||
|
// to check again.
|
||||||
|
if (initial == false) {
|
||||||
|
repeat_delay.* = delay;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// However, if this _is_ the initial check, we called
|
||||||
|
// session.removePage above, and that reset the loop ctx_id.
|
||||||
|
// We can't re-use this timer, because it has the previous ctx_id.
|
||||||
|
// We can create a new timeout though, and that'll get the new ctx_id.
|
||||||
|
//
|
||||||
|
// Page has to be not-null here because we called createPage above.
|
||||||
|
_ = session.page.?.loop.timeout(delay, &self.navigate_node) catch |err| {
|
||||||
|
log.err(.browser, "delayed navigation loop err", .{ .err = err });
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const page = session.currentPage() orelse return;
|
||||||
|
defer if (!page.delayed_navigation) {
|
||||||
|
// If, while loading the page, we intend to navigate to another
|
||||||
|
// page, then we need to keep the transfer_arena around, as this
|
||||||
|
// sub-navigation is probably using it.
|
||||||
|
_ = session.browser.transfer_arena.reset(.{ .retain_with_limit = 64 * 1024 });
|
||||||
|
};
|
||||||
|
|
||||||
|
return page.navigate(self.url, self.opts) catch |err| {
|
||||||
log.err(.browser, "delayed navigation error", .{ .err = err, .url = self.url });
|
log.err(.browser, "delayed navigation error", .{ .err = err, .url = self.url });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -802,14 +983,14 @@ const Script = struct {
|
|||||||
|
|
||||||
fn eval(self: *const Script, page: *Page, body: []const u8) !void {
|
fn eval(self: *const Script, page: *Page, body: []const u8) !void {
|
||||||
var try_catch: Env.TryCatch = undefined;
|
var try_catch: Env.TryCatch = undefined;
|
||||||
try_catch.init(page.scope);
|
try_catch.init(page.main_context);
|
||||||
defer try_catch.deinit();
|
defer try_catch.deinit();
|
||||||
|
|
||||||
const src = self.src orelse "inline";
|
const src = self.src orelse "inline";
|
||||||
_ = switch (self.kind) {
|
_ = switch (self.kind) {
|
||||||
.javascript => page.scope.exec(body, src),
|
.javascript => page.main_context.exec(body, src),
|
||||||
.module => blk: {
|
.module => blk: {
|
||||||
switch (try page.scope.module(body, src)) {
|
switch (try page.main_context.module(body, src)) {
|
||||||
.value => |v| break :blk v,
|
.value => |v| break :blk v,
|
||||||
.exception => |e| {
|
.exception => |e| {
|
||||||
log.warn(.user_script, "eval module", .{
|
log.warn(.user_script, "eval module", .{
|
||||||
@@ -821,9 +1002,17 @@ const Script = struct {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
} catch {
|
} catch {
|
||||||
if (try try_catch.err(page.arena)) |msg| {
|
if (page.delayed_navigation) {
|
||||||
log.warn(.user_script, "eval script", .{ .src = src, .err = msg });
|
return error.Terminated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (try try_catch.err(page.arena)) |msg| {
|
||||||
|
log.warn(.user_script, "eval script", .{
|
||||||
|
.src = src,
|
||||||
|
.err = msg,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
try self.executeCallback("onerror", page);
|
try self.executeCallback("onerror", page);
|
||||||
return error.JsErr;
|
return error.JsErr;
|
||||||
};
|
};
|
||||||
@@ -835,9 +1024,9 @@ const Script = struct {
|
|||||||
switch (callback) {
|
switch (callback) {
|
||||||
.string => |str| {
|
.string => |str| {
|
||||||
var try_catch: Env.TryCatch = undefined;
|
var try_catch: Env.TryCatch = undefined;
|
||||||
try_catch.init(page.scope);
|
try_catch.init(page.main_context);
|
||||||
defer try_catch.deinit();
|
defer try_catch.deinit();
|
||||||
_ = page.scope.exec(str, typ) catch {
|
_ = page.main_context.exec(str, typ) catch {
|
||||||
if (try try_catch.err(page.arena)) |msg| {
|
if (try try_catch.err(page.arena)) |msg| {
|
||||||
log.warn(.user_script, "script callback", .{
|
log.warn(.user_script, "script callback", .{
|
||||||
.src = self.src,
|
.src = self.src,
|
||||||
@@ -896,10 +1085,15 @@ fn timestamp() u32 {
|
|||||||
// immediately.
|
// immediately.
|
||||||
pub export fn scriptAddedCallback(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.?));
|
||||||
|
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| {
|
var script = Script.init(element.?, self) catch |err| {
|
||||||
log.warn(.browser, "script added init error", .{ .err = err });
|
log.warn(.browser, "script added init error", .{ .err = err });
|
||||||
return;
|
return;
|
||||||
} orelse return;
|
} orelse return;
|
||||||
|
|
||||||
self.evalScript(&script);
|
_ = self.evalScript(&script);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ test "Browser.fetch" {
|
|||||||
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
defer runner.deinit();
|
defer runner.deinit();
|
||||||
|
|
||||||
try @import("polyfill.zig").load(testing.allocator, runner.page.scope);
|
try @import("polyfill.zig").load(testing.allocator, runner.page.main_context);
|
||||||
|
|
||||||
try runner.testCases(&.{
|
try runner.testCases(&.{
|
||||||
.{
|
.{
|
||||||
|
|||||||
@@ -30,13 +30,13 @@ const modules = [_]struct {
|
|||||||
.{ .name = "polyfill-fetch", .source = @import("fetch.zig").source },
|
.{ .name = "polyfill-fetch", .source = @import("fetch.zig").source },
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn load(allocator: Allocator, scope: *Env.Scope) !void {
|
pub fn load(allocator: Allocator, js_context: *Env.JsContext) !void {
|
||||||
var try_catch: Env.TryCatch = undefined;
|
var try_catch: Env.TryCatch = undefined;
|
||||||
try_catch.init(scope);
|
try_catch.init(js_context);
|
||||||
defer try_catch.deinit();
|
defer try_catch.deinit();
|
||||||
|
|
||||||
for (modules) |m| {
|
for (modules) |m| {
|
||||||
_ = scope.exec(m.source, m.name) catch |err| {
|
_ = js_context.exec(m.source, m.name) catch |err| {
|
||||||
if (try try_catch.err(allocator)) |msg| {
|
if (try try_catch.err(allocator)) |msg| {
|
||||||
defer allocator.free(msg);
|
defer allocator.free(msg);
|
||||||
log.fatal(.app, "polyfill error", .{ .name = m.name, .err = msg });
|
log.fatal(.app, "polyfill error", .{ .name = m.name, .err = msg });
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ const Allocator = std.mem.Allocator;
|
|||||||
|
|
||||||
const Env = @import("env.zig").Env;
|
const Env = @import("env.zig").Env;
|
||||||
const Page = @import("page.zig").Page;
|
const Page = @import("page.zig").Page;
|
||||||
|
const URL = @import("../url.zig").URL;
|
||||||
const Browser = @import("browser.zig").Browser;
|
const Browser = @import("browser.zig").Browser;
|
||||||
const NavigateOpts = @import("page.zig").NavigateOpts;
|
const NavigateOpts = @import("page.zig").NavigateOpts;
|
||||||
|
|
||||||
@@ -72,7 +73,7 @@ pub const Session = struct {
|
|||||||
|
|
||||||
pub fn deinit(self: *Session) void {
|
pub fn deinit(self: *Session) void {
|
||||||
if (self.page != null) {
|
if (self.page != null) {
|
||||||
self.removePage() catch {};
|
self.removePage();
|
||||||
}
|
}
|
||||||
self.cookie_jar.deinit();
|
self.cookie_jar.deinit();
|
||||||
self.storage_shed.deinit();
|
self.storage_shed.deinit();
|
||||||
@@ -84,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);
|
||||||
@@ -104,7 +105,7 @@ pub const Session = struct {
|
|||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn removePage(self: *Session) !void {
|
pub fn removePage(self: *Session) void {
|
||||||
// Inform CDP the page is going to be removed, allowing other worlds to remove themselves before the main one
|
// Inform CDP the page is going to be removed, allowing other worlds to remove themselves before the main one
|
||||||
self.browser.notification.dispatch(.page_remove, .{});
|
self.browser.notification.dispatch(.page_remove, .{});
|
||||||
|
|
||||||
@@ -115,11 +116,11 @@ pub const Session = struct {
|
|||||||
// phase. It's important that we clean these up, as they're holding onto
|
// phase. It's important that we clean these up, as they're holding onto
|
||||||
// limited resources (like our fixed-sized http state pool).
|
// limited resources (like our fixed-sized http state pool).
|
||||||
//
|
//
|
||||||
// First thing we do, is endScope() which will execute the destructor
|
// First thing we do, is removeJsContext() which will execute the destructor
|
||||||
// of any type that registered a destructor (e.g. XMLHttpRequest).
|
// of any type that registered a destructor (e.g. XMLHttpRequest).
|
||||||
// This will shutdown any pending sockets, which begins our cleaning
|
// This will shutdown any pending sockets, which begins our cleaning
|
||||||
// processed
|
// processed
|
||||||
self.executor.endScope();
|
self.executor.removeJsContext();
|
||||||
|
|
||||||
// Second thing we do is reset the loop. This increments the loop ctx_id
|
// Second thing we do is reset the loop. This increments the loop ctx_id
|
||||||
// so that any "stale" timeouts we process will get ignored. We need to
|
// so that any "stale" timeouts we process will get ignored. We need to
|
||||||
@@ -127,12 +128,6 @@ pub const Session = struct {
|
|||||||
// window.setTimeout and running microtasks should be ignored
|
// window.setTimeout and running microtasks should be ignored
|
||||||
self.browser.app.loop.reset();
|
self.browser.app.loop.reset();
|
||||||
|
|
||||||
// Finally, we run the loop. Because of the reset just above, this will
|
|
||||||
// ignore any timeouts. And, because of the endScope about this, it
|
|
||||||
// should ensure that the http requests detect the shutdown socket and
|
|
||||||
// release their resources.
|
|
||||||
try self.browser.app.loop.run();
|
|
||||||
|
|
||||||
self.page = null;
|
self.page = null;
|
||||||
|
|
||||||
// clear netsurf memory arena.
|
// clear netsurf memory arena.
|
||||||
@@ -144,28 +139,4 @@ pub const Session = struct {
|
|||||||
pub fn currentPage(self: *Session) ?*Page {
|
pub fn currentPage(self: *Session) ?*Page {
|
||||||
return &(self.page orelse return null);
|
return &(self.page orelse return null);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pageNavigate(self: *Session, url_string: []const u8, opts: NavigateOpts) !void {
|
|
||||||
// currently, this is only called from the page, so let's hope
|
|
||||||
// it isn't null!
|
|
||||||
std.debug.assert(self.page != null);
|
|
||||||
|
|
||||||
defer if (self.page) |*p| {
|
|
||||||
if (!p.delayed_navigation) {
|
|
||||||
// If, while loading the page, we intend to navigate to another
|
|
||||||
// page, then we need to keep the transfer_arena around, as this
|
|
||||||
// sub-navigation is probably using it.
|
|
||||||
_ = self.browser.transfer_arena.reset(.{ .retain_with_limit = 64 * 1024 });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// it's safe to use the transfer arena here, because the page will
|
|
||||||
// eventually clone the URL using its own page_arena (after it gets
|
|
||||||
// the final URL, possibly following redirects)
|
|
||||||
const url = try self.page.?.url.resolve(self.transfer_arena, url_string);
|
|
||||||
|
|
||||||
try self.removePage();
|
|
||||||
var page = try self.createPage();
|
|
||||||
return page.navigate(url, opts);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ pub const Interfaces = .{
|
|||||||
// allocatorate data, I should be able to retrieve the scheme + the following `:`
|
// allocatorate data, I should be able to retrieve the scheme + the following `:`
|
||||||
// from rawuri.
|
// from rawuri.
|
||||||
//
|
//
|
||||||
// 2. The other way would bu to copy the `std.Uri` code to ahve a dedicated
|
// 2. The other way would be to copy the `std.Uri` code to have a dedicated
|
||||||
// parser including the characters we want for the web API.
|
// parser including the characters we want for the web API.
|
||||||
pub const URL = struct {
|
pub const URL = struct {
|
||||||
uri: std.Uri,
|
uri: std.Uri,
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ fn collectForm(form: *parser.Form, submitter_: ?*parser.ElementHTML, page: *Page
|
|||||||
const tag = try parser.elementHTMLGetTagType(@as(*parser.ElementHTML, @ptrCast(element)));
|
const tag = try parser.elementHTMLGetTagType(@as(*parser.ElementHTML, @ptrCast(element)));
|
||||||
switch (tag) {
|
switch (tag) {
|
||||||
.input => {
|
.input => {
|
||||||
const tpe = try parser.elementGetAttribute(element, "type") orelse "";
|
const tpe = try parser.inputGetType(@ptrCast(element));
|
||||||
if (std.ascii.eqlIgnoreCase(tpe, "image")) {
|
if (std.ascii.eqlIgnoreCase(tpe, "image")) {
|
||||||
if (submitter_name_) |submitter_name| {
|
if (submitter_name_) |submitter_name| {
|
||||||
if (std.mem.eql(u8, submitter_name, name)) {
|
if (std.mem.eql(u8, submitter_name, name)) {
|
||||||
@@ -162,7 +162,7 @@ fn collectForm(form: *parser.Form, submitter_: ?*parser.ElementHTML, page: *Page
|
|||||||
}
|
}
|
||||||
submitter_included = true;
|
submitter_included = true;
|
||||||
}
|
}
|
||||||
const value = (try parser.elementGetAttribute(element, "value")) orelse "";
|
const value = try parser.inputGetValue(@ptrCast(element));
|
||||||
try entries.appendOwned(arena, name, value);
|
try entries.appendOwned(arena, name, value);
|
||||||
},
|
},
|
||||||
.select => {
|
.select => {
|
||||||
@@ -189,11 +189,11 @@ fn collectForm(form: *parser.Form, submitter_: ?*parser.ElementHTML, page: *Page
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (submitter_included == false) {
|
if (submitter_included == false) {
|
||||||
if (submitter_) |submitter| {
|
if (submitter_name_) |submitter_name| {
|
||||||
// this can happen if the submitter is outside the form, but associated
|
// this can happen if the submitter is outside the form, but associated
|
||||||
// with the form via a form=ID attribute
|
// with the form via a form=ID attribute
|
||||||
const value = (try parser.elementGetAttribute(@ptrCast(submitter), "value")) orelse "";
|
const value = (try parser.elementGetAttribute(@ptrCast(submitter_.?), "value")) orelse "";
|
||||||
try entries.appendOwned(arena, submitter_name_.?, value);
|
try entries.appendOwned(arena, submitter_name, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,7 +216,7 @@ fn collectSelectValues(arena: Allocator, select: *parser.Select, name: []const u
|
|||||||
if (is_multiple == false) {
|
if (is_multiple == false) {
|
||||||
const option = try parser.optionCollectionItem(options, @intCast(selected_index));
|
const option = try parser.optionCollectionItem(options, @intCast(selected_index));
|
||||||
|
|
||||||
if (try parser.elementGetAttribute(@ptrCast(option), "disabled") != null) {
|
if (try parser.elementGetAttribute(@alignCast(@ptrCast(option)), "disabled") != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const value = try parser.optionGetValue(option);
|
const value = try parser.optionGetValue(option);
|
||||||
@@ -228,7 +228,7 @@ fn collectSelectValues(arena: Allocator, select: *parser.Select, name: []const u
|
|||||||
// we can go directly to the first one
|
// we can go directly to the first one
|
||||||
for (@intCast(selected_index)..len) |i| {
|
for (@intCast(selected_index)..len) |i| {
|
||||||
const option = try parser.optionCollectionItem(options, @intCast(i));
|
const option = try parser.optionCollectionItem(options, @intCast(i));
|
||||||
if (try parser.elementGetAttribute(@ptrCast(option), "disabled") != null) {
|
if (try parser.elementGetAttribute(@alignCast(@ptrCast(option)), "disabled") != null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,7 +249,7 @@ fn getSubmitterName(submitter_: ?*parser.ElementHTML) !?[]const u8 {
|
|||||||
switch (tag) {
|
switch (tag) {
|
||||||
.button => return name,
|
.button => return name,
|
||||||
.input => {
|
.input => {
|
||||||
const tpe = (try parser.elementGetAttribute(element, "type")) orelse "";
|
const tpe = try parser.inputGetType(@ptrCast(element));
|
||||||
// only an image type can be a sumbitter
|
// only an image type can be a sumbitter
|
||||||
if (std.ascii.eqlIgnoreCase(tpe, "image") or std.ascii.eqlIgnoreCase(tpe, "submit")) {
|
if (std.ascii.eqlIgnoreCase(tpe, "image") or std.ascii.eqlIgnoreCase(tpe, "submit")) {
|
||||||
return name;
|
return name;
|
||||||
|
|||||||
@@ -338,9 +338,20 @@ pub const XMLHttpRequest = struct {
|
|||||||
// dispatch request event.
|
// dispatch request event.
|
||||||
// errors are logged only.
|
// errors are logged only.
|
||||||
fn dispatchEvt(self: *XMLHttpRequest, typ: []const u8) void {
|
fn dispatchEvt(self: *XMLHttpRequest, typ: []const u8) void {
|
||||||
log.debug(.script_event, "dispatch event", .{ .type = typ, .source = "xhr" });
|
log.debug(.script_event, "dispatch event", .{
|
||||||
|
.type = typ,
|
||||||
|
.source = "xhr",
|
||||||
|
.method = self.method,
|
||||||
|
.url = self.url,
|
||||||
|
});
|
||||||
self._dispatchEvt(typ) catch |err| {
|
self._dispatchEvt(typ) catch |err| {
|
||||||
log.err(.app, "dispatch event error", .{ .err = err, .type = typ, .source = "xhr" });
|
log.err(.app, "dispatch event error", .{
|
||||||
|
.err = err,
|
||||||
|
.type = typ,
|
||||||
|
.source = "xhr",
|
||||||
|
.method = self.method,
|
||||||
|
.url = self.url,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,9 +369,20 @@ pub const XMLHttpRequest = struct {
|
|||||||
typ: []const u8,
|
typ: []const u8,
|
||||||
opts: ProgressEvent.EventInit,
|
opts: ProgressEvent.EventInit,
|
||||||
) void {
|
) void {
|
||||||
log.debug(.script_event, "dispatch progress event", .{ .type = typ, .source = "xhr" });
|
log.debug(.script_event, "dispatch progress event", .{
|
||||||
|
.type = typ,
|
||||||
|
.source = "xhr",
|
||||||
|
.method = self.method,
|
||||||
|
.url = self.url,
|
||||||
|
});
|
||||||
self._dispatchProgressEvent(typ, opts) catch |err| {
|
self._dispatchProgressEvent(typ, opts) catch |err| {
|
||||||
log.err(.app, "dispatch progress event error", .{ .err = err, .type = typ, .source = "xhr" });
|
log.err(.app, "dispatch progress event error", .{
|
||||||
|
.err = err,
|
||||||
|
.type = typ,
|
||||||
|
.source = "xhr",
|
||||||
|
.method = self.method,
|
||||||
|
.url = self.url,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -734,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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -412,11 +412,13 @@ pub fn BrowserContext(comptime CDP_T: type) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn networkEnable(self: *Self) !void {
|
pub fn networkEnable(self: *Self) !void {
|
||||||
|
try self.cdp.browser.notification.register(.http_request_fail, self, onHttpRequestFail);
|
||||||
try self.cdp.browser.notification.register(.http_request_start, self, onHttpRequestStart);
|
try self.cdp.browser.notification.register(.http_request_start, self, onHttpRequestStart);
|
||||||
try self.cdp.browser.notification.register(.http_request_complete, self, onHttpRequestComplete);
|
try self.cdp.browser.notification.register(.http_request_complete, self, onHttpRequestComplete);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn networkDisable(self: *Self) void {
|
pub fn networkDisable(self: *Self) void {
|
||||||
|
self.cdp.browser.notification.unregister(.http_request_fail, self);
|
||||||
self.cdp.browser.notification.unregister(.http_request_start, self);
|
self.cdp.browser.notification.unregister(.http_request_start, self);
|
||||||
self.cdp.browser.notification.unregister(.http_request_complete, self);
|
self.cdp.browser.notification.unregister(.http_request_complete, self);
|
||||||
}
|
}
|
||||||
@@ -448,6 +450,12 @@ pub fn BrowserContext(comptime CDP_T: type) type {
|
|||||||
return @import("domains/network.zig").httpRequestStart(self.notification_arena, self, data);
|
return @import("domains/network.zig").httpRequestStart(self.notification_arena, self, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn onHttpRequestFail(ctx: *anyopaque, data: *const Notification.RequestFail) !void {
|
||||||
|
const self: *Self = @alignCast(@ptrCast(ctx));
|
||||||
|
defer self.resetNotificationArena();
|
||||||
|
return @import("domains/network.zig").httpRequestFail(self.notification_arena, self, data);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn onHttpRequestComplete(ctx: *anyopaque, data: *const Notification.RequestComplete) !void {
|
pub fn onHttpRequestComplete(ctx: *anyopaque, data: *const Notification.RequestComplete) !void {
|
||||||
const self: *Self = @alignCast(@ptrCast(ctx));
|
const self: *Self = @alignCast(@ptrCast(ctx));
|
||||||
defer self.resetNotificationArena();
|
defer self.resetNotificationArena();
|
||||||
@@ -547,8 +555,8 @@ const IsolatedWorld = struct {
|
|||||||
self.executor.deinit();
|
self.executor.deinit();
|
||||||
}
|
}
|
||||||
pub fn removeContext(self: *IsolatedWorld) !void {
|
pub fn removeContext(self: *IsolatedWorld) !void {
|
||||||
if (self.executor.scope == null) return error.NoIsolatedContextToRemove;
|
if (self.executor.js_context == null) return error.NoIsolatedContextToRemove;
|
||||||
self.executor.endScope();
|
self.executor.removeJsContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
// The isolate world must share at least some of the state with the related page, specifically the DocumentHTML
|
// The isolate world must share at least some of the state with the related page, specifically the DocumentHTML
|
||||||
@@ -557,8 +565,8 @@ const IsolatedWorld = struct {
|
|||||||
// This also means this pointer becomes invalid after removePage untill a new page is created.
|
// This also means this pointer becomes invalid after removePage untill a new page is created.
|
||||||
// Currently we have only 1 page/frame and thus also only 1 state in the isolate world.
|
// Currently we have only 1 page/frame and thus also only 1 state in the isolate world.
|
||||||
pub fn createContext(self: *IsolatedWorld, page: *Page) !void {
|
pub fn createContext(self: *IsolatedWorld, page: *Page) !void {
|
||||||
if (self.executor.scope != null) return error.Only1IsolatedContextSupported;
|
if (self.executor.js_context != null) return error.Only1IsolatedContextSupported;
|
||||||
_ = try self.executor.startScope(&page.window, page, {}, false);
|
_ = try self.executor.createJsContext(&page.window, page, {}, false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -259,13 +259,13 @@ fn resolveNode(cmd: anytype) !void {
|
|||||||
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
||||||
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
|
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
|
||||||
|
|
||||||
var scope = page.scope;
|
var js_context = page.main_context;
|
||||||
if (params.executionContextId) |context_id| {
|
if (params.executionContextId) |context_id| {
|
||||||
if (scope.context.debugContextId() != context_id) {
|
if (js_context.v8_context.debugContextId() != context_id) {
|
||||||
var isolated_world = bc.isolated_world orelse return error.ContextNotFound;
|
var isolated_world = bc.isolated_world orelse return error.ContextNotFound;
|
||||||
scope = &(isolated_world.executor.scope orelse return error.ContextNotFound);
|
js_context = &(isolated_world.executor.js_context orelse return error.ContextNotFound);
|
||||||
|
|
||||||
if (scope.context.debugContextId() != context_id) return error.ContextNotFound;
|
if (js_context.v8_context.debugContextId() != context_id) return error.ContextNotFound;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,7 +275,7 @@ fn resolveNode(cmd: anytype) !void {
|
|||||||
// node._node is a *parser.Node we need this to be able to find its most derived type e.g. Node -> Element -> HTMLElement
|
// node._node is a *parser.Node we need this to be able to find its most derived type e.g. Node -> Element -> HTMLElement
|
||||||
// So we use the Node.Union when retrieve the value from the environment
|
// So we use the Node.Union when retrieve the value from the environment
|
||||||
const remote_object = try bc.inspector.getRemoteObject(
|
const remote_object = try bc.inspector.getRemoteObject(
|
||||||
scope,
|
js_context,
|
||||||
params.objectGroup orelse "",
|
params.objectGroup orelse "",
|
||||||
try dom_node.Node.toInterface(node._node),
|
try dom_node.Node.toInterface(node._node),
|
||||||
);
|
);
|
||||||
@@ -368,7 +368,7 @@ fn getNode(arena: Allocator, browser_context: anytype, node_id: ?Node.Id, backen
|
|||||||
if (object_id) |object_id_| {
|
if (object_id) |object_id_| {
|
||||||
// Retrieve the object from which ever context it is in.
|
// Retrieve the object from which ever context it is in.
|
||||||
const parser_node = try browser_context.inspector.getNodePtr(arena, object_id_);
|
const parser_node = try browser_context.inspector.getNodePtr(arena, object_id_);
|
||||||
return try browser_context.node_registry.register(@ptrCast(parser_node));
|
return try browser_context.node_registry.register(@alignCast(@ptrCast(parser_node)));
|
||||||
}
|
}
|
||||||
return error.MissingParams;
|
return error.MissingParams;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,14 +21,60 @@ const Page = @import("../../browser/page.zig").Page;
|
|||||||
|
|
||||||
pub fn processMessage(cmd: anytype) !void {
|
pub fn processMessage(cmd: anytype) !void {
|
||||||
const action = std.meta.stringToEnum(enum {
|
const action = std.meta.stringToEnum(enum {
|
||||||
|
dispatchKeyEvent,
|
||||||
dispatchMouseEvent,
|
dispatchMouseEvent,
|
||||||
}, cmd.input.action) orelse return error.UnknownMethod;
|
}, cmd.input.action) orelse return error.UnknownMethod;
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
|
.dispatchKeyEvent => return dispatchKeyEvent(cmd),
|
||||||
.dispatchMouseEvent => return dispatchMouseEvent(cmd),
|
.dispatchMouseEvent => return dispatchMouseEvent(cmd),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://chromedevtools.github.io/devtools-protocol/tot/Input/#method-dispatchKeyEvent
|
||||||
|
fn dispatchKeyEvent(cmd: anytype) !void {
|
||||||
|
const params = (try cmd.params(struct {
|
||||||
|
type: Type,
|
||||||
|
key: []const u8 = "",
|
||||||
|
code: []const u8 = "",
|
||||||
|
modifiers: u4 = 0,
|
||||||
|
// Many optional parameters are not implemented yet, see documentation url.
|
||||||
|
|
||||||
|
const Type = enum {
|
||||||
|
keyDown,
|
||||||
|
keyUp,
|
||||||
|
rawKeyDown,
|
||||||
|
char,
|
||||||
|
};
|
||||||
|
})) orelse return error.InvalidParams;
|
||||||
|
|
||||||
|
try cmd.sendResult(null, .{});
|
||||||
|
|
||||||
|
// quickly ignore types we know we don't handle
|
||||||
|
switch (params.type) {
|
||||||
|
.keyUp, .rawKeyDown, .char => return,
|
||||||
|
.keyDown => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
const bc = cmd.browser_context orelse return;
|
||||||
|
const page = bc.session.currentPage() orelse return;
|
||||||
|
|
||||||
|
const keyboard_event = Page.KeyboardEvent{
|
||||||
|
.key = params.key,
|
||||||
|
.code = params.code,
|
||||||
|
.type = switch (params.type) {
|
||||||
|
.keyDown => .keydown,
|
||||||
|
else => unreachable,
|
||||||
|
},
|
||||||
|
.alt = params.modifiers & 1 == 1,
|
||||||
|
.ctrl = params.modifiers & 2 == 2,
|
||||||
|
.meta = params.modifiers & 4 == 4,
|
||||||
|
.shift = params.modifiers & 8 == 8,
|
||||||
|
};
|
||||||
|
try page.keyboardEvent(keyboard_event);
|
||||||
|
// result already sent
|
||||||
|
}
|
||||||
|
|
||||||
// https://chromedevtools.github.io/devtools-protocol/tot/Input/#method-dispatchMouseEvent
|
// https://chromedevtools.github.io/devtools-protocol/tot/Input/#method-dispatchMouseEvent
|
||||||
fn dispatchMouseEvent(cmd: anytype) !void {
|
fn dispatchMouseEvent(cmd: anytype) !void {
|
||||||
const params = (try cmd.params(struct {
|
const params = (try cmd.params(struct {
|
||||||
|
|||||||
@@ -84,6 +84,26 @@ fn putAssumeCapacity(headers: *std.ArrayListUnmanaged(std.http.Header), extra: s
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn httpRequestFail(arena: Allocator, bc: anytype, request: *const Notification.RequestFail) !void {
|
||||||
|
// It's possible that the request failed because we aborted when the client
|
||||||
|
// sent Target.closeTarget. In that case, bc.session_id will be cleared
|
||||||
|
// already, and we can skip sending these messages to the client.
|
||||||
|
const session_id = bc.session_id orelse return;
|
||||||
|
|
||||||
|
// Isn't possible to do a network request within a Browser (which our
|
||||||
|
// notification is tied to), without a page.
|
||||||
|
std.debug.assert(bc.session.page != null);
|
||||||
|
|
||||||
|
// We're missing a bunch of fields, but, for now, this seems like enough
|
||||||
|
try bc.cdp.sendEvent("Network.loadingFailed", .{
|
||||||
|
.requestId = try std.fmt.allocPrint(arena, "REQ-{d}", .{request.id}),
|
||||||
|
// Seems to be what chrome answers with. I assume it depends on the type of error?
|
||||||
|
.type = "Ping",
|
||||||
|
.errorText = request.err,
|
||||||
|
.canceled = false,
|
||||||
|
}, .{ .session_id = session_id });
|
||||||
|
}
|
||||||
|
|
||||||
pub fn httpRequestStart(arena: Allocator, bc: anytype, request: *const Notification.RequestStart) !void {
|
pub fn httpRequestStart(arena: Allocator, bc: anytype, request: *const Notification.RequestStart) !void {
|
||||||
// Isn't possible to do a network request within a Browser (which our
|
// Isn't possible to do a network request within a Browser (which our
|
||||||
// notification is tied to), without a page.
|
// notification is tied to), without a page.
|
||||||
|
|||||||
@@ -117,14 +117,14 @@ fn createIsolatedWorld(cmd: anytype) !void {
|
|||||||
const world = try bc.createIsolatedWorld(params.worldName, params.grantUniveralAccess);
|
const world = try bc.createIsolatedWorld(params.worldName, params.grantUniveralAccess);
|
||||||
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
|
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
|
||||||
try pageCreated(bc, page);
|
try pageCreated(bc, page);
|
||||||
const scope = &world.executor.scope.?;
|
const js_context = &world.executor.js_context.?;
|
||||||
|
|
||||||
// Create the auxdata json for the contextCreated event
|
// Create the auxdata json for the contextCreated event
|
||||||
// Calling contextCreated will assign a Id to the context and send the contextCreated event
|
// Calling contextCreated will assign a Id to the context and send the contextCreated event
|
||||||
const aux_data = try std.fmt.allocPrint(cmd.arena, "{{\"isDefault\":false,\"type\":\"isolated\",\"frameId\":\"{s}\"}}", .{params.frameId});
|
const aux_data = try std.fmt.allocPrint(cmd.arena, "{{\"isDefault\":false,\"type\":\"isolated\",\"frameId\":\"{s}\"}}", .{params.frameId});
|
||||||
bc.inspector.contextCreated(scope, world.name, "", aux_data, false);
|
bc.inspector.contextCreated(js_context, world.name, "", aux_data, false);
|
||||||
|
|
||||||
return cmd.sendResult(.{ .executionContextId = scope.context.debugContextId() }, .{});
|
return cmd.sendResult(.{ .executionContextId = js_context.v8_context.debugContextId() }, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn navigate(cmd: anytype) !void {
|
fn navigate(cmd: anytype) !void {
|
||||||
@@ -163,6 +163,11 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
|
|||||||
std.debug.assert(bc.session.page != null);
|
std.debug.assert(bc.session.page != null);
|
||||||
|
|
||||||
var cdp = bc.cdp;
|
var cdp = bc.cdp;
|
||||||
|
|
||||||
|
if (event.opts.reason != .address_bar) {
|
||||||
|
bc.loader_id = bc.cdp.loader_id_gen.next();
|
||||||
|
}
|
||||||
|
|
||||||
const loader_id = bc.loader_id;
|
const loader_id = bc.loader_id;
|
||||||
const target_id = bc.target_id orelse unreachable;
|
const target_id = bc.target_id orelse unreachable;
|
||||||
const session_id = bc.session_id orelse unreachable;
|
const session_id = bc.session_id orelse unreachable;
|
||||||
@@ -248,7 +253,7 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
|
|||||||
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
|
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
|
||||||
const aux_data = try std.fmt.allocPrint(arena, "{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\"}}", .{target_id});
|
const aux_data = try std.fmt.allocPrint(arena, "{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\"}}", .{target_id});
|
||||||
bc.inspector.contextCreated(
|
bc.inspector.contextCreated(
|
||||||
page.scope,
|
page.main_context,
|
||||||
"",
|
"",
|
||||||
try page.origin(arena),
|
try page.origin(arena),
|
||||||
aux_data,
|
aux_data,
|
||||||
@@ -259,7 +264,7 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
|
|||||||
const aux_json = try std.fmt.allocPrint(arena, "{{\"isDefault\":false,\"type\":\"isolated\",\"frameId\":\"{s}\"}}", .{target_id});
|
const aux_json = try std.fmt.allocPrint(arena, "{{\"isDefault\":false,\"type\":\"isolated\",\"frameId\":\"{s}\"}}", .{target_id});
|
||||||
// Calling contextCreated will assign a new Id to the context and send the contextCreated event
|
// Calling contextCreated will assign a new Id to the context and send the contextCreated event
|
||||||
bc.inspector.contextCreated(
|
bc.inspector.contextCreated(
|
||||||
&isolated_world.executor.scope.?,
|
&isolated_world.executor.js_context.?,
|
||||||
isolated_world.name,
|
isolated_world.name,
|
||||||
"://",
|
"://",
|
||||||
aux_json,
|
aux_json,
|
||||||
@@ -281,7 +286,7 @@ pub fn pageCreated(bc: anytype, page: *Page) !void {
|
|||||||
try isolated_world.createContext(page);
|
try isolated_world.createContext(page);
|
||||||
|
|
||||||
const polyfill = @import("../../browser/polyfill/polyfill.zig");
|
const polyfill = @import("../../browser/polyfill/polyfill.zig");
|
||||||
try polyfill.load(bc.arena, &isolated_world.executor.scope.?);
|
try polyfill.load(bc.arena, &isolated_world.executor.js_context.?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ fn createTarget(cmd: anytype) !void {
|
|||||||
{
|
{
|
||||||
const aux_data = try std.fmt.allocPrint(cmd.arena, "{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\"}}", .{target_id});
|
const aux_data = try std.fmt.allocPrint(cmd.arena, "{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\"}}", .{target_id});
|
||||||
bc.inspector.contextCreated(
|
bc.inspector.contextCreated(
|
||||||
page.scope,
|
page.main_context,
|
||||||
"",
|
"",
|
||||||
try page.origin(cmd.arena),
|
try page.origin(cmd.arena),
|
||||||
aux_data,
|
aux_data,
|
||||||
@@ -220,7 +220,7 @@ fn closeTarget(cmd: anytype) !void {
|
|||||||
bc.session_id = null;
|
bc.session_id = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try bc.session.removePage();
|
bc.session.removePage();
|
||||||
if (bc.isolated_world) |*world| {
|
if (bc.isolated_world) |*world| {
|
||||||
world.deinit();
|
world.deinit();
|
||||||
bc.isolated_world = null;
|
bc.isolated_world = null;
|
||||||
|
|||||||
@@ -113,10 +113,18 @@ pub const Client = struct {
|
|||||||
loop: *Loop,
|
loop: *Loop,
|
||||||
opts: RequestOpts,
|
opts: RequestOpts,
|
||||||
) !void {
|
) !void {
|
||||||
if (self.state_pool.acquireOrNull()) |state| {
|
|
||||||
// if we have state ready, we can skip the loop and immediately
|
// See the page's DelayedNavitation for why we're doing this. TL;DR -
|
||||||
// kick this request off.
|
// we need to keep 1 slot available for the blocking page navigation flow
|
||||||
return self.asyncRequestReady(method, uri, ctx, callback, state, opts);
|
// (Almost worth keeping a dedicate State just for that flow, but keep
|
||||||
|
// thinking we need a more permanent solution (i.e. making everything
|
||||||
|
// non-blocking).
|
||||||
|
if (self.freeSlotCount() > 1) {
|
||||||
|
if (self.state_pool.acquireOrNull()) |state| {
|
||||||
|
// if we have state ready, we can skip the loop and immediately
|
||||||
|
// kick this request off.
|
||||||
|
return self.asyncRequestReady(method, uri, ctx, callback, state, opts);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This cannot be a client-owned MemoryPool. The page can end before
|
// This cannot be a client-owned MemoryPool. The page can end before
|
||||||
@@ -174,6 +182,10 @@ pub const Client = struct {
|
|||||||
.client = self,
|
.client = self,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn freeSlotCount(self: *Client) usize {
|
||||||
|
return self.state_pool.freeSlotCount();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const RequestOpts = struct {
|
const RequestOpts = struct {
|
||||||
@@ -354,6 +366,7 @@ pub const Request = struct {
|
|||||||
// Because of things like redirects and error handling, it is possible for
|
// Because of things like redirects and error handling, it is possible for
|
||||||
// the notification functions to be called multiple times, so we guard them
|
// the notification functions to be called multiple times, so we guard them
|
||||||
// with these booleans
|
// with these booleans
|
||||||
|
_notified_fail: bool,
|
||||||
_notified_start: bool,
|
_notified_start: bool,
|
||||||
_notified_complete: bool,
|
_notified_complete: bool,
|
||||||
|
|
||||||
@@ -414,6 +427,7 @@ pub const Request = struct {
|
|||||||
._keepalive = false,
|
._keepalive = false,
|
||||||
._redirect_count = 0,
|
._redirect_count = 0,
|
||||||
._has_host_header = false,
|
._has_host_header = false,
|
||||||
|
._notified_fail = false,
|
||||||
._notified_start = false,
|
._notified_start = false,
|
||||||
._notified_complete = false,
|
._notified_complete = false,
|
||||||
._connection_from_keepalive = false,
|
._connection_from_keepalive = false,
|
||||||
@@ -428,6 +442,7 @@ pub const Request = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn abort(self: *Request) void {
|
pub fn abort(self: *Request) void {
|
||||||
|
self.requestFailed("aborted");
|
||||||
const aborter = self._aborter orelse {
|
const aborter = self._aborter orelse {
|
||||||
self.deinit();
|
self.deinit();
|
||||||
return;
|
return;
|
||||||
@@ -555,6 +570,10 @@ pub const Request = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn doSendSync(self: *Request, use_pool: bool) anyerror!Response {
|
fn doSendSync(self: *Request, use_pool: bool) anyerror!Response {
|
||||||
|
// https://github.com/ziglang/zig/issues/20369
|
||||||
|
// errdefer |err| self.requestFailed(@errorName(err));
|
||||||
|
errdefer self.requestFailed("network error");
|
||||||
|
|
||||||
if (use_pool) {
|
if (use_pool) {
|
||||||
if (self.findExistingConnection(true)) |connection| {
|
if (self.findExistingConnection(true)) |connection| {
|
||||||
self._connection = connection;
|
self._connection = connection;
|
||||||
@@ -847,6 +866,19 @@ pub const Request = struct {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn requestFailed(self: *Request, err: []const u8) void {
|
||||||
|
const notification = self.notification orelse return;
|
||||||
|
if (self._notified_fail) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self._notified_fail = true;
|
||||||
|
notification.dispatch(.http_request_fail, &.{
|
||||||
|
.id = self.id,
|
||||||
|
.err = err,
|
||||||
|
.url = self.request_uri,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn requestCompleted(self: *Request, response: ResponseHeader) void {
|
fn requestCompleted(self: *Request, response: ResponseHeader) void {
|
||||||
const notification = self.notification orelse return;
|
const notification = self.notification orelse return;
|
||||||
if (self._notified_complete) {
|
if (self._notified_complete) {
|
||||||
@@ -1290,6 +1322,8 @@ fn AsyncHandler(comptime H: type, comptime L: type) type {
|
|||||||
self.handler.onHttpResponse(err) catch {};
|
self.handler.onHttpResponse(err) catch {};
|
||||||
// just to be safe
|
// just to be safe
|
||||||
self.request._keepalive = false;
|
self.request._keepalive = false;
|
||||||
|
|
||||||
|
self.request.requestFailed(@errorName(err));
|
||||||
self.request.deinit();
|
self.request.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2509,6 +2543,12 @@ const StatePool = struct {
|
|||||||
allocator.free(self.states);
|
allocator.free(self.states);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn freeSlotCount(self: *StatePool) usize {
|
||||||
|
self.mutex.lock();
|
||||||
|
defer self.mutex.unlock();
|
||||||
|
return self.available;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn acquireWait(self: *StatePool) *State {
|
pub fn acquireWait(self: *StatePool) *State {
|
||||||
const states = self.states;
|
const states = self.states;
|
||||||
|
|
||||||
@@ -3000,8 +3040,14 @@ test "HttpClient: async connect error" {
|
|||||||
.{},
|
.{},
|
||||||
);
|
);
|
||||||
|
|
||||||
try loop.io.run_for_ns(std.time.ns_per_ms);
|
for (0..10) |_| {
|
||||||
try reset.timedWait(std.time.ns_per_s);
|
try loop.io.run_for_ns(std.time.ns_per_ms * 10);
|
||||||
|
if (reset.isSet()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return error.Timeout;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test "HttpClient: async no body" {
|
test "HttpClient: async no body" {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ fn run(arena: Allocator, test_file: []const u8, loader: *FileLoader, err_out: *?
|
|||||||
});
|
});
|
||||||
defer runner.deinit();
|
defer runner.deinit();
|
||||||
|
|
||||||
try polyfill.load(arena, runner.page.scope);
|
try polyfill.load(arena, runner.page.main_context);
|
||||||
|
|
||||||
// loop over the scripts.
|
// loop over the scripts.
|
||||||
const doc = parser.documentHTMLToDocument(runner.page.window.document);
|
const doc = parser.documentHTMLToDocument(runner.page.window.document);
|
||||||
@@ -155,7 +155,7 @@ fn run(arena: Allocator, test_file: []const u8, loader: *FileLoader, err_out: *?
|
|||||||
{
|
{
|
||||||
// wait for all async executions
|
// wait for all async executions
|
||||||
var try_catch: Env.TryCatch = undefined;
|
var try_catch: Env.TryCatch = undefined;
|
||||||
try_catch.init(runner.page.scope);
|
try_catch.init(runner.page.main_context);
|
||||||
defer try_catch.deinit();
|
defer try_catch.deinit();
|
||||||
try runner.page.loop.run();
|
try runner.page.loop.run();
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ pub const Notification = struct {
|
|||||||
page_created: List = .{},
|
page_created: List = .{},
|
||||||
page_navigate: List = .{},
|
page_navigate: List = .{},
|
||||||
page_navigated: List = .{},
|
page_navigated: List = .{},
|
||||||
|
http_request_fail: List = .{},
|
||||||
http_request_start: List = .{},
|
http_request_start: List = .{},
|
||||||
http_request_complete: List = .{},
|
http_request_complete: List = .{},
|
||||||
notification_created: List = .{},
|
notification_created: List = .{},
|
||||||
@@ -69,6 +70,7 @@ pub const Notification = struct {
|
|||||||
page_created: *page.Page,
|
page_created: *page.Page,
|
||||||
page_navigate: *const PageNavigate,
|
page_navigate: *const PageNavigate,
|
||||||
page_navigated: *const PageNavigated,
|
page_navigated: *const PageNavigated,
|
||||||
|
http_request_fail: *const RequestFail,
|
||||||
http_request_start: *const RequestStart,
|
http_request_start: *const RequestStart,
|
||||||
http_request_complete: *const RequestComplete,
|
http_request_complete: *const RequestComplete,
|
||||||
notification_created: *Notification,
|
notification_created: *Notification,
|
||||||
@@ -97,6 +99,12 @@ pub const Notification = struct {
|
|||||||
has_body: bool,
|
has_body: bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const RequestFail = struct {
|
||||||
|
id: usize,
|
||||||
|
url: *const std.Uri,
|
||||||
|
err: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
pub const RequestComplete = struct {
|
pub const RequestComplete = struct {
|
||||||
id: usize,
|
id: usize,
|
||||||
url: *const std.Uri,
|
url: *const std.Uri,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -81,12 +81,13 @@ pub const Loop = struct {
|
|||||||
|
|
||||||
// run tail events. We do run the tail events to ensure all the
|
// run tail events. We do run the tail events to ensure all the
|
||||||
// contexts are correcly free.
|
// contexts are correcly free.
|
||||||
while (self.hasPendinEvents()) {
|
while (self.pending_network_count != 0 or self.pending_timeout_count != 0) {
|
||||||
self.io.run_for_ns(10 * std.time.ns_per_ms) catch |err| {
|
self.io.run_for_ns(std.time.ns_per_ms * 10) catch |err| {
|
||||||
log.err(.loop, "deinit", .{ .err = err });
|
log.err(.loop, "deinit", .{ .err = err });
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (comptime CANCEL_SUPPORTED) {
|
if (comptime CANCEL_SUPPORTED) {
|
||||||
self.io.cancel_all();
|
self.io.cancel_all();
|
||||||
}
|
}
|
||||||
@@ -96,21 +97,6 @@ pub const Loop = struct {
|
|||||||
self.cancelled.deinit(self.alloc);
|
self.cancelled.deinit(self.alloc);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can shutdown once all the pending network IO is complete.
|
|
||||||
// In debug mode we also wait until al the pending timeouts are complete
|
|
||||||
// but we only do this so that the `timeoutCallback` can free all allocated
|
|
||||||
// memory and we won't report a leak.
|
|
||||||
fn hasPendinEvents(self: *const Self) bool {
|
|
||||||
if (self.pending_network_count > 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (builtin.mode != .Debug) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return self.pending_timeout_count > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve all registred I/O events completed by OS kernel,
|
// Retrieve all registred I/O events completed by OS kernel,
|
||||||
// and execute sequentially their callbacks.
|
// and execute sequentially their callbacks.
|
||||||
// Stops when there is no more I/O events registered on the loop.
|
// Stops when there is no more I/O events registered on the loop.
|
||||||
@@ -121,13 +107,14 @@ pub const Loop = struct {
|
|||||||
self.stopping = true;
|
self.stopping = true;
|
||||||
defer self.stopping = false;
|
defer self.stopping = false;
|
||||||
|
|
||||||
while (self.pending_network_count > 0) {
|
while (self.pending_network_count != 0 or self.pending_timeout_count != 0) {
|
||||||
try self.io.run_for_ns(10 * std.time.ns_per_ms);
|
self.io.run_for_ns(std.time.ns_per_ms * 10) catch |err| {
|
||||||
// at each iteration we might have new events registred by previous callbacks
|
log.err(.loop, "deinit", .{ .err = err });
|
||||||
|
break;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// JS callbacks APIs
|
// JS callbacks APIs
|
||||||
// -----------------
|
// -----------------
|
||||||
|
|
||||||
@@ -255,7 +242,6 @@ pub const Loop = struct {
|
|||||||
}
|
}
|
||||||
}.onConnect;
|
}.onConnect;
|
||||||
|
|
||||||
|
|
||||||
const callback = try self.event_callback_pool.create();
|
const callback = try self.event_callback_pool.create();
|
||||||
errdefer self.event_callback_pool.destroy(callback);
|
errdefer self.event_callback_pool.destroy(callback);
|
||||||
callback.* = .{ .loop = self, .ctx = ctx };
|
callback.* = .{ .loop = self, .ctx = ctx };
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ pub fn Runner(comptime State: type, comptime Global: type, comptime types: anyty
|
|||||||
|
|
||||||
return struct {
|
return struct {
|
||||||
env: *Env,
|
env: *Env,
|
||||||
scope: *Env.Scope,
|
js_context: *Env.JsContext,
|
||||||
executor: Env.ExecutionWorld,
|
executor: Env.ExecutionWorld,
|
||||||
|
|
||||||
pub const Env = js.Env(State, struct {
|
pub const Env = js.Env(State, struct {
|
||||||
@@ -48,7 +48,7 @@ pub fn Runner(comptime State: type, comptime Global: type, comptime types: anyty
|
|||||||
self.executor = try self.env.newExecutionWorld();
|
self.executor = try self.env.newExecutionWorld();
|
||||||
errdefer self.executor.deinit();
|
errdefer self.executor.deinit();
|
||||||
|
|
||||||
self.scope = try self.executor.startScope(
|
self.js_context = try self.executor.createJsContext(
|
||||||
if (Global == void) &default_global else global,
|
if (Global == void) &default_global else global,
|
||||||
state,
|
state,
|
||||||
{},
|
{},
|
||||||
@@ -68,10 +68,10 @@ pub fn Runner(comptime State: type, comptime Global: type, comptime types: anyty
|
|||||||
pub fn testCases(self: *Self, cases: []const Case, _: RunOpts) !void {
|
pub fn testCases(self: *Self, cases: []const Case, _: RunOpts) !void {
|
||||||
for (cases, 0..) |case, i| {
|
for (cases, 0..) |case, i| {
|
||||||
var try_catch: Env.TryCatch = undefined;
|
var try_catch: Env.TryCatch = undefined;
|
||||||
try_catch.init(self.scope);
|
try_catch.init(self.js_context);
|
||||||
defer try_catch.deinit();
|
defer try_catch.deinit();
|
||||||
|
|
||||||
const value = self.scope.exec(case.@"0", null) catch |err| {
|
const value = self.js_context.exec(case.@"0", null) catch |err| {
|
||||||
if (try try_catch.err(allocator)) |msg| {
|
if (try try_catch.err(allocator)) |msg| {
|
||||||
defer allocator.free(msg);
|
defer allocator.free(msg);
|
||||||
if (isExpectedTypeError(case.@"1", msg)) {
|
if (isExpectedTypeError(case.@"1", msg)) {
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -419,17 +421,17 @@ pub const JsRunner = struct {
|
|||||||
const RunOpts = struct {};
|
const RunOpts = struct {};
|
||||||
pub const Case = std.meta.Tuple(&.{ []const u8, ?[]const u8 });
|
pub const Case = std.meta.Tuple(&.{ []const u8, ?[]const u8 });
|
||||||
pub fn testCases(self: *JsRunner, cases: []const Case, _: RunOpts) !void {
|
pub fn testCases(self: *JsRunner, cases: []const Case, _: RunOpts) !void {
|
||||||
const scope = self.page.scope;
|
const js_context = self.page.main_context;
|
||||||
const arena = self.page.arena;
|
const arena = self.page.arena;
|
||||||
|
|
||||||
const start = try std.time.Instant.now();
|
const start = try std.time.Instant.now();
|
||||||
|
|
||||||
for (cases, 0..) |case, i| {
|
for (cases, 0..) |case, i| {
|
||||||
var try_catch: Env.TryCatch = undefined;
|
var try_catch: Env.TryCatch = undefined;
|
||||||
try_catch.init(scope);
|
try_catch.init(js_context);
|
||||||
defer try_catch.deinit();
|
defer try_catch.deinit();
|
||||||
|
|
||||||
const value = scope.exec(case.@"0", null) catch |err| {
|
const value = js_context.exec(case.@"0", null) catch |err| {
|
||||||
if (try try_catch.err(arena)) |msg| {
|
if (try try_catch.err(arena)) |msg| {
|
||||||
std.debug.print("{s}\n\nCase: {d}\n{s}\n", .{ msg, i + 1, case.@"0" });
|
std.debug.print("{s}\n\nCase: {d}\n{s}\n", .{ msg, i + 1, case.@"0" });
|
||||||
}
|
}
|
||||||
@@ -453,14 +455,14 @@ pub const JsRunner = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn eval(self: *JsRunner, src: []const u8, name: ?[]const u8, err_msg: *?[]const u8) !Env.Value {
|
pub fn eval(self: *JsRunner, src: []const u8, name: ?[]const u8, err_msg: *?[]const u8) !Env.Value {
|
||||||
const scope = self.page.scope;
|
const js_context = self.page.main_context;
|
||||||
const arena = self.page.arena;
|
const arena = self.page.arena;
|
||||||
|
|
||||||
var try_catch: Env.TryCatch = undefined;
|
var try_catch: Env.TryCatch = undefined;
|
||||||
try_catch.init(scope);
|
try_catch.init(js_context);
|
||||||
defer try_catch.deinit();
|
defer try_catch.deinit();
|
||||||
|
|
||||||
return scope.exec(src, name) catch |err| {
|
return js_context.exec(src, name) catch |err| {
|
||||||
if (try try_catch.err(arena)) |msg| {
|
if (try try_catch.err(arena)) |msg| {
|
||||||
err_msg.* = msg;
|
err_msg.* = msg;
|
||||||
std.debug.print("Error running script: {s}\n", .{msg});
|
std.debug.print("Error running script: {s}\n", .{msg});
|
||||||
|
|||||||
2
vendor/netsurf/libdom
vendored
2
vendor/netsurf/libdom
vendored
Submodule vendor/netsurf/libdom updated: 614187b0aa...f22449c52e
Reference in New Issue
Block a user