Skip to content

Commit 0387c45

Browse files
committed
Bug 1958985 - Don't allow a generated-content block as a "result frame" in GetNextPrevLineFromBlockFrame. r=layout-reviewers,emilio
If we return the generated-content block, PeekOffsetForLine will descend into it, and then determine it isn't a valid result, and move on from the desired target line. Excluding the generated-content block allows us to properly return the next (or previous) frame on the line instead. Differential Revision: https://phabricator.services.mozilla.com/D245902
1 parent cb62b6f commit 0387c45

File tree

2 files changed

+264
-3
lines changed

2 files changed

+264
-3
lines changed

layout/generic/nsIFrame.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9243,9 +9243,10 @@ static nsresult GetNextPrevLineFromBlockFrame(PeekOffsetStruct* aPos,
92439243
if (!aPos->FrameContentIsInAncestorLimiter(resultFrame)) {
92449244
return NS_ERROR_FAILURE;
92459245
}
9246-
// check to see if this is ANOTHER blockframe inside the other one if so
9247-
// then call into its lines
9248-
if (resultFrame->CanProvideLineIterator()) {
9246+
// Check to see if this is ANOTHER blockframe inside the other one that
9247+
// we should look inside.
9248+
if (resultFrame->CanProvideLineIterator() &&
9249+
IsRelevantBlockFrame(resultFrame)) {
92499250
aPos->mResultFrame = resultFrame;
92509251
return NS_OK;
92519252
}
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Test for caret movement around generated content</title>
6+
<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1958985">
7+
<script src="/resources/testharness.js"></script>
8+
<script src="/resources/testharnessreport.js"></script>
9+
<script src="/resources/testdriver.js"></script>
10+
<script src="/resources/testdriver-vendor.js"></script>
11+
<script src="/resources/testdriver-actions.js"></script>
12+
<script>
13+
"use strict";
14+
15+
function getRangeDescription(range) {
16+
function getNodeDescription(node) {
17+
if (!node) {
18+
return "null";
19+
}
20+
switch (node.nodeType) {
21+
case Node.TEXT_NODE:
22+
return `${node.nodeName} "${node.data}"`;
23+
case Node.ELEMENT_NODE:
24+
return `<${node.nodeName.toLowerCase()}>`;
25+
default:
26+
return `${node.nodeName}`;
27+
}
28+
}
29+
if (range === null) {
30+
return "null";
31+
}
32+
if (range === undefined) {
33+
return "undefined";
34+
}
35+
return range.startContainer == range.endContainer &&
36+
range.startOffset == range.endOffset
37+
? `(${getNodeDescription(range.startContainer)}, ${range.startOffset})`
38+
: `(${getNodeDescription(range.startContainer)}, ${
39+
range.startOffset
40+
}) - (${getNodeDescription(range.endContainer)}, ${range.endOffset})`;
41+
}
42+
43+
const kArrowLeft = "\uE012";
44+
const kArrowUp = "\uE013";
45+
const kArrowRight = "\uE014";
46+
const kArrowDown = "\uE015";
47+
48+
function sendKey(k) {
49+
return new test_driver.Actions().keyDown(k).keyUp(k).send();
50+
}
51+
52+
promise_test(async () => {
53+
await new Promise(resolve => {
54+
addEventListener("load", resolve, {once: true});
55+
});
56+
}, "Initializing tests");
57+
58+
promise_test(async t => {
59+
const editingHost = document.querySelector("#test1");
60+
editingHost.focus();
61+
const p = editingHost.querySelector("#a");
62+
63+
test(() => {
64+
assert_equals(
65+
getRangeDescription(getSelection().getRangeAt(0)),
66+
getRangeDescription({
67+
startContainer: editingHost.querySelector("#a").firstChild,
68+
startOffset: 0,
69+
endContainer: editingHost.querySelector("#a").firstChild,
70+
endOffset: 0,
71+
}),
72+
);
73+
}, `${t.name}: caret should start at the beginning of the first line`);
74+
75+
await sendKey(kArrowDown);
76+
test(() => {
77+
assert_equals(
78+
getRangeDescription(getSelection().getRangeAt(0)),
79+
getRangeDescription({
80+
startContainer: editingHost.querySelector("#one").firstChild,
81+
startOffset: 0,
82+
endContainer: editingHost.querySelector("#one").firstChild,
83+
endOffset: 0,
84+
}),
85+
);
86+
}, `${t.name}: arrow-down should move the caret to the start of the first list item`);
87+
88+
await sendKey(kArrowDown);
89+
test(() => {
90+
assert_equals(
91+
getRangeDescription(getSelection().getRangeAt(0)),
92+
getRangeDescription({
93+
startContainer: editingHost.querySelector("#two"),
94+
startOffset: 0,
95+
endContainer: editingHost.querySelector("#two"),
96+
endOffset: 0,
97+
}),
98+
);
99+
}, `${t.name}: arrow-down should move the caret to the second list item`);
100+
101+
await sendKey(kArrowDown);
102+
test(() => {
103+
assert_equals(
104+
getRangeDescription(getSelection().getRangeAt(0)),
105+
getRangeDescription({
106+
startContainer: editingHost.querySelector("#three").firstChild,
107+
startOffset: 0,
108+
endContainer: editingHost.querySelector("#three").firstChild,
109+
endOffset: 0,
110+
}),
111+
);
112+
}, `${t.name}: arrow-down should move the caret to the third list item`);
113+
114+
await sendKey(kArrowDown);
115+
test(() => {
116+
assert_equals(
117+
getRangeDescription(getSelection().getRangeAt(0)),
118+
getRangeDescription({
119+
startContainer: editingHost.querySelector("#z").firstChild,
120+
startOffset: 0,
121+
endContainer: editingHost.querySelector("#z").firstChild,
122+
endOffset: 0,
123+
}),
124+
);
125+
}, `${t.name}: arrow-down should move the caret to the final paragraph`);
126+
}, "Moving caret between list items using arrow keys");
127+
128+
promise_test(async t => {
129+
const editingHost = document.querySelector("#test2");
130+
editingHost.focus();
131+
const p = editingHost.querySelector("#before");
132+
133+
test(() => {
134+
assert_equals(
135+
getRangeDescription(getSelection().getRangeAt(0)),
136+
getRangeDescription({
137+
startContainer: editingHost.querySelector("#before").firstChild,
138+
startOffset: 0,
139+
endContainer: editingHost.querySelector("#before").firstChild,
140+
endOffset: 0,
141+
}),
142+
);
143+
}, `${t.name}: caret should start at the beginning of the first line`);
144+
145+
await sendKey(kArrowDown);
146+
test(() => {
147+
assert_equals(
148+
getRangeDescription(getSelection().getRangeAt(0)),
149+
getRangeDescription({
150+
startContainer: editingHost.querySelector("#quote").firstChild,
151+
startOffset: 0,
152+
endContainer: editingHost.querySelector("#quote").firstChild,
153+
endOffset: 0,
154+
}),
155+
);
156+
}, `${t.name}: arrow-down should move the caret to the start of the block quote`);
157+
158+
await sendKey(kArrowDown);
159+
test(() => {
160+
assert_equals(
161+
getRangeDescription(getSelection().getRangeAt(0)),
162+
getRangeDescription({
163+
startContainer: editingHost.querySelector("#after").firstChild,
164+
startOffset: 0,
165+
endContainer: editingHost.querySelector("#after").firstChild,
166+
endOffset: 0,
167+
}),
168+
);
169+
}, `${t.name}: arrow-down should move the caret to the final paragraph`);
170+
171+
await sendKey(kArrowLeft);
172+
test(() => {
173+
assert_equals(
174+
getRangeDescription(getSelection().getRangeAt(0)),
175+
getRangeDescription({
176+
startContainer: editingHost.querySelector("#quote").firstChild,
177+
startOffset: 5,
178+
endContainer: editingHost.querySelector("#quote").firstChild,
179+
endOffset: 5,
180+
}),
181+
);
182+
}, `${t.name}: arrow-left should move the caret to the end of the quote`);
183+
184+
await sendKey(kArrowUp);
185+
test(() => {
186+
assert_equals(
187+
getRangeDescription(getSelection().getRangeAt(0)),
188+
getRangeDescription({
189+
startContainer: editingHost.querySelector("#before").firstChild,
190+
startOffset: 10,
191+
endContainer: editingHost.querySelector("#before").firstChild,
192+
endOffset: 10,
193+
}),
194+
);
195+
}, `${t.name}: arrow-up should move the caret to into the first line`);
196+
197+
await sendKey(kArrowRight);
198+
test(() => {
199+
assert_equals(
200+
getRangeDescription(getSelection().getRangeAt(0)),
201+
getRangeDescription({
202+
startContainer: editingHost.querySelector("#before").firstChild,
203+
startOffset: 11,
204+
endContainer: editingHost.querySelector("#before").firstChild,
205+
endOffset: 11,
206+
}),
207+
);
208+
}, `${t.name}: arrow-right should move the caret forward by one character`);
209+
210+
await sendKey(kArrowDown);
211+
test(() => {
212+
assert_equals(
213+
getRangeDescription(getSelection().getRangeAt(0)),
214+
getRangeDescription({
215+
startContainer: editingHost.querySelector("#quote").firstChild,
216+
startOffset: 5,
217+
endContainer: editingHost.querySelector("#quote").firstChild,
218+
endOffset: 5,
219+
}),
220+
);
221+
}, `${t.name}: arrow-down should move the caret to the end of the quote`);
222+
}, "Moving caret past the block-quote using arrow keys");
223+
</script>
224+
<style>
225+
div {
226+
font-family: monospace;
227+
}
228+
ul {
229+
list-style-type: none;
230+
}
231+
li::before {
232+
content: "*\00a0";
233+
display: inline-block;
234+
}
235+
blockquote {
236+
margin-inline-start: 5ch;
237+
}
238+
blockquote::after {
239+
content: "\00a0*";
240+
display: inline-block;
241+
}
242+
</style>
243+
</head>
244+
<body>
245+
<div id=test1 contenteditable>
246+
<p id=a>abc</p>
247+
<ul>
248+
<li id=one>one</li>
249+
<li id=two><br></li>
250+
<li id=three>three</li>
251+
</ul>
252+
<p id=z>xyz</p>
253+
</div>
254+
<div id=test2 contenteditable>
255+
<p id=before>paragraph before the blockquote</p>
256+
<blockquote id=quote>quote</blockquote>
257+
<p id=after>after the blockquote</p>
258+
</div>
259+
</body>
260+
</html>

0 commit comments

Comments
 (0)