Skip to content

Commit 62437d9

Browse files
masayuki-nakanomoz-wptsync-bot
authored andcommitted
part 1: Make nsIFrame treat editable nodes always selectable
As tested by `css/css-ui/user-select-none-in-editable.html`, we should treat editable nodes as selectable even if `user-select: none` is specified. Then, some tests start failing due to an existing bug of caret move when an inline element starts/ends with invisible white-spaces. Therefore, to make them keep passing, this fixes the bug of `nsTextFrame::PeekOffsetCharacter()`. Differential Revision: https://phabricator.services.mozilla.com/D272608 bugzilla-url: https://bugzilla.mozilla.org/show_bug.cgi?id=1998858 gecko-commit: 66276ea4e5104488a702aaf7d20ac76e56c1597f gecko-reviewers: emilio, jfkthame, layout-reviewers
1 parent 706aaed commit 62437d9

File tree

1 file changed

+329
-0
lines changed

1 file changed

+329
-0
lines changed
Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Test Selection.modify("move", "left" or "right", "character") around inline element boundaries</title>
6+
<script src="/resources/testharness.js"></script>
7+
<script src="/resources/testharnessreport.js"></script>
8+
<script src="../../editing/include/editor-test-utils.js"></script>
9+
<script>
10+
"use strict";
11+
12+
addEventListener("load", () => {
13+
const testContainer = document.getElementById("testBody");
14+
function stringifySelectionRanges() {
15+
let result = "[";
16+
for (let i = 0; i < getSelection().rangeCount; i++) {
17+
const range = getSelection().getRangeAt(i);
18+
if (result != "[") {
19+
result += ", "
20+
}
21+
result += EditorTestUtils.getRangeDescription(range);
22+
}
23+
return result + "]";
24+
}
25+
function stringifyExpectedRange(expectedRange) {
26+
return `[${EditorTestUtils.getRangeDescription({
27+
startContainer: expectedRange.container,
28+
startOffset: expectedRange.offset,
29+
endContainer: expectedRange.container,
30+
endOffset: expectedRange.offset,
31+
})}]`;
32+
}
33+
for (const data of [
34+
// Basic tests. If moving caret from middle of a Text, shouldn't go out different Text.
35+
// This allows users preserve style at previous cursor position for the new text.
36+
{
37+
innerHTML: "ab[]c<span>def</span>",
38+
direction: "right",
39+
expectedResult: function () {
40+
return { container: testContainer.firstChild, offset: "abc".length };
41+
},
42+
},
43+
{
44+
innerHTML: "abc[]<span>def</span>",
45+
direction: "right",
46+
expectedResult: function () {
47+
return { container: testContainer.querySelector("span").firstChild, offset: "d".length };
48+
},
49+
},
50+
{
51+
innerHTML: "<b>ab[]c</b><i>def</i>",
52+
direction: "right",
53+
expectedResult: function () {
54+
return { container: testContainer.querySelector("b").firstChild, offset: "abc".length };
55+
},
56+
},
57+
{
58+
innerHTML: "<b>abc[]</b><i>def</i>",
59+
direction: "right",
60+
expectedResult: function () {
61+
return { container: testContainer.querySelector("i").firstChild, offset: "d".length };
62+
},
63+
},
64+
{
65+
innerHTML: "abc<span>d[]ef</span>",
66+
direction: "left",
67+
expectedResult: function () {
68+
return { container: testContainer.querySelector("span").firstChild, offset: 0 };
69+
},
70+
},
71+
{
72+
innerHTML: "abc<span>[]def</span>",
73+
direction: "left",
74+
expectedResult: function () {
75+
return { container: testContainer.firstChild, offset: "ab".length };
76+
},
77+
},
78+
{
79+
innerHTML: "<b>abc</b><i>d[]ef</i>",
80+
direction: "left",
81+
expectedResult: function () {
82+
return { container: testContainer.querySelector("i").firstChild, offset: 0 };
83+
},
84+
},
85+
{
86+
innerHTML: "<b>abc</b><i>[]def</i>",
87+
direction: "left",
88+
expectedResult: function () {
89+
return { container: testContainer.querySelector("b").firstChild, offset: "ab".length };
90+
},
91+
},
92+
// Don't skip visible white-space around the inline element boundaries.
93+
{
94+
innerHTML: "abc[] <span>def</span>",
95+
direction: "right",
96+
expectedResult: function () {
97+
return { container: testContainer.firstChild, offset: "abc ".length };
98+
},
99+
},
100+
{
101+
innerHTML: "abc[]<span> def</span>",
102+
direction: "right",
103+
expectedResult: function () {
104+
return { container: testContainer.querySelector("span").firstChild, offset: " ".length };
105+
},
106+
},
107+
{
108+
innerHTML: "abc <span>[]def</span>",
109+
direction: "left",
110+
expectedResult: function () {
111+
return { container: testContainer.firstChild, offset: "abc".length };
112+
},
113+
},
114+
{
115+
innerHTML: "abc<span> []def</span>",
116+
direction: "left",
117+
expectedResult: function () {
118+
return { container: testContainer.querySelector("span").firstChild, offset: 0 };
119+
},
120+
},
121+
122+
// Skip invisible white-spaces around the inline element boundaries.
123+
124+
// Only the first white-space should be visible when multiple spaces are collapsed.
125+
// Therefore, the following tests expect that selection is collapsed after the first white-space.
126+
{
127+
innerHTML: "abc[] <span>def</span>",
128+
direction: "right",
129+
expectedResult: function () {
130+
return { container: testContainer.firstChild, offset: "abc ".length };
131+
},
132+
},
133+
{
134+
innerHTML: "abc[] <span> def</span>",
135+
direction: "right",
136+
expectedResult: function () {
137+
return { container: testContainer.firstChild, offset: "abc ".length };
138+
},
139+
},
140+
{
141+
innerHTML: "abc[]<span> def</span>",
142+
direction: "right",
143+
expectedResult: function () {
144+
return { container: testContainer.querySelector("span").firstChild, offset: " ".length };
145+
},
146+
},
147+
{
148+
innerHTML: "<span>abc[] </span>def",
149+
direction: "right",
150+
expectedResult: function () {
151+
return { container: testContainer.querySelector("span").firstChild, offset: "abc ".length };
152+
},
153+
},
154+
{
155+
innerHTML: "<span>abc[] </span> def",
156+
direction: "right",
157+
expectedResult: function () {
158+
return { container: testContainer.querySelector("span").firstChild, offset: "abc ".length };
159+
},
160+
},
161+
{
162+
innerHTML: "<span>abc[]</span> def",
163+
direction: "right",
164+
expectedResult: function () {
165+
return { container: testContainer.lastChild, offset: " ".length };
166+
},
167+
},
168+
// Similarly, these tests expect that selection is collapsed before the first white-space.
169+
{
170+
innerHTML: "abc <span>[]def</span>",
171+
direction: "left",
172+
expectedResult: function () {
173+
return { container: testContainer.firstChild, offset: "abc".length };
174+
},
175+
},
176+
{
177+
innerHTML: "abc <span> []def</span>",
178+
direction: "left",
179+
expectedResult: function () {
180+
return { container: testContainer.firstChild, offset: "abc".length };
181+
},
182+
},
183+
{
184+
innerHTML: "abc<span> []def</span>",
185+
direction: "left",
186+
expectedResult: function () {
187+
return { container: testContainer.querySelector("span").firstChild, offset: 0 };
188+
},
189+
},
190+
{
191+
innerHTML: "<span>abc </span>[]def",
192+
direction: "left",
193+
expectedResult: function () {
194+
return { container: testContainer.querySelector("span").firstChild, offset: "abc".length };
195+
},
196+
},
197+
{
198+
innerHTML: "<span>abc </span> []def",
199+
direction: "left",
200+
expectedResult: function () {
201+
return { container: testContainer.querySelector("span").firstChild, offset: "abc".length };
202+
},
203+
},
204+
{
205+
innerHTML: "<span>abc</span> []def",
206+
direction: "left",
207+
expectedResult: function () {
208+
return { container: testContainer.lastChild, offset: 0 };
209+
},
210+
},
211+
// Invisible white-spaces must be skipped by modify().
212+
{
213+
innerHTML: "abc[] <span>def</span>",
214+
direction: "right",
215+
repeat: 2,
216+
expectedResult: function () {
217+
return { container: testContainer.querySelector("span").firstChild, offset: "d".length };
218+
},
219+
},
220+
{
221+
innerHTML: "abc[] <span> def</span>",
222+
direction: "right",
223+
repeat: 2,
224+
expectedResult: function () {
225+
return { container: testContainer.querySelector("span").firstChild, offset: " d".length };
226+
},
227+
},
228+
{
229+
innerHTML: "abc[]<span> def</span>",
230+
direction: "right",
231+
repeat: 2,
232+
expectedResult: function () {
233+
return { container: testContainer.querySelector("span").firstChild, offset: " d".length };
234+
},
235+
},
236+
{
237+
innerHTML: "<span>abc[] </span>def",
238+
direction: "right",
239+
repeat: 2,
240+
expectedResult: function () {
241+
return { container: testContainer.lastChild, offset: "d".length };
242+
},
243+
},
244+
{
245+
innerHTML: "<span>abc[] </span> def",
246+
direction: "right",
247+
repeat: 2,
248+
expectedResult: function () {
249+
return { container: testContainer.lastChild, offset: " d".length };
250+
},
251+
},
252+
{
253+
innerHTML: "<span>abc[]</span> def",
254+
direction: "right",
255+
repeat: 2,
256+
expectedResult: function () {
257+
return { container: testContainer.lastChild, offset: " d".length };
258+
},
259+
},
260+
{
261+
innerHTML: "abc <span>[]def</span>",
262+
direction: "left",
263+
repeat: 2,
264+
expectedResult: function () {
265+
return { container: testContainer.firstChild, offset: "ab".length };
266+
},
267+
},
268+
{
269+
innerHTML: "abc <span> []def</span>",
270+
direction: "left",
271+
repeat: 2,
272+
expectedResult: function () {
273+
return { container: testContainer.firstChild, offset: "ab".length };
274+
},
275+
},
276+
{
277+
innerHTML: "abc<span> []def</span>",
278+
direction: "left",
279+
repeat: 2,
280+
expectedResult: function () {
281+
return { container: testContainer.firstChild, offset: "ab".length };
282+
},
283+
},
284+
{
285+
innerHTML: "<span>abc </span>[]def",
286+
direction: "left",
287+
repeat: 2,
288+
expectedResult: function () {
289+
return { container: testContainer.querySelector("span").firstChild, offset: "ab".length };
290+
},
291+
},
292+
{
293+
innerHTML: "<span>abc </span> []def",
294+
direction: "left",
295+
repeat: 2,
296+
expectedResult: function () {
297+
return { container: testContainer.querySelector("span").firstChild, offset: "ab".length };
298+
},
299+
},
300+
{
301+
innerHTML: "<span>abc</span> []def",
302+
direction: "left",
303+
repeat: 2,
304+
expectedResult: function () {
305+
return { container: testContainer.querySelector("span").firstChild, offset: "ab".length };
306+
},
307+
},
308+
]) {
309+
test(() => {
310+
const utils = new EditorTestUtils(testContainer);
311+
utils.setupEditingHost(data.innerHTML);
312+
for (let i = 0; i < (data.repeat ?? 1); i++) {
313+
getSelection().modify("move", data.direction, "character");
314+
}
315+
assert_equals(
316+
stringifySelectionRanges(),
317+
stringifyExpectedRange(data.expectedResult())
318+
);
319+
}, `Selection.modify("move", "${data.direction}", "character")${
320+
data.repeat > 1 ? ` (${data.repeat} times)` : ""
321+
} when "${data.innerHTML}"`);
322+
}
323+
}, {once: true});
324+
</script>
325+
</head>
326+
<body>
327+
<div id="testBody" contenteditable></div>
328+
</body>
329+
</html>

0 commit comments

Comments
 (0)