Skip to content

Commit ebd7f41

Browse files
masayuki-nakanomoz-wptsync-bot
authored andcommitted
Make Range.deleteContents(), Range.cloneContents(), Range.extractContents() and Selection.deleteFromDocument() work as in the DOM
The spec does not mention about the flattened tree and Chrome handles the range as in a DOM. E.g., unassigned node is handled, but the nodes in the shadow are ignored. For the compatibility with Chrome, we should follow their behavior for now. The new tests for `Selection.deleteFromDocument` does not pass even with this patch. However, we just call `nsRange::DeleteContents()` [1]. Therefore, it may be caused by a bug of `Selection::SetBaseAndExtent()`. Fixing it may require more changes. So, this does not fix the failures. 1. https://searchfox.org/firefox-main/rev/a07b2cb8ff33387a81968a7ed9d59ebdc4dad607/dom/base/Selection.cpp#4205 Differential Revision: https://phabricator.services.mozilla.com/D311661 bugzilla-url: https://bugzilla.mozilla.org/show_bug.cgi?id=2053997 gecko-commit: bd08d097d3d05aac6aa7598bc082090dd857032f gecko-commit-git: bc8abb63926115cb0c94d645c81897a3a4a24531 gecko-reviewers: smaug
1 parent 8d4905c commit ebd7f41

7 files changed

