Skip to content

Commit 1f2478b

Browse files
masayuki-nakanomoz-wptsync-bot
authored andcommitted
Make the insertText handler of HTMLEditor remove unnecessary line break first
X (Twitter) expects that when the user typing text, the last mutation is a character data change or a `Text` node addition. However, it's removing a padding `<br>` on Gecko. Therefore, they fail to handle it if user inputs a Unicode character when the editor is empty. Although it's a bug of X, but they don't maintain actively. Therefore, we cannot hope they will fix bug 2012342 so that we need to fix this with changing the handling order. Differential Revision: https://phabricator.services.mozilla.com/D285465 bugzilla-url: https://bugzilla.mozilla.org/show_bug.cgi?id=2019689 gecko-commit: 151dbfb1f4ceb496bddf34501d7aaed6eba60278 gecko-reviewers: m_kato
1 parent 9c52c98 commit 1f2478b

1 file changed

Lines changed: 205 additions & 0 deletions

File tree

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="variant" content="?whiteSpace=normal">
6+
<meta name="variant" content="?whiteSpace=pre">
7+
<meta name="variant" content="?whiteSpace=pre-wrap">
8+
<meta name="variant" content="?whiteSpace=pre-line">
9+
<title>Inserting text should cause a character data change at last even if browsers clean up unnecessary line break</title>
10+
<script src="/resources/testharness.js"></script>
11+
<script src="/resources/testharnessreport.js"></script>
12+
<script src="/resources/testdriver.js"></script>
13+
<script src="/resources/testdriver-vendor.js"></script>
14+
<script src="/resources/testdriver-actions.js"></script>
15+
<script src="../include/editor-test-utils.js"></script>
16+
<script>
17+
"use strict";
18+
19+
const params = new URLSearchParams(document.location.search);
20+
const whiteSpace = params.get("whiteSpace");
21+
const whiteSpaceIsCollapsible = whiteSpace == "normal" || whiteSpace == "pre-line";
22+
23+
/**
24+
* X (Twitter) expects a character data change notification or a Text node
25+
* addition at last when the user types text. Therefore, browsers need to clean
26+
* up unnecessary line break before if it's required.
27+
* Note that this test does not check whether the padding line break result is
28+
* exactly same as one expectation because it's out of scope of the issue.
29+
*/
30+
addEventListener("load", () => {
31+
const editingHost = document.querySelector("div[contenteditable]");
32+
editingHost.style.whiteSpace = whiteSpace;
33+
const utils = new EditorTestUtils(editingHost);
34+
let lastMutation;
35+
const mutationObserver = new MutationObserver(mutations => {
36+
lastMutation =
37+
mutations.length ? mutations[mutations.length - 1] : undefined;
38+
});
39+
mutationObserver.observe(editingHost, {
40+
childList: true,
41+
characterData: true,
42+
subtree: true,
43+
});
44+
function waitForTick() {
45+
return new Promise(resolve => {
46+
requestAnimationFrame(
47+
() => requestAnimationFrame(resolve)
48+
);
49+
});
50+
}
51+
for (const data of [
52+
{
53+
summary: "Typing X",
54+
initialHTML: "{}<br>",
55+
run: () => utils.sendKey("X"),
56+
expectedInnerHTML: [ "X", "X<br>" ],
57+
},
58+
{
59+
summary: "Typing space",
60+
initialHTML: "{}<br>",
61+
run: () => utils.sendKey(" "),
62+
expectedInnerHTML:
63+
whiteSpaceIsCollapsible
64+
? [ "&nbsp;", "&nbsp;<br>" ]
65+
: [ " ", " <br>" ],
66+
},
67+
{
68+
summary: "Typing X",
69+
initialHTML: "abc[]<br>",
70+
run: () => utils.sendKey("X"),
71+
expectedInnerHTML: [ "abcX", "abcX<br>" ],
72+
},
73+
{
74+
summary: "Typing space",
75+
initialHTML: "abc[]<br>",
76+
run: () => utils.sendKey(" "),
77+
expectedInnerHTML:
78+
whiteSpaceIsCollapsible
79+
? [ "abc&nbsp;", "abc&nbsp;<br>" ]
80+
: [ "abc ", "abc <br>" ],
81+
},
82+
{
83+
summary: "Typing X",
84+
initialHTML: "abc{}<br>",
85+
run: () => utils.sendKey("X"),
86+
expectedInnerHTML: [ "abcX", "abcX<br>" ],
87+
},
88+
{
89+
summary: "Typing space",
90+
initialHTML: "abc{}<br>",
91+
run: () => utils.sendKey(" "),
92+
expectedInnerHTML:
93+
whiteSpaceIsCollapsible
94+
? [ "abc&nbsp;", "abc&nbsp;<br>" ]
95+
: [ "abc ", "abc <br>" ],
96+
},
97+
{
98+
summary: "Typing X",
99+
initialHTML: "ab[]c<br>",
100+
run: () => utils.sendKey("X"),
101+
expectedInnerHTML: [ "abXc", "abXc<br>" ],
102+
},
103+
{
104+
summary: "Typing space",
105+
initialHTML: "ab[]c<br>",
106+
run: () => utils.sendKey(" "),
107+
expectedInnerHTML: [ "ab c", "ab c<br>" ],
108+
},
109+
{
110+
summary: "Typing X",
111+
initialHTML: "<b>{}<br></b>",
112+
run: () => utils.sendKey("X"),
113+
expectedInnerHTML: [ "<b>X</b>", "<b>X<br></b>" ],
114+
},
115+
{
116+
summary: "Typing space",
117+
initialHTML: "<b>{}<br></b>",
118+
run: () => utils.sendKey(" "),
119+
expectedInnerHTML:
120+
whiteSpaceIsCollapsible
121+
? [ "<b>&nbsp;</b>", "<b>&nbsp;<br></b>" ]
122+
: [ "<b> </b>", "<b> <br></b>" ],
123+
},
124+
{
125+
summary: "Typing X",
126+
initialHTML: "abc[]<b><br></b>",
127+
run: () => utils.sendKey("X"),
128+
expectedInnerHTML: [ "abcX", "abcX<b><br></b>" ],
129+
},
130+
{
131+
summary: "Typing space",
132+
initialHTML: "abc[]<b><br></b>",
133+
run: () => utils.sendKey(" "),
134+
expectedInnerHTML:
135+
whiteSpaceIsCollapsible
136+
? [ "abc&nbsp;", "abc&nbsp;<b><br></b>" ]
137+
: [ "abc ", "abc <b><br></b>" ],
138+
},
139+
{
140+
summary: "Typing X",
141+
initialHTML: "abc<b>{}<br></b>",
142+
run: () => utils.sendKey("X"),
143+
expectedInnerHTML: [ "abc<b>X</b>", "abc<b>X<br></b>",
144+
"abcX", "abcX<b><br></b>" ],
145+
},
146+
{
147+
summary: "Typing space",
148+
initialHTML: "abc<b>{}<br></b>",
149+
run: () => utils.sendKey(" "),
150+
expectedInnerHTML:
151+
whiteSpaceIsCollapsible
152+
? [ "abc<b>&nbsp;</b>", "abc<b>&nbsp;<br></b>",
153+
"abc&nbsp;", "abc&nbsp;<b><br></b>" ]
154+
: [ "abc<b> </b>", "abc<b> <br></b>",
155+
"abc ", "abc <b><br></b>" ],
156+
},
157+
{
158+
summary: "Typing X",
159+
initialHTML: "abc&nbsp;[]<br>",
160+
run: () => utils.sendKey("X"),
161+
expectedInnerHTML:
162+
whiteSpaceIsCollapsible
163+
? [ "abc X", "abc X<br>" ]
164+
: [ "abc&nbsp;X", "abc&nbsp;X<br>" ],
165+
},
166+
{
167+
summary: "Typing space",
168+
initialHTML: "abc&nbsp;[]<br>",
169+
run: () => utils.sendKey(" "),
170+
expectedInnerHTML:
171+
whiteSpaceIsCollapsible
172+
? [ "abc&nbsp;&nbsp;", "abc&nbsp;&nbsp;<br>",
173+
"abc &nbsp;", "abc &nbsp;<br>" ]
174+
: [ "abc&nbsp; ", "abc&nbsp; <br>" ],
175+
},
176+
]) {
177+
promise_test(async t => {
178+
utils.setupEditingHost(data.initialHTML);
179+
await waitForTick();
180+
lastMutation = null;
181+
await data.run();
182+
assert_true(
183+
lastMutation?.type == "characterData" || lastMutation?.addedNodes[0]?.nodeName == "#text",
184+
`${
185+
t.name
186+
}: The last mutation should be a character data change or a text node addition (got: { target: ${
187+
lastMutation?.target
188+
}, addedNodes[0]: ${lastMutation?.addedNodes[0]}, removedNodes[0]: ${
189+
lastMutation?.removedNodes[0]
190+
} })`
191+
);
192+
assert_in_array(
193+
editingHost.innerHTML,
194+
data.expectedInnerHTML,
195+
`${t.name}: comparing innerHTML`
196+
);
197+
}, `${data.summary} when <div contenteditable style="white-space:${whiteSpace}">${data.initialHTML}</div>`);
198+
}
199+
}, {once: true});
200+
</script>
201+
</head>
202+
<body>
203+
<div contenteditable></div>
204+
</body>
205+
</html>

0 commit comments

Comments
 (0)