diff --git a/src/browser/tests/element/inner.html b/src/browser/tests/element/inner.html index ada3f7c1..1783020a 100644 --- a/src/browser/tests/element/inner.html +++ b/src/browser/tests/element/inner.html @@ -7,6 +7,9 @@ This is a
text +

Hello World

+

Hello + World

diff --git a/src/browser/webapi/CData.zig b/src/browser/webapi/CData.zig index a22a0d6c..cb17aaad 100644 --- a/src/browser/webapi/CData.zig +++ b/src/browser/webapi/CData.zig @@ -71,7 +71,8 @@ pub const RenderOpts = struct { }; // Replace successives whitespaces with one withespace. // Trims left and right according to the options. -pub fn render(self: *const CData, writer: *std.io.Writer, opts: RenderOpts) !void { +// Returns true if the string ends with a trimmed whitespace. +pub fn render(self: *const CData, writer: *std.io.Writer, opts: RenderOpts) !bool { var start: usize = 0; var prev_w: ?bool = null; var is_w: bool = undefined; @@ -110,11 +111,15 @@ pub fn render(self: *const CData, writer: *std.io.Writer, opts: RenderOpts) !voi // If the string contains only whitespaces, don't write it. if (start > 0 and opts.trim_right == false) { try writer.writeByte(' '); + } else { + return true; } } else { // last chunk is non whitespaces. try writer.writeAll(s[start..]); } + + return false; } pub fn setData(self: *CData, value: ?[]const u8, page: *Page) !void { @@ -288,19 +293,20 @@ test "WebApi: CData.render" { const TestCase = struct { value: []const u8, expected: []const u8, + result: bool = false, opts: RenderOpts = .{}, }; const test_cases = [_]TestCase{ - .{ .value = " ", .expected = "" }, - .{ .value = " ", .expected = "", .opts = .{ .trim_left = false, .trim_right = false } }, + .{ .value = " ", .expected = "", .result = true }, + .{ .value = " ", .expected = "", .opts = .{ .trim_left = false, .trim_right = false }, .result = true }, .{ .value = "foo bar", .expected = "foo bar" }, .{ .value = "foo bar", .expected = "foo bar" }, .{ .value = " foo bar", .expected = "foo bar" }, - .{ .value = "foo bar ", .expected = "foo bar" }, - .{ .value = " foo bar ", .expected = "foo bar" }, + .{ .value = "foo bar ", .expected = "foo bar", .result = true }, + .{ .value = " foo bar ", .expected = "foo bar", .result = true }, .{ .value = "foo\n\tbar", .expected = "foo bar" }, - .{ .value = "\tfoo bar baz \t\n yeah\r\n", .expected = "foo bar baz yeah" }, + .{ .value = "\tfoo bar baz \t\n yeah\r\n", .expected = "foo bar baz yeah", .result = true }, .{ .value = " foo bar", .expected = " foo bar", .opts = .{ .trim_left = false } }, .{ .value = "foo bar ", .expected = "foo bar ", .opts = .{ .trim_right = false } }, .{ .value = " foo bar ", .expected = " foo bar ", .opts = .{ .trim_left = false, .trim_right = false } }, @@ -317,8 +323,9 @@ test "WebApi: CData.render" { ._data = test_case.value, }; - try cdata.render(&buffer.writer, test_case.opts); + const result = try cdata.render(&buffer.writer, test_case.opts); try std.testing.expectEqualStrings(test_case.expected, buffer.written()); + try std.testing.expect(result == test_case.result); } } diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig index 91560010..f8ab2501 100644 --- a/src/browser/webapi/Element.zig +++ b/src/browser/webapi/Element.zig @@ -228,21 +228,46 @@ pub fn getNamespaceURI(self: *const Element) []const u8 { // innerText represents the **rendered** text content of a node and its // descendants. pub fn getInnerText(self: *Element, writer: *std.Io.Writer) !void { + var state = innerTextState{}; + return try self._getInnerText(writer, &state); +} +const innerTextState = struct { + pre_w: bool = false, + trim_left: bool = true, +}; +fn _getInnerText(self: *Element, writer: *std.Io.Writer, state: *innerTextState) !void { var it = self.asNode().childrenIterator(); while (it.next()) |child| { switch (child._type) { .element => |e| switch (e._type) { .html => |he| switch (he._type) { - .br => try writer.writeByte('\n'), - .script, .style, .template => continue, - else => try e.getInnerText(writer), // TODO check if elt is hidden. + .br => { + try writer.writeByte('\n'); + state.pre_w = false; // prevent a next pre space. + state.trim_left = true; + }, + .script, .style, .template => { + state.pre_w = false; // prevent a next pre space. + state.trim_left = true; + }, + else => try e._getInnerText(writer, state), // TODO check if elt is hidden. }, .svg => {}, }, .cdata => |c| switch (c._type) { - .comment => continue, - .text => try c.render(writer, .{ .trim_right = false, .trim_left = false }), - .cdata_section => try writer.writeAll(c._data), + .comment => { + state.pre_w = false; // prevent a next pre space. + state.trim_left = true; + }, + .text => { + if (state.pre_w) try writer.writeByte(' '); + state.pre_w = try c.render(writer, .{ .trim_left = state.trim_left }); + // if we had a pre space, trim left next one. + state.trim_left = state.pre_w; + }, + // CDATA sections should not be used within HTML. They are + // considered comments and are not displayed. + .cdata_section => {}, }, .document => {}, .document_type => {}, diff --git a/src/browser/webapi/element/html/Script.zig b/src/browser/webapi/element/html/Script.zig index 5b760d75..64e574e6 100644 --- a/src/browser/webapi/element/html/Script.zig +++ b/src/browser/webapi/element/html/Script.zig @@ -15,6 +15,7 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +const std = @import("std"); const log = @import("../../../../log.zig"); const js = @import("../../../js/js.zig"); @@ -93,6 +94,10 @@ pub fn getNoModule(self: *const Script) bool { return self.asConstElement().getAttributeSafe("nomodule") != null; } +pub fn setInnerText(self: *Script, text: []const u8, page: *Page) !void { + try self.asNode().setTextContent(text, page); +} + pub const JsApi = struct { pub const bridge = js.Bridge(Script); @@ -107,6 +112,12 @@ pub const JsApi = struct { pub const onload = bridge.accessor(Script.getOnLoad, Script.setOnLoad, .{}); pub const onerror = bridge.accessor(Script.getOnError, Script.setOnError, .{}); pub const noModule = bridge.accessor(Script.getNoModule, null, .{}); + pub const innerText = bridge.accessor(_innerText, Script.setInnerText, .{}); + fn _innerText(self: *Script, page: *const Page) ![]const u8 { + var buf = std.Io.Writer.Allocating.init(page.call_arena); + try self.asNode().getTextContent(&buf.writer); + return buf.written(); + } }; pub const Build = struct {