diff --git a/src/dom/element.zig b/src/dom/element.zig index 792bbcb9..27d4870f 100644 --- a/src/dom/element.zig +++ b/src/dom/element.zig @@ -89,6 +89,28 @@ pub const Element = struct { return buf.toOwnedSlice(); } + pub fn set_innerHTML(self: *parser.Element, str: []const u8) !void { + const node = parser.elementToNode(self); + const doc = try parser.nodeOwnerDocument(node) orelse return parser.DOMError.WrongDocument; + // parse the fragment + const fragment = try parser.documentParseFragmentFromStr(doc, str); + + // remove existing children + try Node.removeChildren(node); + + // get fragment body children + const children = try parser.documentFragmentBodyChildren(fragment) orelse return; + + // append children to the node + const ln = try parser.nodeListLength(children); + var i: u32 = 0; + while (i < ln) { + defer i += 1; + const child = try parser.nodeListItem(children, i) orelse continue; + _ = try parser.nodeAppendChild(node, child); + } + } + pub fn _hasAttributes(self: *parser.Element) !bool { return try parser.nodeHasAttributes(parser.elementToNode(self)); } @@ -435,6 +457,11 @@ pub fn testExecFn( var innerHTML = [_]Case{ .{ .src = "document.getElementById('para').innerHTML", .ex = " And" }, .{ .src = "document.getElementById('para-empty').innerHTML.trim()", .ex = "" }, + + .{ .src = "let h = document.getElementById('para-empty')", .ex = "undefined" }, + .{ .src = "const prev = h.innerHTML", .ex = "undefined" }, + .{ .src = "h.innerHTML = prev; true", .ex = "true" }, + .{ .src = "document.getElementById('para-empty').innerHTML.trim()", .ex = "" }, }; try checkCases(js_env, &innerHTML); } diff --git a/src/netsurf.zig b/src/netsurf.zig index 442a0fb7..a3ef1a8f 100644 --- a/src/netsurf.zig +++ b/src/netsurf.zig @@ -1514,6 +1514,22 @@ pub const Video = struct { base: *c.dom_html_element }; // Document Fragment pub const DocumentFragment = c.dom_document_fragment; +pub inline fn documentFragmentToNode(doc: *DocumentFragment) *Node { + return @as(*Node, @ptrCast(doc)); +} + +pub fn documentFragmentBodyChildren(doc: *DocumentFragment) !?*NodeList { + const node = documentFragmentToNode(doc); + const html = try nodeFirstChild(node) orelse return null; + // TODO unref + const head = try nodeFirstChild(html) orelse return null; + // TODO unref + const body = try nodeNextSibling(head) orelse return null; + // TODO unref + + return try nodeGetChildNodes(body); +} + // Document Position pub const DocumentPosition = enum(u2) { @@ -1859,6 +1875,53 @@ pub fn documentHTMLParse(reader: anytype, enc: ?[:0]const u8) !*DocumentHTML { return @as(*DocumentHTML, @ptrCast(doc.?)); } +pub fn documentParseFragmentFromStr(self: *Document, str: []const u8) !*DocumentFragment { + var fbs = std.io.fixedBufferStream(str); + return try documentParseFragment(self, fbs.reader(), "UTF-8"); +} + +pub fn documentParseFragment(self: *Document, reader: anytype, enc: ?[:0]const u8) !*DocumentFragment { + var parser: ?*c.dom_hubbub_parser = undefined; + var fragment: ?*c.dom_document_fragment = undefined; + var err: c.hubbub_error = undefined; + + var params = c.dom_hubbub_parser_params{ + .enc = null, + .fix_enc = true, + .msg = null, + .script = null, + .enable_script = false, + .ctx = null, + .daf = null, + }; + + if (enc) |e| params.enc = e; + + err = c.dom_hubbub_fragment_parser_create(¶ms, self, &parser, &fragment); + try parserErr(err); + defer c.dom_hubbub_parser_destroy(parser); + + var buffer: [1024]u8 = undefined; + var ln = buffer.len; + while (ln > 0) { + ln = try reader.read(&buffer); + err = c.dom_hubbub_parser_parse_chunk(parser, &buffer, ln); + // TODO handle encoding change error return. + // When the HTML contains a META tag with a different encoding than the + // original one, a c.DOM_HUBBUB_HUBBUB_ERR_ENCODINGCHANGE error is + // returned. + // In this case, we must restart the parsing with the new detected + // encoding. The detected encoding is stored in the document and we can + // get it with documentGetInputEncoding(). + try parserErr(err); + } + + err = c.dom_hubbub_parser_completed(parser); + try parserErr(err); + + return @as(*DocumentFragment, @ptrCast(fragment.?)); +} + // documentHTMLClose closes the document. pub fn documentHTMLClose(doc: *DocumentHTML) !void { const err = documentHTMLVtable(doc).close.?(doc);