mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-12-16 08:18:59 +00:00
@@ -151,7 +151,7 @@ pub const Page = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if the page has a pointer to a document, dumps the HTML.
|
// if the page has a pointer to a document, dumps the HTML.
|
||||||
try Dump.htmlFile(self.doc.?, out);
|
try Dump.writeHTML(self.doc.?, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
// spec reference: https://html.spec.whatwg.org/#document-lifecycle
|
// spec reference: https://html.spec.whatwg.org/#document-lifecycle
|
||||||
|
|||||||
@@ -4,13 +4,15 @@ const File = std.fs.File;
|
|||||||
const parser = @import("../netsurf.zig");
|
const parser = @import("../netsurf.zig");
|
||||||
const Walker = @import("../dom/walker.zig").WalkerChildren;
|
const Walker = @import("../dom/walker.zig").WalkerChildren;
|
||||||
|
|
||||||
pub fn htmlFile(doc: *parser.Document, out: File) !void {
|
// writer must be a std.io.Writer
|
||||||
try out.writeAll("<!DOCTYPE html>\n");
|
pub fn writeHTML(doc: *parser.Document, writer: anytype) !void {
|
||||||
try nodeFile(parser.documentToNode(doc), out);
|
try writer.writeAll("<!DOCTYPE html>\n");
|
||||||
try out.writeAll("\n");
|
try writeNode(parser.documentToNode(doc), writer);
|
||||||
|
try writer.writeAll("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn nodeFile(root: *parser.Node, out: File) !void {
|
// writer must be a std.io.Writer
|
||||||
|
pub fn writeNode(root: *parser.Node, writer: anytype) !void {
|
||||||
const walker = Walker{};
|
const walker = Walker{};
|
||||||
var next: ?*parser.Node = null;
|
var next: ?*parser.Node = null;
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -19,8 +21,8 @@ fn nodeFile(root: *parser.Node, out: File) !void {
|
|||||||
.element => {
|
.element => {
|
||||||
// open the tag
|
// open the tag
|
||||||
const tag = try parser.nodeLocalName(next.?);
|
const tag = try parser.nodeLocalName(next.?);
|
||||||
try out.writeAll("<");
|
try writer.writeAll("<");
|
||||||
try out.writeAll(tag);
|
try writer.writeAll(tag);
|
||||||
|
|
||||||
// write the attributes
|
// write the attributes
|
||||||
const map = try parser.nodeGetAttributes(next.?);
|
const map = try parser.nodeGetAttributes(next.?);
|
||||||
@@ -28,40 +30,40 @@ fn nodeFile(root: *parser.Node, out: File) !void {
|
|||||||
var i: u32 = 0;
|
var i: u32 = 0;
|
||||||
while (i < ln) {
|
while (i < ln) {
|
||||||
const attr = try parser.namedNodeMapItem(map, i) orelse break;
|
const attr = try parser.namedNodeMapItem(map, i) orelse break;
|
||||||
try out.writeAll(" ");
|
try writer.writeAll(" ");
|
||||||
try out.writeAll(try parser.attributeGetName(attr));
|
try writer.writeAll(try parser.attributeGetName(attr));
|
||||||
try out.writeAll("=\"");
|
try writer.writeAll("=\"");
|
||||||
try out.writeAll(try parser.attributeGetValue(attr) orelse "");
|
try writer.writeAll(try parser.attributeGetValue(attr) orelse "");
|
||||||
try out.writeAll("\"");
|
try writer.writeAll("\"");
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
try out.writeAll(">");
|
try writer.writeAll(">");
|
||||||
|
|
||||||
// write the children
|
// write the children
|
||||||
// TODO avoid recursion
|
// TODO avoid recursion
|
||||||
try nodeFile(next.?, out);
|
try writeNode(next.?, writer);
|
||||||
|
|
||||||
// close the tag
|
// close the tag
|
||||||
try out.writeAll("</");
|
try writer.writeAll("</");
|
||||||
try out.writeAll(tag);
|
try writer.writeAll(tag);
|
||||||
try out.writeAll(">");
|
try writer.writeAll(">");
|
||||||
},
|
},
|
||||||
.text => {
|
.text => {
|
||||||
const v = try parser.nodeValue(next.?) orelse continue;
|
const v = try parser.nodeValue(next.?) orelse continue;
|
||||||
try out.writeAll(v);
|
try writer.writeAll(v);
|
||||||
},
|
},
|
||||||
.cdata_section => {
|
.cdata_section => {
|
||||||
const v = try parser.nodeValue(next.?) orelse continue;
|
const v = try parser.nodeValue(next.?) orelse continue;
|
||||||
try out.writeAll("<![CDATA[");
|
try writer.writeAll("<![CDATA[");
|
||||||
try out.writeAll(v);
|
try writer.writeAll(v);
|
||||||
try out.writeAll("]]>");
|
try writer.writeAll("]]>");
|
||||||
},
|
},
|
||||||
.comment => {
|
.comment => {
|
||||||
const v = try parser.nodeValue(next.?) orelse continue;
|
const v = try parser.nodeValue(next.?) orelse continue;
|
||||||
try out.writeAll("<!--");
|
try writer.writeAll("<!--");
|
||||||
try out.writeAll(v);
|
try writer.writeAll(v);
|
||||||
try out.writeAll("-->");
|
try writer.writeAll("-->");
|
||||||
},
|
},
|
||||||
// TODO handle processing instruction dump
|
// TODO handle processing instruction dump
|
||||||
.processing_instruction => continue,
|
.processing_instruction => continue,
|
||||||
@@ -81,8 +83,10 @@ fn nodeFile(root: *parser.Node, out: File) !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTMLFileTestFn is run by run_tests.zig
|
test "dump.writeHTML" {
|
||||||
pub fn HTMLFileTestFn(out: File) !void {
|
const out = try std.fs.openFileAbsolute("/dev/null", .{ .mode = .write_only });
|
||||||
|
defer out.close();
|
||||||
|
|
||||||
const file = try std.fs.cwd().openFile("test.html", .{});
|
const file = try std.fs.cwd().openFile("test.html", .{});
|
||||||
defer file.close();
|
defer file.close();
|
||||||
|
|
||||||
@@ -92,5 +96,5 @@ pub fn HTMLFileTestFn(out: File) !void {
|
|||||||
|
|
||||||
const doc = parser.documentHTMLToDocument(doc_html);
|
const doc = parser.documentHTMLToDocument(doc_html);
|
||||||
|
|
||||||
try htmlFile(doc, out);
|
try writeHTML(doc, out);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const checkCases = jsruntime.test_utils.checkCases;
|
|||||||
const Variadic = jsruntime.Variadic;
|
const Variadic = jsruntime.Variadic;
|
||||||
|
|
||||||
const collection = @import("html_collection.zig");
|
const collection = @import("html_collection.zig");
|
||||||
|
const writeNode = @import("../browser/dump.zig").writeNode;
|
||||||
|
|
||||||
const Node = @import("node.zig").Node;
|
const Node = @import("node.zig").Node;
|
||||||
const Walker = @import("walker.zig").WalkerDepthFirst;
|
const Walker = @import("walker.zig").WalkerDepthFirst;
|
||||||
@@ -78,6 +79,38 @@ pub const Element = struct {
|
|||||||
return try parser.nodeGetAttributes(parser.elementToNode(self));
|
return try parser.nodeGetAttributes(parser.elementToNode(self));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_innerHTML(self: *parser.Element, alloc: std.mem.Allocator) ![]const u8 {
|
||||||
|
var buf = std.ArrayList(u8).init(alloc);
|
||||||
|
defer buf.deinit();
|
||||||
|
|
||||||
|
try writeNode(parser.elementToNode(self), buf.writer());
|
||||||
|
// TODO express the caller owned the slice.
|
||||||
|
// https://github.com/lightpanda-io/jsruntime-lib/issues/195
|
||||||
|
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 {
|
pub fn _hasAttributes(self: *parser.Element) !bool {
|
||||||
return try parser.nodeHasAttributes(parser.elementToNode(self));
|
return try parser.nodeHasAttributes(parser.elementToNode(self));
|
||||||
}
|
}
|
||||||
@@ -420,4 +453,20 @@ pub fn testExecFn(
|
|||||||
.{ .src = "f.getAttributeNode('bar')", .ex = "null" },
|
.{ .src = "f.getAttributeNode('bar')", .ex = "null" },
|
||||||
};
|
};
|
||||||
try checkCases(js_env, &attrNode);
|
try checkCases(js_env, &attrNode);
|
||||||
|
|
||||||
|
var innerHTML = [_]Case{
|
||||||
|
.{ .src = "document.getElementById('para').innerHTML", .ex = " And" },
|
||||||
|
.{ .src = "document.getElementById('para-empty').innerHTML.trim()", .ex = "<span id=\"para-empty-child\"></span>" },
|
||||||
|
|
||||||
|
.{ .src = "let h = document.getElementById('para-empty')", .ex = "undefined" },
|
||||||
|
.{ .src = "const prev = h.innerHTML", .ex = "undefined" },
|
||||||
|
.{ .src = "h.innerHTML = '<p id=\"hello\">hello world</p>'", .ex = "<p id=\"hello\">hello world</p>" },
|
||||||
|
.{ .src = "h.innerHTML", .ex = "<p id=\"hello\">hello world</p>" },
|
||||||
|
.{ .src = "h.firstChild.nodeName", .ex = "P" },
|
||||||
|
.{ .src = "h.firstChild.id", .ex = "hello" },
|
||||||
|
.{ .src = "h.firstChild.textContent", .ex = "hello world" },
|
||||||
|
.{ .src = "h.innerHTML = prev; true", .ex = "true" },
|
||||||
|
.{ .src = "document.getElementById('para-empty').innerHTML.trim()", .ex = "<span id=\"para-empty-child\"></span>" },
|
||||||
|
};
|
||||||
|
try checkCases(js_env, &innerHTML);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -294,27 +294,33 @@ pub const Node = struct {
|
|||||||
// function must accept either node or string.
|
// function must accept either node or string.
|
||||||
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
|
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
|
||||||
pub fn replaceChildren(self: *parser.Node, nodes: ?Variadic(*parser.Node)) !void {
|
pub fn replaceChildren(self: *parser.Node, nodes: ?Variadic(*parser.Node)) !void {
|
||||||
|
// remove existing children
|
||||||
|
try removeChildren(self);
|
||||||
|
|
||||||
if (nodes == null) return;
|
if (nodes == null) return;
|
||||||
if (nodes.?.slice.len == 0) return;
|
if (nodes.?.slice.len == 0) return;
|
||||||
|
|
||||||
// remove existing children
|
|
||||||
if (try parser.nodeHasChildNodes(self)) {
|
|
||||||
const children = try parser.nodeGetChildNodes(self);
|
|
||||||
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.nodeRemoveChild(self, child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add new children
|
// add new children
|
||||||
for (nodes.?.slice) |node| {
|
for (nodes.?.slice) |node| {
|
||||||
_ = try parser.nodeAppendChild(self, node);
|
_ = try parser.nodeAppendChild(self, node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn removeChildren(self: *parser.Node) !void {
|
||||||
|
if (!try parser.nodeHasChildNodes(self)) return;
|
||||||
|
|
||||||
|
const children = try parser.nodeGetChildNodes(self);
|
||||||
|
const ln = try parser.nodeListLength(children);
|
||||||
|
var i: u32 = 0;
|
||||||
|
while (i < ln) {
|
||||||
|
defer i += 1;
|
||||||
|
// we always retrieve the 0 index child on purpose: libdom nodelist
|
||||||
|
// are dynamic. So the next child to remove is always as pos 0.
|
||||||
|
const child = try parser.nodeListItem(children, 0) orelse continue;
|
||||||
|
_ = try parser.nodeRemoveChild(self, child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn deinit(_: *parser.Node, _: std.mem.Allocator) void {}
|
pub fn deinit(_: *parser.Node, _: std.mem.Allocator) void {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1514,6 +1514,22 @@ pub const Video = struct { base: *c.dom_html_element };
|
|||||||
// Document Fragment
|
// Document Fragment
|
||||||
pub const DocumentFragment = c.dom_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
|
// Document Position
|
||||||
|
|
||||||
pub const DocumentPosition = enum(u2) {
|
pub const DocumentPosition = enum(u2) {
|
||||||
@@ -1826,9 +1842,40 @@ pub fn documentHTMLParse(reader: anytype, enc: ?[:0]const u8) !*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 = c.dom_hubbub_parser_params{
|
err = c.dom_hubbub_parser_create(¶ms, &parser, &doc);
|
||||||
.enc = null,
|
try parserErr(err);
|
||||||
|
defer c.dom_hubbub_parser_destroy(parser);
|
||||||
|
|
||||||
|
try parseData(parser.?, reader);
|
||||||
|
|
||||||
|
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 = parseParams(enc);
|
||||||
|
|
||||||
|
err = c.dom_hubbub_fragment_parser_create(¶ms, self, &parser, &fragment);
|
||||||
|
try parserErr(err);
|
||||||
|
defer c.dom_hubbub_parser_destroy(parser);
|
||||||
|
|
||||||
|
try parseData(parser.?, reader);
|
||||||
|
|
||||||
|
return @as(*DocumentFragment, @ptrCast(fragment.?));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseParams(enc: ?[:0]const u8) c.dom_hubbub_parser_params {
|
||||||
|
return .{
|
||||||
|
.enc = enc orelse null,
|
||||||
.fix_enc = true,
|
.fix_enc = true,
|
||||||
.msg = null,
|
.msg = null,
|
||||||
.script = null,
|
.script = null,
|
||||||
@@ -1836,13 +1883,10 @@ pub fn documentHTMLParse(reader: anytype, enc: ?[:0]const u8) !*DocumentHTML {
|
|||||||
.ctx = null,
|
.ctx = null,
|
||||||
.daf = null,
|
.daf = null,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (enc) |e| params.enc = e;
|
fn parseData(parser: *c.dom_hubbub_parser, reader: anytype) !void {
|
||||||
|
var err: c.hubbub_error = undefined;
|
||||||
err = c.dom_hubbub_parser_create(¶ms, &parser, &doc);
|
|
||||||
try parserErr(err);
|
|
||||||
defer c.dom_hubbub_parser_destroy(parser);
|
|
||||||
|
|
||||||
var buffer: [1024]u8 = undefined;
|
var buffer: [1024]u8 = undefined;
|
||||||
var ln = buffer.len;
|
var ln = buffer.len;
|
||||||
while (ln > 0) {
|
while (ln > 0) {
|
||||||
@@ -1860,8 +1904,6 @@ pub fn documentHTMLParse(reader: anytype, enc: ?[:0]const u8) !*DocumentHTML {
|
|||||||
|
|
||||||
err = c.dom_hubbub_parser_completed(parser);
|
err = c.dom_hubbub_parser_completed(parser);
|
||||||
try parserErr(err);
|
try parserErr(err);
|
||||||
|
|
||||||
return @as(*DocumentHTML, @ptrCast(doc.?));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// documentHTMLClose closes the document.
|
// documentHTMLClose closes the document.
|
||||||
|
|||||||
@@ -99,8 +99,11 @@ pub fn main() !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
const TestAsync = @import("async/test.zig");
|
const AsyncTest = @import("async/test.zig");
|
||||||
std.testing.refAllDecls(TestAsync);
|
std.testing.refAllDecls(AsyncTest);
|
||||||
|
|
||||||
|
const DumpTest = @import("browser/dump.zig");
|
||||||
|
std.testing.refAllDecls(DumpTest);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "jsruntime" {
|
test "jsruntime" {
|
||||||
@@ -138,15 +141,6 @@ test "bug document html parsing #4" {
|
|||||||
parser.documentHTMLClose(doc) catch {};
|
parser.documentHTMLClose(doc) catch {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const dump = @import("browser/dump.zig");
|
|
||||||
test "run browser tests" {
|
|
||||||
// const out = std.io.getStdOut();
|
|
||||||
const out = try std.fs.openFileAbsolute("/dev/null", .{ .mode = .write_only });
|
|
||||||
defer out.close();
|
|
||||||
|
|
||||||
try dump.HTMLFileTestFn(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Window is a libdom event target" {
|
test "Window is a libdom event target" {
|
||||||
var window = Window.create(null);
|
var window = Window.create(null);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user