mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-12-15 15:58:57 +00:00
Merge pull request #1242 from lightpanda-io/nikneym/element-apis
add `insertAdjacentHTML` and other variants
This commit is contained in:
54
src/browser/tests/document/insert_adjacent_element.html
Normal file
54
src/browser/tests/document/insert_adjacent_element.html
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<head id="the_head">
|
||||||
|
<title>Test Document Title</title>
|
||||||
|
<script src="../testing.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!-- This structure will get mutated by insertAdjacentElement test -->
|
||||||
|
<div id="insert-adjacent-element-outer-wrapper">
|
||||||
|
<div id="insert-adjacent-element-inner-wrapper">
|
||||||
|
<span></span>
|
||||||
|
<p>content</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script id=insertAdjacentElement>
|
||||||
|
// Insert "beforeend".
|
||||||
|
const wrapper = $("#insert-adjacent-element-inner-wrapper");
|
||||||
|
const h1 = document.createElement("h1");
|
||||||
|
h1.innerHTML = "title";
|
||||||
|
wrapper.insertAdjacentElement("beforeend", h1);
|
||||||
|
let newElement = wrapper.lastElementChild;
|
||||||
|
testing.expectEqual("H1", newElement.tagName);
|
||||||
|
testing.expectEqual("title", newElement.innerText);
|
||||||
|
|
||||||
|
// Insert "beforebegin".
|
||||||
|
const h2 = document.createElement("h2");
|
||||||
|
h2.innerHTML = "small title";
|
||||||
|
wrapper.insertAdjacentElement("beforebegin", h2);
|
||||||
|
newElement = wrapper.previousElementSibling;
|
||||||
|
testing.expectEqual("H2", newElement.tagName);
|
||||||
|
testing.expectEqual("small title", newElement.innerText);
|
||||||
|
|
||||||
|
// Insert "afterend".
|
||||||
|
const divAfterEnd = document.createElement("div");
|
||||||
|
divAfterEnd.id = "afterend";
|
||||||
|
divAfterEnd.innerHTML = "after end";
|
||||||
|
wrapper.insertAdjacentElement("afterend", divAfterEnd);
|
||||||
|
newElement = wrapper.nextElementSibling;
|
||||||
|
testing.expectEqual("DIV", newElement.tagName);
|
||||||
|
testing.expectEqual("after end", newElement.innerText);
|
||||||
|
testing.expectEqual("afterend", newElement.id);
|
||||||
|
|
||||||
|
// Insert "afterbegin".
|
||||||
|
const divAfterBegin = document.createElement("div");
|
||||||
|
divAfterBegin.className = "afterbegin";
|
||||||
|
divAfterBegin.innerHTML = "after begin";
|
||||||
|
wrapper.insertAdjacentElement("afterbegin", divAfterBegin);
|
||||||
|
newElement = wrapper.firstElementChild;
|
||||||
|
testing.expectEqual("DIV", newElement.tagName);
|
||||||
|
testing.expectEqual("after begin", newElement.innerText);
|
||||||
|
testing.expectEqual("afterbegin", newElement.className);
|
||||||
|
</script>
|
||||||
44
src/browser/tests/document/insert_adjacent_html.html
Normal file
44
src/browser/tests/document/insert_adjacent_html.html
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<head id="the_head">
|
||||||
|
<title>Test Document Title</title>
|
||||||
|
<script src="../testing.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!-- This structure will get mutated by insertAdjacentHTML test -->
|
||||||
|
<div id="insert-adjacent-html-outer-wrapper">
|
||||||
|
<div id="insert-adjacent-html-inner-wrapper">
|
||||||
|
<span></span>
|
||||||
|
<p>content</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script id=insertAdjacentHTML>
|
||||||
|
// Insert "beforeend".
|
||||||
|
const wrapper = $("#insert-adjacent-html-inner-wrapper");
|
||||||
|
wrapper.insertAdjacentHTML("beforeend", "<h1>title</h1>");
|
||||||
|
let newElement = wrapper.lastElementChild;
|
||||||
|
testing.expectEqual("H1", newElement.tagName);
|
||||||
|
testing.expectEqual("title", newElement.innerText);
|
||||||
|
|
||||||
|
// Insert "beforebegin".
|
||||||
|
wrapper.insertAdjacentHTML("beforebegin", "<h2>small title</h2>");
|
||||||
|
newElement = wrapper.previousElementSibling;
|
||||||
|
testing.expectEqual("H2", newElement.tagName);
|
||||||
|
testing.expectEqual("small title", newElement.innerText);
|
||||||
|
|
||||||
|
// Insert "afterend".
|
||||||
|
wrapper.insertAdjacentHTML("afterend", "<div id=\"afterend\">after end</div>");
|
||||||
|
newElement = wrapper.nextElementSibling;
|
||||||
|
testing.expectEqual("DIV", newElement.tagName);
|
||||||
|
testing.expectEqual("after end", newElement.innerText);
|
||||||
|
testing.expectEqual("afterend", newElement.id);
|
||||||
|
|
||||||
|
// Insert "afterbegin".
|
||||||
|
wrapper.insertAdjacentHTML("afterbegin", "<div class=\"afterbegin\">after begin</div><yy></yy>");
|
||||||
|
newElement = wrapper.firstElementChild;
|
||||||
|
testing.expectEqual("DIV", newElement.tagName);
|
||||||
|
testing.expectEqual("after begin", newElement.innerText);
|
||||||
|
testing.expectEqual("afterbegin", newElement.className);
|
||||||
|
</script>
|
||||||
49
src/browser/tests/document/insert_adjacent_text.html
Normal file
49
src/browser/tests/document/insert_adjacent_text.html
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<head id="the_head">
|
||||||
|
<title>Test Document Title</title>
|
||||||
|
<script src="../testing.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!-- This structure will get mutated by insertAdjacentText test -->
|
||||||
|
<div id="insert-adjacent-text-outer-wrapper">
|
||||||
|
<div id="insert-adjacent-text-inner-wrapper">
|
||||||
|
<span></span>
|
||||||
|
<p>content</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script id=insertAdjacentText>
|
||||||
|
const wrapper = $("#insert-adjacent-text-inner-wrapper");
|
||||||
|
|
||||||
|
// Insert "beforeend".
|
||||||
|
{
|
||||||
|
wrapper.insertAdjacentText("beforeend", "atlas, rise!");
|
||||||
|
const { nodeType, data } = wrapper.lastChild;
|
||||||
|
testing.expectEqual(nodeType, Node.TEXT_NODE);
|
||||||
|
testing.expectEqual(data, "atlas, rise!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert "beforebegin".
|
||||||
|
{
|
||||||
|
wrapper.insertAdjacentText("beforebegin", "before everything else");
|
||||||
|
const { nodeType, data } = wrapper.previousSibling;
|
||||||
|
testing.expectEqual(nodeType, Node.TEXT_NODE);
|
||||||
|
testing.expectEqual(data, "before everything else");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert "afterend".
|
||||||
|
{
|
||||||
|
wrapper.insertAdjacentText("afterend", "after end");
|
||||||
|
const { nodeType, data } = wrapper.nextSibling;
|
||||||
|
testing.expectEqual(nodeType, Node.TEXT_NODE);
|
||||||
|
testing.expectEqual(data, "after end");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert "afterbegin".
|
||||||
|
wrapper.insertAdjacentText("afterbegin", "after begin");
|
||||||
|
const { nodeType, data } = wrapper.firstChild;
|
||||||
|
testing.expectEqual(nodeType, Node.TEXT_NODE);
|
||||||
|
testing.expectEqual(data, "after begin");
|
||||||
|
</script>
|
||||||
@@ -416,6 +416,65 @@ pub fn attachShadow(self: *Element, mode_str: []const u8, page: *Page) !*ShadowR
|
|||||||
return shadow_root;
|
return shadow_root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn insertAdjacentHTML(
|
||||||
|
self: *Element,
|
||||||
|
position: []const u8,
|
||||||
|
/// TODO: Add support for XML parsing.
|
||||||
|
html_or_xml: []const u8,
|
||||||
|
page: *Page,
|
||||||
|
) !void {
|
||||||
|
// Create a new HTMLDocument.
|
||||||
|
const doc = try page._factory.document(@import("HTMLDocument.zig"){
|
||||||
|
._proto = undefined,
|
||||||
|
});
|
||||||
|
const doc_node = doc.asNode();
|
||||||
|
|
||||||
|
const Parser = @import("../parser/Parser.zig");
|
||||||
|
var parser = Parser.init(page.call_arena, doc_node, page);
|
||||||
|
parser.parse(html_or_xml);
|
||||||
|
// Check if there's parsing error.
|
||||||
|
if (parser.err) |_| return error.Invalid;
|
||||||
|
|
||||||
|
// We always get it wrapped like so:
|
||||||
|
// <html><head></head><body>{ ... }</body></html>
|
||||||
|
// None of the following can be null.
|
||||||
|
const maybe_html_node = doc_node.firstChild();
|
||||||
|
std.debug.assert(maybe_html_node != null);
|
||||||
|
const html_node = maybe_html_node orelse return;
|
||||||
|
|
||||||
|
const maybe_body_node = html_node.lastChild();
|
||||||
|
std.debug.assert(maybe_body_node != null);
|
||||||
|
const body = maybe_body_node orelse return;
|
||||||
|
|
||||||
|
const target_node, const prev_node = try self.asNode().findAdjacentNodes(position);
|
||||||
|
|
||||||
|
var iter = body.childrenIterator();
|
||||||
|
while (iter.next()) |child_node| {
|
||||||
|
_ = try target_node.insertBefore(child_node, prev_node, page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insertAdjacentElement(
|
||||||
|
self: *Element,
|
||||||
|
position: []const u8,
|
||||||
|
element: *Element,
|
||||||
|
page: *Page,
|
||||||
|
) !void {
|
||||||
|
const target_node, const prev_node = try self.asNode().findAdjacentNodes(position);
|
||||||
|
_ = try target_node.insertBefore(element.asNode(), prev_node, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insertAdjacentText(
|
||||||
|
self: *Element,
|
||||||
|
where: []const u8,
|
||||||
|
data: []const u8,
|
||||||
|
page: *Page,
|
||||||
|
) !void {
|
||||||
|
const text_node = try page.createTextNode(data);
|
||||||
|
const target_node, const prev_node = try self.asNode().findAdjacentNodes(where);
|
||||||
|
_ = try target_node.insertBefore(text_node, prev_node, page);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn setAttributeNode(self: *Element, attr: *Attribute, page: *Page) !?*Attribute {
|
pub fn setAttributeNode(self: *Element, attr: *Attribute, page: *Page) !?*Attribute {
|
||||||
if (attr._element) |el| {
|
if (attr._element) |el| {
|
||||||
if (el == self) {
|
if (el == self) {
|
||||||
@@ -1060,6 +1119,9 @@ pub const JsApi = struct {
|
|||||||
pub const removeAttributeNode = bridge.function(Element.removeAttributeNode, .{ .dom_exception = true });
|
pub const removeAttributeNode = bridge.function(Element.removeAttributeNode, .{ .dom_exception = true });
|
||||||
pub const shadowRoot = bridge.accessor(Element.getShadowRoot, null, .{});
|
pub const shadowRoot = bridge.accessor(Element.getShadowRoot, null, .{});
|
||||||
pub const attachShadow = bridge.function(_attachShadow, .{ .dom_exception = true });
|
pub const attachShadow = bridge.function(_attachShadow, .{ .dom_exception = true });
|
||||||
|
pub const insertAdjacentHTML = bridge.function(Element.insertAdjacentHTML, .{ .dom_exception = true });
|
||||||
|
pub const insertAdjacentElement = bridge.function(Element.insertAdjacentElement, .{ .dom_exception = true });
|
||||||
|
pub const insertAdjacentText = bridge.function(Element.insertAdjacentText, .{ .dom_exception = true });
|
||||||
|
|
||||||
const ShadowRootInit = struct {
|
const ShadowRootInit = struct {
|
||||||
mode: []const u8,
|
mode: []const u8,
|
||||||
|
|||||||
@@ -115,6 +115,54 @@ pub fn is(self: *Node, comptime T: type) ?*T {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Given a position, returns target and previous nodes required for
|
||||||
|
/// insertAdjacentHTML, insertAdjacentElement and insertAdjacentText.
|
||||||
|
/// * `target_node` is `*Node` (where we actually insert),
|
||||||
|
/// * `previous_node` is `?*Node`.
|
||||||
|
pub fn findAdjacentNodes(self: *Node, position: []const u8) !struct { *Node, ?*Node } {
|
||||||
|
// Prefer case-sensitive match.
|
||||||
|
// "beforeend" was the most common case in my tests; we might adjust the order
|
||||||
|
// depending on which ones websites prefer most.
|
||||||
|
if (std.mem.eql(u8, position, "beforeend")) {
|
||||||
|
return .{ self, null };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, position, "afterbegin")) {
|
||||||
|
// Get the first child; null indicates there are no children.
|
||||||
|
return .{ self, self.firstChild() };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, position, "beforebegin")) {
|
||||||
|
// The node must have a parent node in order to use this variant.
|
||||||
|
const parent_node = self.parentNode() orelse return error.NoModificationAllowed;
|
||||||
|
// Parent cannot be Document.
|
||||||
|
switch (parent_node._type) {
|
||||||
|
.document, .document_fragment => return error.NoModificationAllowed,
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{ parent_node, self };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, position, "afterend")) {
|
||||||
|
// The node must have a parent node in order to use this variant.
|
||||||
|
const parent_node = self.parentNode() orelse return error.NoModificationAllowed;
|
||||||
|
// Parent cannot be Document.
|
||||||
|
switch (parent_node._type) {
|
||||||
|
.document, .document_fragment => return error.NoModificationAllowed,
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the next sibling or null; null indicates our node is the only one.
|
||||||
|
return .{ parent_node, self.nextSibling() };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returned if:
|
||||||
|
// * position is not one of the four listed values.
|
||||||
|
// * The input is XML that is not well-formed.
|
||||||
|
return error.Syntax;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn firstChild(self: *const Node) ?*Node {
|
pub fn firstChild(self: *const Node) ?*Node {
|
||||||
const children = self._children orelse return null;
|
const children = self._children orelse return null;
|
||||||
return children.first();
|
return children.first();
|
||||||
|
|||||||
Reference in New Issue
Block a user