mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-10-28 08:36:17 +08:00
Merge pull request #4222 from lightpanda-io/range-getClientRects
webapi: Fix Range.getClientRects
This commit is contained in:
2 files changed
+127
-8
No files matched your search
@@ -1,4 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<meta name=lp-mode content=test>
|
||||
<script src="testing.js"></script>
|
||||
<body>
|
||||
<div id="test-content">
|
||||
@@ -1196,11 +1197,53 @@
|
||||
|
||||
<script id=getClientRects_element>
|
||||
{
|
||||
// One rect per selected element whose parent isn't selected, plus one per
|
||||
// selected piece of text, in tree order
|
||||
const host = document.createElement('div');
|
||||
document.body.appendChild(host);
|
||||
host.innerHTML = '<p><span>a</span>b</p>';
|
||||
const p = host.firstChild;
|
||||
const span = p.firstChild;
|
||||
const range = new Range();
|
||||
const p = document.getElementById('p1');
|
||||
|
||||
// span, its text, b
|
||||
range.selectNodeContents(p);
|
||||
const rects = range.getClientRects();
|
||||
const elemRects = p.getClientRects();
|
||||
testing.expectEqual(elemRects.length, rects.length);
|
||||
let rects = range.getClientRects();
|
||||
testing.expectEqual(3, rects.length);
|
||||
testing.expectEqual(span.getBoundingClientRect().width, rects[0].width);
|
||||
|
||||
// p covers span, so: p, a, b
|
||||
range.selectNode(p);
|
||||
rects = range.getClientRects();
|
||||
testing.expectEqual(3, rects.length);
|
||||
testing.expectEqual(p.getBoundingClientRect().width, rects[0].width);
|
||||
|
||||
// From inside span: only text, span is partly selected
|
||||
range.setStart(span.firstChild, 0);
|
||||
range.setEnd(p.lastChild, 1);
|
||||
testing.expectEqual(2, range.getClientRects().length);
|
||||
|
||||
// An ancestor of the end is partly selected too: span, ab, c
|
||||
host.innerHTML = '<div><span>ab</span><i>cd</i></div>';
|
||||
const div = host.firstChild;
|
||||
range.setStart(div, 0);
|
||||
range.setEnd(div.lastChild.firstChild, 1);
|
||||
testing.expectEqual(3, range.getClientRects().length);
|
||||
|
||||
// A partial text rect measures only its part
|
||||
const text = div.lastChild.firstChild;
|
||||
range.setStart(text, 0);
|
||||
range.setEnd(text, 1);
|
||||
const part = range.getClientRects();
|
||||
testing.expectEqual(1, part.length);
|
||||
range.setEnd(text, 2);
|
||||
testing.expectTrue(range.getClientRects()[0].width > part[0].width);
|
||||
|
||||
// Comments and boxless elements add nothing
|
||||
host.innerHTML = '<p>a<!--x--><span style="display:none">x</span>b</p>';
|
||||
range.selectNodeContents(host.firstChild);
|
||||
testing.expectEqual(2, range.getClientRects().length);
|
||||
|
||||
host.remove();
|
||||
}
|
||||
</script>
|
||||
@@ -835,11 +835,87 @@ pub fn getBoundingClientRect(self: *const Range, frame: *Frame) !*DOMRect {
|
||||
return DOMRect.create(rect, frame._factory);
|
||||
}
|
||||
|
||||
// One rect per selected element whose parent isn't selected, and one per
|
||||
// selected piece of text, including text inside those elements.
|
||||
fn getClientRects(self: *const Range, frame: *Frame) ![]*DOMRect {
|
||||
const rect = try self.clientRect(frame) orelse return &.{};
|
||||
const rects = try frame.local_arena.alloc(*DOMRect, 1);
|
||||
rects[0] = try DOMRect.create(rect, frame._factory);
|
||||
return rects;
|
||||
if (self._proto.getCollapsed()) {
|
||||
return &.{};
|
||||
}
|
||||
const start_node = self._proto._start_container;
|
||||
const end_node = self._proto._end_container;
|
||||
const start_offset = self._proto._start_offset;
|
||||
const end_offset = self._proto._end_offset;
|
||||
|
||||
var rects: std.ArrayList(*DOMRect) = .empty;
|
||||
|
||||
if (start_node == end_node) {
|
||||
if (start_node.is(Node.CData)) |cdata| {
|
||||
try appendTextRect(&rects, cdata, start_offset, end_offset, frame);
|
||||
return rects.items;
|
||||
}
|
||||
}
|
||||
|
||||
if (start_node.is(Node.CData)) |cdata| {
|
||||
try appendTextRect(&rects, cdata, start_offset, null, frame);
|
||||
}
|
||||
|
||||
const root = self._proto.getCommonAncestorContainer();
|
||||
var current: ?*Node = if (start_node.is(Node.CData) != null)
|
||||
nextInTreeOrder(start_node, root)
|
||||
else
|
||||
start_node.getChildAt(start_offset) orelse nextAfterSubtree(start_node, root);
|
||||
const walk_end: ?*Node = if (end_node.is(Node.CData) != null)
|
||||
end_node
|
||||
else
|
||||
end_node.getChildAt(end_offset) orelse nextAfterSubtree(end_node, root);
|
||||
|
||||
// The last element whose rect was added: its descendants add only text
|
||||
var covering: ?*Node = null;
|
||||
while (current) |node| : (current = nextInTreeOrder(node, root)) {
|
||||
if (node == walk_end) {
|
||||
break;
|
||||
}
|
||||
if (node.is(Node.CData)) |cdata| {
|
||||
try appendTextRect(&rects, cdata, 0, null, frame);
|
||||
continue;
|
||||
}
|
||||
const el = node.is(Node.Element) orelse continue;
|
||||
if (covering) |c| {
|
||||
if (c.contains(node)) {
|
||||
continue;
|
||||
}
|
||||
covering = null;
|
||||
}
|
||||
// An ancestor of the end boundary is only partly selected
|
||||
if (node.contains(end_node)) {
|
||||
continue;
|
||||
}
|
||||
covering = node;
|
||||
if (el.isVisible(frame)) {
|
||||
try rects.append(frame.local_arena, try DOMRect.create(el.boundingClientRectValuesForVisible(frame), frame._factory));
|
||||
}
|
||||
}
|
||||
|
||||
if (start_node != end_node) {
|
||||
if (end_node.is(Node.CData)) |cdata| {
|
||||
try appendTextRect(&rects, cdata, 0, end_offset, frame);
|
||||
}
|
||||
}
|
||||
return rects.items;
|
||||
}
|
||||
|
||||
// The rect of cdata's text between two UTF-16 offsets; a null end is its end.
|
||||
fn appendTextRect(rects: *std.ArrayList(*DOMRect), cdata: *Node.CData, start: u32, end: ?u32, frame: *Frame) !void {
|
||||
if (isCommentOrPI(cdata)) {
|
||||
return;
|
||||
}
|
||||
const parent = cdata.asNode().parentNode() orelse return;
|
||||
const element = layoutElement(parent) orelse return;
|
||||
const data = cdata.getData().str();
|
||||
const s = byteOffset(data, start);
|
||||
const e = if (end) |offset| byteOffset(data, offset) else data.len;
|
||||
const rect = measureText(element, data[s..e], frame) orelse return;
|
||||
try rects.append(frame.local_arena, try DOMRect.create(rect, frame._factory));
|
||||
}
|
||||
|
||||
// null when nothing in the range has a box
|
||||
|
||||
Reference in new issue
Block a user