Lines changed: 1669 additions & 0 deletions
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Range.cloneContents() should work as in a range in the DOM rather than in the flat tree</title>
6+
<script src="/resources/testharness.js"></script>
7+
<script src="/resources/testharnessreport.js"></script>
8+
<script>
9+
"use strict";
10+
11+
addEventListener("load", () => {
12+
function innerHTMLOfDocumentFragment(docFlag) {
13+
const div = document.createElement("div");
14+
div.appendChild(docFlag.cloneNode(true));
15+
return div.innerHTML;
16+
}
17+
const container = document.getElementById("container");
18+
test(t => {
19+
container.innerHTML = "<p>abcdef</p><div><span>UnassignedText</span></div>";
20+
const p = container.querySelector("p");
21+
const host = container.querySelector("div");
22+
const shadowRoot = host.attachShadow({mode: "open"});
23+
shadowRoot.innerHTML = "<span>InnerText</span>";
24+
const range = document.createRange();
25+
range.setStart(p.firstChild, "abc".length);
26+
range.setEnd(host, 1);
27+
let docFlag;
28+
try {
29+
docFlag = range.cloneContents();
30+
} catch (e) {
31+
assert_true(
32+
false,
33+
`${t.name}: range.cloneContents() shouldn't throw exception`
34+
);
35+
}
36+
test(() => {
37+
assert_equals(
38+
innerHTMLOfDocumentFragment(docFlag),
39+
"<p>def</p><div><span>UnassignedText</span></div>"
40+
);
41+
}, `${t.name}: range.cloneContents() should clone a part of the text and the unassigned <span>`);
42+
test(() => {
43+
assert_equals(docFlag.querySelector("div").shadowRoot, null);
44+
}, `${t.name}: range.cloneContents() shouldn't clone the host with the shadow`);
45+
}, 'Range.cloneContents() when "<p>abc[def</p><div><shadow-root><span>InnerText</span></shadow-root><span>UnassignedText</span>}</div>"');
46+
47+
test(t => {
48+
container.innerHTML = "<p>abcdef</p><div><span>UnassignedText</span></div>";
49+
const p = container.querySelector("p");
50+
const host = container.querySelector("div");
51+
const shadowRoot = host.attachShadow({mode: "open"});
52+
shadowRoot.innerHTML = "<span>InnerText</span>";
53+
const range = document.createRange();
54+
range.setStart(p.firstChild, "abc".length);
55+
range.setEnd(host.firstChild.firstChild, "Unassigned".length);
56+
let docFlag;
57+
try {
58+
docFlag = range.cloneContents();
59+
} catch (e) {
60+
assert_true(
61+
false,
62+
`${t.name}: range.cloneContents() shouldn't throw exception`
63+
);
64+
}
65+
test(() => {
66+
assert_equals(
67+
innerHTMLOfDocumentFragment(docFlag),
68+
"<p>def</p><div><span>Unassigned</span></div>"
69+
);
70+
}, `${t.name}: range.cloneContents() should clone a part of the text and a part of the unassigned text`);
71+
test(() => {
72+
assert_equals(docFlag.querySelector("div").shadowRoot, null);
73+
}, `${t.name}: range.cloneContents() shouldn't clone the host with the shadow`);
74+
}, 'Range.cloneContents() when "<p>abc[def</p><div><shadow-root><span>InnerText</span></shadow-root><span>Unassigned]Text</span></div>"');
75+
76+
test(t => {
77+
container.innerHTML = "<div><span>UnassignedText</span></div>";
78+
const p = container.querySelector("p");
79+
const host = container.querySelector("div");
80+
const shadowRoot = host.attachShadow({mode: "open"});
81+
shadowRoot.innerHTML = "<span>InnerText</span>";
82+
const range = document.createRange();
83+
range.setStart(host.firstChild, 0);
84+
range.setEnd(host.firstChild, 1);
85+
let docFlag;
86+
try {
87+
docFlag = range.cloneContents();
88+
} catch (e) {
89+
assert_true(
90+
false,
91+
`${t.name}: range.cloneContents() shouldn't throw exception`
92+
);
93+
}
94+
assert_equals(
95+
innerHTMLOfDocumentFragment(docFlag),
96+
"UnassignedText",
97+
`${t.name}: range.cloneContents() should clone the unassigned text`
98+
);
99+
}, 'Range.cloneContents() when "<div><shadow-root><span>InnerText</span></shadow-root><span>{UnassignedText}</span></div>"');
100+
101+
test(t => {
102+
container.innerHTML = "<div><span>UnassignedText</span></div><p>abcdef</p>";
103+
const p = container.querySelector("p");
104+
const host = container.querySelector("div");
105+
const shadowRoot = host.attachShadow({mode: "open"});
106+
shadowRoot.innerHTML = "<span>InnerText</span>";
107+
const range = document.createRange();
108+
range.setStart(host, 0);
109+
range.setEnd(p.firstChild, "abc".length);
110+
let docFlag;
111+
try {
112+
docFlag = range.cloneContents();
113+
} catch (e) {
114+
assert_true(
115+
false,
116+
`${t.name}: range.cloneContents() shouldn't throw exception`
117+
);
118+
}
119+
test(() => {
120+
assert_equals(
121+
innerHTMLOfDocumentFragment(docFlag),
122+
"<div><span>UnassignedText</span></div><p>abc</p>"
123+
);
124+
}, `${t.name}: range.cloneContents() should clone the unassigned <span> and a part of the text`);
125+
test(() => {
126+
assert_equals(docFlag.querySelector("div").shadowRoot, null);
127+
}, `${t.name}: range.cloneContents() shouldn't clone the host with the shadow`);
128+
}, 'Range.cloneContents() when "<div><shadow-root><span>InnerText</span></shadow-root>{<span>UnassignedText</span></div><p>abc]def</p>"');
129+
130+
test(t => {
131+
container.innerHTML = "<div><span>UnassignedText</span></div><p>abcdef</p>";
132+
const p = container.querySelector("p");
133+
const host = container.querySelector("div");
134+
const shadowRoot = host.attachShadow({mode: "open"});
135+
shadowRoot.innerHTML = "<span>InnerText</span>";
136+
const range = document.createRange();
137+
range.setStart(host.firstChild.firstChild, "Unassigned".length);
138+
range.setEnd(p.firstChild, "abc".length);
139+
let docFlag;
140+
try {
141+
docFlag = range.cloneContents();
142+
} catch (e) {
143+
assert_true(
144+
false,
145+
`${t.name}: range.cloneContents() shouldn't throw exception`
146+
);
147+
}
148+
test(() => {
149+
assert_equals(
150+
innerHTMLOfDocumentFragment(docFlag),
151+
"<div><span>Text</span></div><p>abc</p>"
152+
);
153+
}, `${t.name}: range.cloneContents() should clone a part of unassigned text and a part of the text`);
154+
test(() => {
155+
assert_equals(docFlag.querySelector("div").shadowRoot, null);
156+
}, `${t.name}: range.cloneContents() shouldn't clone the host with the shadow`);
157+
}, 'Range.cloneContents() when "<div><shadow-root><span>InnerText</span></shadow-root><span>Unassigned[Text</span></div><p>abc]def</p>"');
158+
159+
test(t => {
160+
container.innerHTML = "<p>abcdef</p><div><span>AssignedText</span></div>";
161+
const p = container.querySelector("p");
162+
const host = container.querySelector("div");
163+
const shadowRoot = host.attachShadow({mode: "open"});
164+
shadowRoot.innerHTML = "<slot></slot><span>InnerText</span>";
165+
const range = document.createRange();
166+
range.setStart(p.firstChild, "abc".length);
167+
range.setEnd(host.firstChild.firstChild, "Assigned".length);
168+
let docFlag;
169+
try {
170+
docFlag = range.cloneContents();
171+
} catch (e) {
172+
assert_true(
173+
false,
174+
`${t.name}: range.cloneContents() shouldn't throw exception`
175+
);
176+
}
177+
test(() => {
178+
assert_equals(
179+
innerHTMLOfDocumentFragment(docFlag),
180+
"<p>def</p><div><span>Assigned</span></div>"
181+
);
182+
}, `${t.name}: range.cloneContents() should clone a part of the text and a part of assigned text`);
183+
test(() => {
184+
assert_equals(docFlag.querySelector("div").shadowRoot, null);
185+
}, `${t.name}: range.cloneContents() shouldn't clone the host with the shadow`);
186+
}, 'Range.cloneContents() when "<p>abc[def</p><div><shadow-root></slot><span>InnerText</span></shadow-root><slot><span>Assigned]Text</span></div>"');
187+
188+
test(t => {
189+
container.innerHTML = "<p>abcdef</p><div><span>AssignedText</span></div>";
190+
const p = container.querySelector("p");
191+
const host = container.querySelector("div");
192+
const shadowRoot = host.attachShadow({mode: "open"});
193+
shadowRoot.innerHTML = "<span>InnerText</span><slot></slot>";
194+
const range = document.createRange();
195+
range.setStart(p.firstChild, "abc".length);
196+
range.setEnd(host.firstChild.firstChild, "Assigned".length);
197+
let docFlag;
198+
try {
199+
docFlag = range.cloneContents();
200+
} catch (e) {
201+
assert_true(
202+
false,
203+
`${t.name}: range.cloneContents() shouldn't throw exception`
204+
);
205+
}
206+
test(() => {
207+
assert_equals(
208+
innerHTMLOfDocumentFragment(docFlag),
209+
"<p>def</p><div><span>Assigned</span></div>"
210+
);
211+
}, `${t.name}: range.cloneContents() should clone a part of the text and a part of assigned text`);
212+
test(() => {
213+
assert_equals(docFlag.querySelector("div").shadowRoot, null);
214+
}, `${t.name}: range.cloneContents() shouldn't clone the host with the shadow`);
215+
}, 'Range.cloneContents() when "<p>abc[def</p><div><shadow-root><span>InnerText</span><slot></slot></shadow-root><span>Assigned]Text</span></div>"');
216+
217+
test(t => {
218+
container.innerHTML = `<p>abcdef</p><div><span slot="assigned">AssignedText</span><span>UnassignedText</span></div>`;
219+
const p = container.querySelector("p");
220+
const host = container.querySelector("div");
221+
const shadowRoot = host.attachShadow({mode: "open"});
222+
shadowRoot.innerHTML = `<span>InnerText</span><slot name="assigned"></slot>`;
223+
const range = document.createRange();
224+
range.setStart(p.firstChild, "abc".length);
225+
range.setEnd(host.querySelector("span[slot]").firstChild, "Assigned".length);
226+
let docFlag;
227+
try {
228+
docFlag = range.cloneContents();
229+
} catch (e) {
230+
assert_true(
231+
false,
232+
`${t.name}: range.cloneContents() shouldn't throw exception`
233+
);
234+
}
235+
test(() => {
236+
assert_equals(
237+
innerHTMLOfDocumentFragment(docFlag),
238+
`<p>def</p><div><span slot="assigned">Assigned</span></div>`
239+
);
240+
}, `${t.name}: range.cloneContents() should clone a part of the text and a part of assigned text`);
241+
test(() => {
242+
assert_equals(docFlag.querySelector("div").shadowRoot, null);
243+
}, `${t.name}: range.cloneContents() shouldn't clone the host with the shadow`);
244+
}, 'Range.cloneContents() when "<p>abc[def</p><div><shadow-root><span>InnerText</span><slot name="assigned"></slot></shadow-root><span>Assigned]Text</span><span>UnassignedText</span></div>"');
245+
246+
test(t => {
247+
container.innerHTML = `<p>abcdef</p><div><span>UnassignedText</span><span slot="assigned">AssignedText</span></div>`;
248+
const p = container.querySelector("p");
249+
const host = container.querySelector("div");
250+
const shadowRoot = host.attachShadow({mode: "open"});
251+
shadowRoot.innerHTML = `<span>InnerText</span><slot name="assigned"></slot>`;
252+
const range = document.createRange();
253+
range.setStart(p.firstChild, "abc".length);
254+
range.setEnd(host.querySelector("span[slot]").firstChild, "Assigned".length);
255+
let docFlag;
256+
try {
257+
docFlag = range.cloneContents();
258+
} catch (e) {
259+
assert_true(
260+
false,
261+
`${t.name}: range.cloneContents() shouldn't throw exception`
262+
);
263+
}
264+
test(() => {
265+
assert_equals(
266+
innerHTMLOfDocumentFragment(docFlag),
267+
`<p>def</p><div><span>UnassignedText</span><span slot="assigned">Assigned</span></div>`
268+
);
269+
}, `${t.name}: range.cloneContents() should clone a part of the text, the unassigned <span> and a part of assigned text`);
270+
test(() => {
271+
assert_equals(docFlag.querySelector("div").shadowRoot, null);
272+
}, `${t.name}: range.cloneContents() shouldn't clone the host with the shadow`);
273+
}, 'Range.cloneContents() when "<p>abc[def</p><div><shadow-root><span>InnerText</span><slot name="assigned"></slot></shadow-root><span>UnassignedText</span><span>Assigned]Text</span></div>"');
274+
}, {once: true});
275+
</script>
276+
</head>
277+
<body><div id="container"></div></body>
278+
</html>
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Range.cloneContents() should work as in a range in the DOM rather than in the flat tree</title>
6+
<script src="/resources/testharness.js"></script>
7+
<script src="/resources/testharnessreport.js"></script>
8+
<script>
9+
"use strict";
10+
11+
addEventListener("load", () => {
12+
function innerHTMLOfDocumentFragment(docFlag) {
13+
const div = document.createElement("div");
14+
div.appendChild(docFlag.cloneNode(true));
15+
return div.innerHTML;
16+
}
17+
const container = document.getElementById("container");
18+
test(t => {
19+
container.innerHTML = "<p>abcdef</p><div><span>UnassignedText</span></div>";
20+
const p = container.querySelector("p");
21+
const host = container.querySelector("div");
22+
const shadowRoot = host.attachShadow({mode: "open"});
23+
shadowRoot.innerHTML = "<span>InnerText</span>";
24+
getSelection().setBaseAndExtent(
25+
p.firstChild, "abc".length,
26+
shadowRoot.firstChild.firstChild, "Inner".length
27+
);
28+
const range = getSelection().getRangeAt(0);
29+
let docFlag;
30+
try {
31+
docFlag = range.cloneContents();
32+
} catch (e) {
33+
assert_true(
34+
false,
35+
`${t.name}: range.cloneContents() shouldn't throw exception`
36+
);
37+
}
38+
assert_equals(
39+
innerHTMLOfDocumentFragment(docFlag),
40+
"",
41+
`${t.name}: range.cloneContents() should clone nothing`
42+
);
43+
}, 'Range.cloneContents() when "<p>abc[def</p><div><shadow-root><span>Inner]Text</span></shadow-root><span>UnassignedText</span></div>"');
44+
45+
test(t => {
46+
container.innerHTML = "<div><span>UnassignedText</span></div><p>abcdef</p>";
47+
const p = container.querySelector("p");
48+
const host = container.querySelector("div");
49+
const shadowRoot = host.attachShadow({mode: "open"});
50+
shadowRoot.innerHTML = "<span>InnerText</span>";
51+
getSelection().setBaseAndExtent(
52+
shadowRoot.firstChild.firstChild, "Inner".length,
53+
p.firstChild, "abc".length
54+
);
55+
const range = getSelection().getRangeAt(0);
56+
let docFlag;
57+
try {
58+
docFlag = range.cloneContents();
59+
} catch (e) {
60+
assert_true(
61+
false,
62+
`${t.name}: range.cloneContents() shouldn't throw exception`
63+
);
64+
}
65+
assert_equals(
66+
innerHTMLOfDocumentFragment(docFlag),
67+
"",
68+
`${t.name}: range.cloneContents() should clone nothing`
69+
);
70+
}, 'Range.cloneContents() when "<div><shadow-root><span>Inner[Text</span></shadow-root><span>UnassignedText</span></div><p>abc]def</p>"');
71+
72+
// Clonable shadow root case.
73+
test(t => {
74+
container.innerHTML = "<p>abcdef</p><div><span>UnassignedText</span></div>";
75+
const p = container.querySelector("p");
76+
const host = container.querySelector("div");
77+
const shadowRoot = host.attachShadow({mode: "open", clonable: true});
78+
shadowRoot.innerHTML = "<span>InnerText</span>";
79+
const range = document.createRange();
80+
range.setStart(p.firstChild, "abc".length);
81+
range.setEnd(host, 1);
82+
let docFlag;
83+
try {
84+
docFlag = range.cloneContents();
85+
} catch (e) {
86+
assert_true(
87+
false,
88+
`${t.name}: range.cloneContents() shouldn't throw exception`
89+
);
90+
}
91+
test(() => {
92+
assert_equals(
93+
innerHTMLOfDocumentFragment(docFlag),
94+
"<p>def</p><div><span>UnassignedText</span></div>"
95+
);
96+
}, `${t.name}: range.cloneContents() should clone a part of the text and the unassigned <span>`);
97+
test(() => {
98+
assert_true(docFlag.querySelector("div").shadowRoot && docFlag.querySelector("div").shadowRoot != shadowRoot);
99+
assert_equals(
100+
docFlag.querySelector("div").shadowRoot.innerHTML,
101+
shadowRoot.innerHTML
102+
);
103+
}, `${t.name}: range.cloneContents() should clone the host with the shadow`);
104+
}, 'Range.cloneContents() when "<p>abc[def</p><div><shadow-root><span>InnerText</span></shadow-root><span>UnassignedText</span>}</div>" (the shadow is clonable)');
105+
}, {once: true});
106+
</script>
107+
</head>
108+
<body><div id="container"></div></body>
109+
</html>

0 commit comments

Comments
 (0)