diff --git a/src/browser/dom/document.zig b/src/browser/dom/document.zig index 58d0bf96..214a5e4a 100644 --- a/src/browser/dom/document.zig +++ b/src/browser/dom/document.zig @@ -149,7 +149,9 @@ pub const Document = struct { tag_name: []const u8, page: *Page, ) !collection.HTMLCollection { - return try collection.HTMLCollectionByTagName(page.arena, parser.documentToNode(self), tag_name, true); + return try collection.HTMLCollectionByTagName(page.arena, parser.documentToNode(self), tag_name, .{ + .include_root = true, + }); } pub fn _getElementsByClassName( @@ -157,7 +159,9 @@ pub const Document = struct { classNames: []const u8, page: *Page, ) !collection.HTMLCollection { - return try collection.HTMLCollectionByClassName(page.arena, parser.documentToNode(self), classNames, true); + return try collection.HTMLCollectionByClassName(page.arena, parser.documentToNode(self), classNames, .{ + .include_root = true, + }); } pub fn _createDocumentFragment(self: *parser.Document) !*parser.DocumentFragment { @@ -201,7 +205,9 @@ pub const Document = struct { // ParentNode // https://dom.spec.whatwg.org/#parentnode pub fn get_children(self: *parser.Document) !collection.HTMLCollection { - return try collection.HTMLCollectionChildren(parser.documentToNode(self), false); + return collection.HTMLCollectionChildren(parser.documentToNode(self), .{ + .include_root = false, + }); } pub fn get_firstElementChild(self: *parser.Document) !?ElementUnion { diff --git a/src/browser/dom/document_fragment.zig b/src/browser/dom/document_fragment.zig index 615eca2a..10245624 100644 --- a/src/browser/dom/document_fragment.zig +++ b/src/browser/dom/document_fragment.zig @@ -79,7 +79,9 @@ pub const DocumentFragment = struct { } pub fn get_children(self: *parser.DocumentFragment) !collection.HTMLCollection { - return collection.HTMLCollectionChildren(parser.documentFragmentToNode(self), false); + return collection.HTMLCollectionChildren(parser.documentFragmentToNode(self), .{ + .include_root = false, + }); } }; diff --git a/src/browser/dom/element.zig b/src/browser/dom/element.zig index 41c882e8..054ba57e 100644 --- a/src/browser/dom/element.zig +++ b/src/browser/dom/element.zig @@ -294,7 +294,7 @@ pub const Element = struct { page.arena, parser.elementToNode(self), tag_name, - false, + .{ .include_root = false }, ); } @@ -307,14 +307,16 @@ pub const Element = struct { page.arena, parser.elementToNode(self), classNames, - false, + .{ .include_root = false }, ); } // ParentNode // https://dom.spec.whatwg.org/#parentnode pub fn get_children(self: *parser.Element) !collection.HTMLCollection { - return try collection.HTMLCollectionChildren(parser.elementToNode(self), false); + return collection.HTMLCollectionChildren(parser.elementToNode(self), .{ + .include_root = false, + }); } pub fn get_firstElementChild(self: *parser.Element) !?Union { diff --git a/src/browser/dom/html_collection.zig b/src/browser/dom/html_collection.zig index db0c56b0..8d50ae25 100644 --- a/src/browser/dom/html_collection.zig +++ b/src/browser/dom/html_collection.zig @@ -72,13 +72,14 @@ pub fn HTMLCollectionByTagName( arena: Allocator, root: ?*parser.Node, tag_name: []const u8, - include_root: bool, + opts: Opts, ) !HTMLCollection { return HTMLCollection{ .root = root, .walker = .{ .walkerDepthFirst = .{} }, .matcher = .{ .matchByTagName = try MatchByTagName.init(arena, tag_name) }, - .include_root = include_root, + .mutable = opts.mutable, + .include_root = opts.include_root, }; } @@ -109,13 +110,14 @@ pub fn HTMLCollectionByClassName( arena: Allocator, root: ?*parser.Node, classNames: []const u8, - include_root: bool, + opts: Opts, ) !HTMLCollection { return HTMLCollection{ .root = root, .walker = .{ .walkerDepthFirst = .{} }, .matcher = .{ .matchByClassName = try MatchByClassName.init(arena, classNames) }, - .include_root = include_root, + .mutable = opts.mutable, + .include_root = opts.include_root, }; } @@ -139,13 +141,14 @@ pub fn HTMLCollectionByName( arena: Allocator, root: ?*parser.Node, name: []const u8, - include_root: bool, + opts: Opts, ) !HTMLCollection { return HTMLCollection{ .root = root, .walker = .{ .walkerDepthFirst = .{} }, .matcher = .{ .matchByName = try MatchByName.init(arena, name) }, - .include_root = include_root, + .mutable = opts.mutable, + .include_root = opts.include_root, }; } @@ -189,13 +192,14 @@ pub const HTMLAllCollection = struct { pub fn HTMLCollectionChildren( root: ?*parser.Node, - include_root: bool, -) !HTMLCollection { + opts: Opts, +) HTMLCollection { return HTMLCollection{ .root = root, .walker = .{ .walkerChildren = .{} }, .matcher = .{ .matchTrue = .{} }, - .include_root = include_root, + .mutable = opts.mutable, + .include_root = opts.include_root, }; } @@ -224,13 +228,14 @@ pub const MatchByLinks = struct { pub fn HTMLCollectionByLinks( root: ?*parser.Node, - include_root: bool, + opts: Opts, ) !HTMLCollection { return HTMLCollection{ .root = root, .walker = .{ .walkerDepthFirst = .{} }, .matcher = .{ .matchByLinks = MatchByLinks{} }, - .include_root = include_root, + .mutable = opts.mutable, + .include_root = opts.include_root, }; } @@ -249,13 +254,14 @@ pub const MatchByAnchors = struct { pub fn HTMLCollectionByAnchors( root: ?*parser.Node, - include_root: bool, + opts: Opts, ) !HTMLCollection { return HTMLCollection{ .root = root, .walker = .{ .walkerDepthFirst = .{} }, .matcher = .{ .matchByAnchors = MatchByAnchors{} }, - .include_root = include_root, + .mutable = opts.mutable, + .include_root = opts.include_root, }; } @@ -285,6 +291,11 @@ pub const HTMLCollectionIterator = struct { } }; +const Opts = struct { + include_root: bool, + mutable: bool = false, +}; + // WEB IDL https://dom.spec.whatwg.org/#htmlcollection // HTMLCollection is re implemented in zig here because libdom // dom_html_collection expects a comparison function callback as arguement. @@ -300,6 +311,8 @@ pub const HTMLCollection = struct { // itself. include_root: bool = false, + mutable: bool = false, + // save a state for the collection to improve the _item speed. cur_idx: ?u32 = null, cur_node: ?*parser.Node = null, @@ -350,12 +363,14 @@ pub const HTMLCollection = struct { var node: *parser.Node = undefined; // Use the current state to improve speed if possible. - if (self.cur_idx != null and index >= self.cur_idx.?) { + if (self.mutable == false and self.cur_idx != null and index >= self.cur_idx.?) { i = self.cur_idx.?; node = self.cur_node.?; } else { node = try self.start() orelse return null; } + // i = 0; + // node = try self.start() orelse return null; while (true) { if (try parser.nodeType(node) == .element) { diff --git a/src/browser/html/document.zig b/src/browser/html/document.zig index 92b19535..cf2ddd7e 100644 --- a/src/browser/html/document.zig +++ b/src/browser/html/document.zig @@ -118,7 +118,9 @@ pub const HTMLDocument = struct { if (name.len == 0) return list; const root = parser.documentHTMLToNode(self); - var c = try collection.HTMLCollectionByName(arena, root, name, false); + var c = try collection.HTMLCollectionByName(arena, root, name, .{ + .include_root = false, + }); const ln = try c.get_length(); var i: u32 = 0; @@ -132,11 +134,15 @@ pub const HTMLDocument = struct { } pub fn get_images(self: *parser.DocumentHTML, page: *Page) !collection.HTMLCollection { - return try collection.HTMLCollectionByTagName(page.arena, parser.documentHTMLToNode(self), "img", false); + return try collection.HTMLCollectionByTagName(page.arena, parser.documentHTMLToNode(self), "img", .{ + .include_root = false, + }); } pub fn get_embeds(self: *parser.DocumentHTML, page: *Page) !collection.HTMLCollection { - return try collection.HTMLCollectionByTagName(page.arena, parser.documentHTMLToNode(self), "embed", false); + return try collection.HTMLCollectionByTagName(page.arena, parser.documentHTMLToNode(self), "embed", .{ + .include_root = false, + }); } pub fn get_plugins(self: *parser.DocumentHTML, page: *Page) !collection.HTMLCollection { @@ -144,11 +150,15 @@ pub const HTMLDocument = struct { } pub fn get_forms(self: *parser.DocumentHTML, page: *Page) !collection.HTMLCollection { - return try collection.HTMLCollectionByTagName(page.arena, parser.documentHTMLToNode(self), "form", false); + return try collection.HTMLCollectionByTagName(page.arena, parser.documentHTMLToNode(self), "form", .{ + .include_root = false, + }); } pub fn get_scripts(self: *parser.DocumentHTML, page: *Page) !collection.HTMLCollection { - return try collection.HTMLCollectionByTagName(page.arena, parser.documentHTMLToNode(self), "script", false); + return try collection.HTMLCollectionByTagName(page.arena, parser.documentHTMLToNode(self), "script", .{ + .include_root = false, + }); } pub fn get_applets(_: *parser.DocumentHTML) !collection.HTMLCollection { @@ -156,11 +166,15 @@ pub const HTMLDocument = struct { } pub fn get_links(self: *parser.DocumentHTML) !collection.HTMLCollection { - return try collection.HTMLCollectionByLinks(parser.documentHTMLToNode(self), false); + return try collection.HTMLCollectionByLinks(parser.documentHTMLToNode(self), .{ + .include_root = false, + }); } pub fn get_anchors(self: *parser.DocumentHTML) !collection.HTMLCollection { - return try collection.HTMLCollectionByAnchors(parser.documentHTMLToNode(self), false); + return try collection.HTMLCollectionByAnchors(parser.documentHTMLToNode(self), .{ + .include_root = false, + }); } pub fn get_all(self: *parser.DocumentHTML) collection.HTMLAllCollection { diff --git a/src/browser/html/elements.zig b/src/browser/html/elements.zig index 4d72ebd4..f294e1de 100644 --- a/src/browser/html/elements.zig +++ b/src/browser/html/elements.zig @@ -75,7 +75,6 @@ pub const Interfaces = .{ HTMLOListElement, HTMLObjectElement, HTMLOptGroupElement, - HTMLOptionElement, HTMLOutputElement, HTMLParagraphElement, HTMLParamElement, @@ -102,7 +101,7 @@ pub const Interfaces = .{ HTMLVideoElement, @import("form.zig").HTMLFormElement, - @import("select.zig").HTMLSelectElement, + @import("select.zig").Interfaces, }; pub const Union = generate.Union(Interfaces); @@ -813,12 +812,6 @@ pub const HTMLOptGroupElement = struct { pub const subtype = .node; }; -pub const HTMLOptionElement = struct { - pub const Self = parser.Option; - pub const prototype = *HTMLElement; - pub const subtype = .node; -}; - pub const HTMLOutputElement = struct { pub const Self = parser.Output; pub const prototype = *HTMLElement; diff --git a/src/browser/html/select.zig b/src/browser/html/select.zig index ab39dd2c..b31183c1 100644 --- a/src/browser/html/select.zig +++ b/src/browser/html/select.zig @@ -18,8 +18,16 @@ const std = @import("std"); const parser = @import("../netsurf.zig"); -const HTMLElement = @import("elements.zig").HTMLElement; +const collection = @import("../dom/html_collection.zig"); + const Page = @import("../page.zig").Page; +const HTMLElement = @import("elements.zig").HTMLElement; + +pub const Interfaces = .{ + HTMLSelectElement, + HTMLOptionElement, + HTMLOptionsCollection, +}; pub const HTMLSelectElement = struct { pub const Self = parser.Select; @@ -89,6 +97,105 @@ pub const HTMLSelectElement = struct { try parser.optionSetSelected(option, true); } } + + pub fn get_options(select: *parser.Select) HTMLOptionsCollection { + return .{ + .select = select, + .proto = collection.HTMLCollectionChildren(@alignCast(@ptrCast(select)), .{ + .mutable = true, + .include_root = false, + }), + }; + } +}; + +pub const HTMLOptionElement = struct { + pub const Self = parser.Option; + pub const prototype = *HTMLElement; + pub const subtype = .node; + + pub fn get_value(self: *parser.Option) ![]const u8 { + return parser.optionGetValue(self); + } + pub fn set_value(self: *parser.Option, value: []const u8) !void { + return parser.optionSetValue(self, value); + } + + pub fn get_label(self: *parser.Option) ![]const u8 { + return parser.optionGetLabel(self); + } + pub fn set_label(self: *parser.Option, label: []const u8) !void { + return parser.optionSetLabel(self, label); + } + + pub fn get_selected(self: *parser.Option) !bool { + return parser.optionGetSelected(self); + } + pub fn set_selected(self: *parser.Option, value: bool) !void { + return parser.optionSetSelected(self, value); + } + + pub fn get_disabled(self: *parser.Option) !bool { + return parser.optionGetDisabled(self); + } + pub fn set_disabled(self: *parser.Option, value: bool) !void { + return parser.optionSetDisabled(self, value); + } + + pub fn get_text(self: *parser.Option) ![]const u8 { + return parser.optionGetText(self); + } + + pub fn get_form(self: *parser.Option) !?*parser.Form { + return parser.optionGetForm(self); + } +}; + +pub const HTMLOptionsCollection = struct { + pub const prototype = *collection.HTMLCollection; + + proto: collection.HTMLCollection, + select: *parser.Select, + + pub fn get_selectedIndex(self: *HTMLOptionsCollection, page: *Page) !i32 { + return HTMLSelectElement.get_selectedIndex(self.select, page); + } + + pub fn set_selectedIndex(self: *HTMLOptionsCollection, index: i32, page: *Page) !void { + return HTMLSelectElement.set_selectedIndex(self.select, index, page); + } + + const BeforeOpts = union(enum) { + index: u32, + option: *parser.Option, + }; + pub fn _add(self: *HTMLOptionsCollection, option: *parser.Option, before_: ?BeforeOpts) !void { + const Node = @import("../dom/node.zig").Node; + const before = before_ orelse { + return self.appendOption(option); + }; + + const insert_before: *parser.Node = switch (before) { + .option => |o| @alignCast(@ptrCast(o)), + .index => |i| (try self.proto.item(i)) orelse return self.appendOption(option), + }; + return Node.before(insert_before, &.{ + .{ .node = @alignCast(@ptrCast(option)) }, + }); + } + + pub fn _remove(self: *HTMLOptionsCollection, index: u32) !void { + const Node = @import("../dom/node.zig").Node; + const option = (try self.proto.item(index)) orelse return; + _ = try Node._removeChild(@alignCast(@ptrCast(self.select)), option); + } + + fn appendOption(self: *HTMLOptionsCollection, option: *parser.Option) !void { + const Node = @import("../dom/node.zig").Node; + return Node.append(@alignCast(@ptrCast(self.select)), &.{ + .{ .node = @alignCast(@ptrCast(option)) }, + }); + } }; const testing = @import("../../testing.zig"); @@ -140,5 +247,32 @@ test "Browser.HTML.Select" { .{ "s.selectedIndex = -323", null }, .{ "s.selectedIndex", "-1" }, + + .{ "let options = s.options", null }, + .{ "options.length", "2" }, + .{ "options.item(1).value", "o2" }, + .{ "options.selectedIndex", "-1" }, + + .{ "let o3 = document.createElement('option');", null }, + .{ "o3.value = 'o3';", null }, + .{ "options.add(o3)", null }, + .{ "options.length", "3" }, + .{ "options.item(2).value", "o3" }, + + .{ "let o4 = document.createElement('option');", null }, + .{ "o4.value = 'o4';", null }, + .{ "options.add(o4, 1)", null }, + .{ "options.length", "4" }, + .{ "options.item(1).value", "o4" }, + + .{ "let o5 = document.createElement('option');", null }, + .{ "o5.value = 'o5';", null }, + .{ "options.add(o5, o3)", null }, + .{ "options.length", "5" }, + .{ "options.item(3).value", "o5" }, + + .{ "options.remove(3)", null }, + .{ "options.length", "4" }, + .{ "options.item(3).value", "o3" }, }, .{}); } diff --git a/src/browser/netsurf.zig b/src/browser/netsurf.zig index abec0859..19e5230e 100644 --- a/src/browser/netsurf.zig +++ b/src/browser/netsurf.zig @@ -2590,6 +2590,24 @@ pub fn optionGetValue(option: *Option) ![]const u8 { return strToData(s); } +pub fn optionSetLabel(input: *Option, label: []const u8) !void { + const err = c.dom_html_option_element_set_label(input, try strFromData(label)); + try DOMErr(err); +} + +pub fn optionGetLabel(option: *Option) ![]const u8 { + var s_: ?*String = null; + const err = c.dom_html_option_element_get_label(option, &s_); + try DOMErr(err); + const s = s_ orelse return ""; + return strToData(s); +} + +pub fn optionSetValue(input: *Option, value: []const u8) !void { + const err = c.dom_html_option_element_set_value(input, try strFromData(value)); + try DOMErr(err); +} + pub fn optionGetSelected(option: *Option) !bool { var selected: bool = false; const err = c.dom_html_option_element_get_selected(option, &selected); @@ -2597,11 +2615,38 @@ pub fn optionGetSelected(option: *Option) !bool { return selected; } +pub fn optionSetDisabled(option: *Option, disabled: bool) !void { + const err = c.dom_html_option_element_set_disabled(option, disabled); + try DOMErr(err); +} + +pub fn optionGetDisabled(option: *Option) !bool { + var disabled: bool = false; + const err = c.dom_html_option_element_get_disabled(option, &disabled); + try DOMErr(err); + return disabled; +} + pub fn optionSetSelected(option: *Option, selected: bool) !void { const err = c.dom_html_option_element_set_selected(option, selected); try DOMErr(err); } +pub fn optionGetText(option: *Option) ![]const u8 { + var s_: ?*String = null; + const err = c.dom_html_option_element_get_text(option, &s_); + try DOMErr(err); + const s = s_ orelse return ""; + return strToData(s); +} + +pub fn optionGetForm(option: *Option) !?*Form { + var form: ?*Form = null; + const err = c.dom_html_option_element_get_form(option, &form); + try DOMErr(err); + return form; +} + // HtmlCollection pub fn htmlCollectionGetLength(collection: *HTMLCollection) !u32 { var len: u32 = 0;