mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 07:03:29 +00:00
Merge pull request #937 from lightpanda-io/event_composedPath
Add ShadowRoot get/set innerHTML
This commit is contained in:
@@ -558,6 +558,8 @@ pub const Element = struct {
|
|||||||
.proto = fragment,
|
.proto = fragment,
|
||||||
};
|
};
|
||||||
state.shadow_root = sr;
|
state.shadow_root = sr;
|
||||||
|
parser.documentFragmentSetHost(sr.proto, @alignCast(@ptrCast(self)));
|
||||||
|
|
||||||
return sr;
|
return sr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,10 +17,12 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const dump = @import("../dump.zig");
|
||||||
const parser = @import("../netsurf.zig");
|
const parser = @import("../netsurf.zig");
|
||||||
|
|
||||||
const Env = @import("../env.zig").Env;
|
const Env = @import("../env.zig").Env;
|
||||||
const Page = @import("../page.zig").Page;
|
const Page = @import("../page.zig").Page;
|
||||||
|
const Node = @import("node.zig").Node;
|
||||||
const Element = @import("element.zig").Element;
|
const Element = @import("element.zig").Element;
|
||||||
const ElementUnion = @import("element.zig").Union;
|
const ElementUnion = @import("element.zig").Union;
|
||||||
|
|
||||||
@@ -56,6 +58,41 @@ pub const ShadowRoot = struct {
|
|||||||
pub fn set_adoptedStyleSheets(self: *ShadowRoot, sheets: Env.JsObject) !void {
|
pub fn set_adoptedStyleSheets(self: *ShadowRoot, sheets: Env.JsObject) !void {
|
||||||
self.adopted_style_sheets = try sheets.persist();
|
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");
|
const testing = @import("../../testing.zig");
|
||||||
@@ -94,8 +131,8 @@ test "Browser.DOM.ShadowRoot" {
|
|||||||
try runner.testCases(&.{
|
try runner.testCases(&.{
|
||||||
.{ "sr2.getElementById('conflict')", "null" },
|
.{ "sr2.getElementById('conflict')", "null" },
|
||||||
.{ "const n1 = document.createElement('div')", null },
|
.{ "const n1 = document.createElement('div')", null },
|
||||||
.{ "n1.id = 'conflict'", null},
|
.{ "n1.id = 'conflict'", null },
|
||||||
.{ "sr2.append(n1)", null},
|
.{ "sr2.append(n1)", null },
|
||||||
.{ "sr2.getElementById('conflict') == n1", "true" },
|
.{ "sr2.getElementById('conflict') == n1", "true" },
|
||||||
}, .{});
|
}, .{});
|
||||||
|
|
||||||
@@ -105,4 +142,14 @@ test "Browser.DOM.ShadowRoot" {
|
|||||||
.{ "acss.push(new CSSStyleSheet())", null },
|
.{ "acss.push(new CSSStyleSheet())", null },
|
||||||
.{ "sr2.adoptedStyleSheets.length", "1" },
|
.{ "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 generate = @import("../../runtime/generate.zig");
|
||||||
|
|
||||||
const Page = @import("../page.zig").Page;
|
const Page = @import("../page.zig").Page;
|
||||||
|
const Node = @import("../dom/node.zig").Node;
|
||||||
const DOMException = @import("../dom/exceptions.zig").DOMException;
|
const DOMException = @import("../dom/exceptions.zig").DOMException;
|
||||||
const EventTarget = @import("../dom/event_target.zig").EventTarget;
|
const EventTarget = @import("../dom/event_target.zig").EventTarget;
|
||||||
const EventTargetUnion = @import("../dom/event_target.zig").Union;
|
const EventTargetUnion = @import("../dom/event_target.zig").Union;
|
||||||
@@ -139,6 +140,64 @@ pub const Event = struct {
|
|||||||
pub fn _preventDefault(self: *parser.Event) !void {
|
pub fn _preventDefault(self: *parser.Event) !void {
|
||||||
return try parser.eventPreventDefault(self);
|
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 {
|
pub const EventHandler = struct {
|
||||||
@@ -446,4 +505,37 @@ test "Browser.Event" {
|
|||||||
.{ "nb", "2" },
|
.{ "nb", "2" },
|
||||||
.{ "document.removeEventListener('count', cbk)", "undefined" },
|
.{ "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)));
|
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
|
// Document Position
|
||||||
|
|
||||||
pub const DocumentPosition = enum(u32) {
|
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