mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-28 14:43:28 +00:00
Add ShadowRoot get/set innerHTML
Adds event.composedPath() This depends on https://github.com/lightpanda-io/libdom/pull/34
This commit is contained in:
@@ -558,6 +558,8 @@ pub const Element = struct {
|
||||
.proto = fragment,
|
||||
};
|
||||
state.shadow_root = sr;
|
||||
parser.documentFragmentSetHost(sr.proto, @alignCast(@ptrCast(self)));
|
||||
|
||||
return sr;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,10 +17,12 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const dump = @import("../dump.zig");
|
||||
const parser = @import("../netsurf.zig");
|
||||
|
||||
const Env = @import("../env.zig").Env;
|
||||
const Page = @import("../page.zig").Page;
|
||||
const Node = @import("node.zig").Node;
|
||||
const Element = @import("element.zig").Element;
|
||||
const ElementUnion = @import("element.zig").Union;
|
||||
|
||||
@@ -56,13 +58,48 @@ pub const ShadowRoot = struct {
|
||||
pub fn set_adoptedStyleSheets(self: *ShadowRoot, sheets: Env.JsObject) !void {
|
||||
self.adopted_style_sheets = try sheets.persist();
|
||||
}
|
||||
|
||||
pub fn get_innerHTML(self: *ShadowRoot, page: *Page) ![]const u8 {
|
||||
var buf = std.ArrayList(u8).init(page.call_arena);
|
||||
try dump.writeChildren(parser.documentFragmentToNode(self.proto), .{}, buf.writer());
|
||||
return buf.items;
|
||||
}
|
||||
|
||||
pub fn set_innerHTML(self: *ShadowRoot, str_: ?[]const u8) !void {
|
||||
const sr_doc = parser.documentFragmentToNode(self.proto);
|
||||
const doc = try parser.nodeOwnerDocument(sr_doc) orelse return parser.DOMError.WrongDocument;
|
||||
try Node.removeChildren(sr_doc);
|
||||
const str = str_ orelse return;
|
||||
|
||||
const fragment = try parser.documentParseFragmentFromStr(doc, str);
|
||||
const fragment_node = parser.documentFragmentToNode(fragment);
|
||||
|
||||
// Element.set_innerHTML also has some weirdness here. It isn't clear
|
||||
// what should and shouldn't be set. Whatever string you pass to libdom,
|
||||
// it always creates a full HTML document, with an html, head and body
|
||||
// element.
|
||||
// For ShadowRoot, it appears the only the children within the body should
|
||||
// be set.
|
||||
const html = try parser.nodeFirstChild(fragment_node) orelse return;
|
||||
const head = try parser.nodeFirstChild(html) orelse return;
|
||||
const body = try parser.nodeNextSibling(head) orelse return;
|
||||
|
||||
const children = try parser.nodeGetChildNodes(body);
|
||||
const ln = try parser.nodeListLength(children);
|
||||
for (0..ln) |_| {
|
||||
// always index 0, because nodeAppendChild moves the node out of
|
||||
// the nodeList and into the new tree
|
||||
const child = try parser.nodeListItem(children, 0) orelse continue;
|
||||
_ = try parser.nodeAppendChild(sr_doc, child);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const testing = @import("../../testing.zig");
|
||||
test "Browser.DOM.ShadowRoot" {
|
||||
defer testing.reset();
|
||||
|
||||
var runner = try testing.jsRunner(testing.tracking_allocator, .{ .html =
|
||||
var runner = try testing.jsRunner(testing.tracking_allocator, .{ .html =
|
||||
\\ <div id=conflict>nope</div>
|
||||
});
|
||||
defer runner.deinit();
|
||||
@@ -94,8 +131,8 @@ test "Browser.DOM.ShadowRoot" {
|
||||
try runner.testCases(&.{
|
||||
.{ "sr2.getElementById('conflict')", "null" },
|
||||
.{ "const n1 = document.createElement('div')", null },
|
||||
.{ "n1.id = 'conflict'", null},
|
||||
.{ "sr2.append(n1)", null},
|
||||
.{ "n1.id = 'conflict'", null },
|
||||
.{ "sr2.append(n1)", null },
|
||||
.{ "sr2.getElementById('conflict') == n1", "true" },
|
||||
}, .{});
|
||||
|
||||
@@ -105,4 +142,14 @@ test "Browser.DOM.ShadowRoot" {
|
||||
.{ "acss.push(new CSSStyleSheet())", null },
|
||||
.{ "sr2.adoptedStyleSheets.length", "1" },
|
||||
}, .{});
|
||||
|
||||
try runner.testCases(&.{
|
||||
.{ "sr1.innerHTML = '<p>hello</p>'", null },
|
||||
.{ "sr1.innerHTML", "<p>hello</p>" },
|
||||
.{ "sr1.querySelector('*')", "[object HTMLParagraphElement]" },
|
||||
|
||||
.{ "sr1.innerHTML = null", null },
|
||||
.{ "sr1.innerHTML", "" },
|
||||
.{ "sr1.querySelector('*')", "null" },
|
||||
}, .{});
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ const parser = @import("../netsurf.zig");
|
||||
const generate = @import("../../runtime/generate.zig");
|
||||
|
||||
const Page = @import("../page.zig").Page;
|
||||
const Node = @import("../dom/node.zig").Node;
|
||||
const DOMException = @import("../dom/exceptions.zig").DOMException;
|
||||
const EventTarget = @import("../dom/event_target.zig").EventTarget;
|
||||
const EventTargetUnion = @import("../dom/event_target.zig").Union;
|
||||
@@ -139,6 +140,64 @@ pub const Event = struct {
|
||||
pub fn _preventDefault(self: *parser.Event) !void {
|
||||
return try parser.eventPreventDefault(self);
|
||||
}
|
||||
|
||||
pub fn _composedPath(self: *parser.Event, page: *Page) ![]const EventTargetUnion {
|
||||
const et_ = try parser.eventTarget(self);
|
||||
const et = et_ orelse return &.{};
|
||||
|
||||
var node: ?*parser.Node = switch (try parser.eventTargetInternalType(et)) {
|
||||
.libdom_node => @as(*parser.Node, @ptrCast(et)),
|
||||
.plain => parser.eventTargetToNode(et),
|
||||
else => {
|
||||
// Window, XHR, MessagePort, etc...no path beyond the event itself
|
||||
return &.{try EventTarget.toInterface(et, page)};
|
||||
},
|
||||
};
|
||||
|
||||
const arena = page.call_arena;
|
||||
var path: std.ArrayListUnmanaged(EventTargetUnion) = .empty;
|
||||
while (node) |n| {
|
||||
try path.append(arena, .{
|
||||
.node = try Node.toInterface(n),
|
||||
});
|
||||
|
||||
node = try parser.nodeParentNode(n);
|
||||
if (node == null and try parser.nodeType(n) == .document_fragment) {
|
||||
// we have a non-continuous hook from a shadowroot to its host (
|
||||
// it's parent element). libdom doesn't really support ShdowRoots
|
||||
// and, for the most part, that works out well since it naturally
|
||||
// provides isolation. But events don't follow the same
|
||||
// shadowroot isolation as most other things, so, if this is
|
||||
// a parent-less document fragment, we need to check if it has
|
||||
// a host.
|
||||
if (parser.documentFragmentGetHost(@ptrCast(n))) |host| {
|
||||
node = host;
|
||||
|
||||
// If a document fragment has a host, then that host
|
||||
// _has_ to have a state and that state _has_ to have
|
||||
// a shadow_root field. All of this is set in Element._attachShadow
|
||||
if (page.getNodeState(host).?.shadow_root.?.mode == .closed) {
|
||||
// if the shadow root is closed, then the composedPath
|
||||
// starts at the host element.
|
||||
path.clearRetainingCapacity();
|
||||
}
|
||||
} else {
|
||||
// Our document fragement has no parent and no host, we
|
||||
// can break out of the loop.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (path.getLastOrNull()) |last| {
|
||||
// the Window isn't part of the DOM hierarchy, but for events, it
|
||||
// is, so we need to glue it on.
|
||||
if (last.node == .HTMLDocument and last.node.HTMLDocument == page.window.document) {
|
||||
try path.append(arena, .{ .node = .{ .Window = &page.window } });
|
||||
}
|
||||
}
|
||||
return path.items;
|
||||
}
|
||||
};
|
||||
|
||||
pub const EventHandler = struct {
|
||||
@@ -446,4 +505,37 @@ test "Browser.Event" {
|
||||
.{ "nb", "2" },
|
||||
.{ "document.removeEventListener('count', cbk)", "undefined" },
|
||||
}, .{});
|
||||
|
||||
try runner.testCases(&.{
|
||||
.{ "new Event('').composedPath()", "" },
|
||||
.{
|
||||
\\ let div1 = document.createElement('div');
|
||||
\\ let sr1 = div1.attachShadow({mode: 'open'});
|
||||
\\ sr1.innerHTML = "<p id=srp1></p>";
|
||||
\\ document.getElementsByTagName('body')[0].appendChild(div1);
|
||||
\\ let cp = null;
|
||||
\\ div1.addEventListener('click', (e) => {
|
||||
\\ cp = e.composedPath().map((n) => n.id || n.nodeName || n.toString());
|
||||
\\ });
|
||||
\\ sr1.getElementById('srp1').click();
|
||||
\\ cp.join(', ');
|
||||
,
|
||||
"srp1, #document-fragment, DIV, BODY, HTML, #document, [object Window]",
|
||||
},
|
||||
|
||||
.{
|
||||
\\ let div2 = document.createElement('div');
|
||||
\\ let sr2 = div2.attachShadow({mode: 'closed'});
|
||||
\\ sr2.innerHTML = "<p id=srp2></p>";
|
||||
\\ document.getElementsByTagName('body')[0].appendChild(div2);
|
||||
\\ cp = null;
|
||||
\\ div2.addEventListener('click', (e) => {
|
||||
\\ cp = e.composedPath().map((n) => n.id || n.nodeName || n.toString());
|
||||
\\ });
|
||||
\\ sr2.getElementById('srp2').click();
|
||||
\\ cp.join(', ');
|
||||
,
|
||||
"DIV, BODY, HTML, #document, [object Window]",
|
||||
},
|
||||
}, .{});
|
||||
}
|
||||
|
||||
@@ -1993,6 +1993,15 @@ pub inline fn documentFragmentToNode(doc: *DocumentFragment) *Node {
|
||||
return @as(*Node, @alignCast(@ptrCast(doc)));
|
||||
}
|
||||
|
||||
pub fn documentFragmentGetHost(frag: *DocumentFragment) ?*Node {
|
||||
var node: ?*NodeExternal = undefined;
|
||||
c._dom_document_fragment_get_host(frag, &node);
|
||||
return if (node) |n| @ptrCast(n) else null;
|
||||
}
|
||||
pub fn documentFragmentSetHost(frag: *DocumentFragment, host: *Node) void {
|
||||
c._dom_document_fragment_set_host(frag, host);
|
||||
}
|
||||
|
||||
// Document Position
|
||||
|
||||
pub const DocumentPosition = enum(u32) {
|
||||
|
||||
2
vendor/netsurf/libdom
vendored
2
vendor/netsurf/libdom
vendored
Submodule vendor/netsurf/libdom updated: 0c590b265a...c0df458132
Reference in New Issue
Block a user