Skip to content

Commit 1aff3fa

Browse files
authored
Merge pull request #389 from webcomponents/parent-node
Add polyfills for select ParentNode APIs.
2 parents 93f9e56 + 664abc2 commit 1aff3fa

File tree

17 files changed

+731
-4
lines changed

17 files changed

+731
-4
lines changed

packages/shadydom/CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
<!-- ## [Unreleased] -->
8+
## [Unreleased]
9+
10+
- Add support for select ParentNode APIs.
11+
([#389](https://github.com/webcomponents/polyfills/pull/389))
912

1013
## [1.7.4] - 2020-07-20
1114

packages/shadydom/src/patch-native.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,10 @@ const ParentNodeAccessors = [
9494

9595
const ParentNodeMethods = [
9696
'querySelector',
97-
'querySelectorAll'
98-
// 'append', 'prepend'
97+
'querySelectorAll',
98+
'append',
99+
'prepend',
100+
'replaceChildren',
99101
];
100102

101103
export const addNativePrefixedProperties = () => {

packages/shadydom/src/patches/ParentNode.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,27 @@ export const ParentNodePatches = utils.getOwnPropertyDescriptors({
9393
return children.length;
9494
}
9595
return 0;
96-
}
96+
},
97+
98+
/** @this {Element} */
99+
append(...args) {
100+
this[utils.SHADY_PREFIX + 'insertBefore'](utils.convertNodesIntoANode(...args), null);
101+
},
102+
103+
/** @this {Element} */
104+
prepend(...args) {
105+
this[utils.SHADY_PREFIX + 'insertBefore'](
106+
utils.convertNodesIntoANode(...args), this[utils.SHADY_PREFIX + 'firstChild']);
107+
},
108+
109+
/** @this {Element} */
110+
['replaceChildren'](...args) {
111+
let child;
112+
while ((child = this[utils.SHADY_PREFIX + 'firstChild']) !== null) {
113+
this[utils.SHADY_PREFIX + 'removeChild'](child);
114+
}
115+
this[utils.SHADY_PREFIX + 'insertBefore'](utils.convertNodesIntoANode(...args), null);
116+
},
97117

98118
});
99119

packages/shadydom/src/utils.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,3 +230,35 @@ export const assign = (target, source) => {
230230
export const arrayFrom = (object) => {
231231
return [].slice.call(/** @type {IArrayLike} */(object));
232232
};
233+
234+
/**
235+
* Converts a single value to a node for `convertNodesIntoANode`.
236+
*
237+
* @param {*} arg
238+
* @return {!Node}
239+
*/
240+
const convertIntoANode = (arg) => {
241+
return !(arg instanceof Node) ? document.createTextNode(arg) : arg;
242+
};
243+
244+
/**
245+
* Implements 'convert nodes into a node'. The spec text indicates that strings
246+
* become text nodes, but doesn't describe what should happen if a non-Node,
247+
* non-string value is found in the arguments list. In practice, browsers coerce
248+
* these values to strings and convert those to text nodes.
249+
* https://dom.spec.whatwg.org/#converting-nodes-into-a-node
250+
*
251+
* @param {...*} args
252+
* @return {!Node}
253+
*/
254+
export const convertNodesIntoANode = (...args) => {
255+
if (args.length === 1) {
256+
return convertIntoANode(args[0]);
257+
}
258+
259+
const fragment = document.createDocumentFragment();
260+
for (const arg of args) {
261+
fragment.appendChild(convertIntoANode(arg));
262+
}
263+
return fragment;
264+
};

packages/shadydom/src/wrapper.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,18 @@ class Wrapper {
245245
return this.node[utils.SHADY_PREFIX + 'className'] = value;
246246
}
247247

248+
append(...args) {
249+
return this.node[utils.SHADY_PREFIX + 'append'](...args);
250+
}
251+
252+
prepend(...args) {
253+
return this.node[utils.SHADY_PREFIX + 'prepend'](...args);
254+
}
255+
256+
replaceChildren(...args) {
257+
return this.node[utils.SHADY_PREFIX + 'replaceChildren'](...args);
258+
}
259+
248260
}
249261

250262
const addEventPropertyWrapper = (name) => {

packages/tests/shadydom/shady.html

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,129 @@
458458
assert.strictEqual(getComposedHTML(host), '<b></b>');
459459
});
460460

461+
test('append - mutate host', function() {
462+
ShadyDOM.wrapIfNeeded(host).innerHTML = '<a>Hello</a>';
463+
ShadyDOM.wrapIfNeeded(ShadyDOM.wrapIfNeeded(host).shadowRoot).innerHTML = '<slot></slot>';
464+
ShadyDOM.flush();
465+
assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');
466+
host.append(document.createElement('b'), 'c', document.createElement('d'));
467+
ShadyDOM.flush();
468+
assert.strictEqual(getComposedHTML(host), '<a>Hello</a><b></b>c<d></d>');
469+
});
470+
471+
test('append - mutate host - single string', function() {
472+
ShadyDOM.wrapIfNeeded(host).innerHTML = '<a>Hello</a>';
473+
ShadyDOM.wrapIfNeeded(ShadyDOM.wrapIfNeeded(host).shadowRoot).innerHTML = '<slot></slot>';
474+
ShadyDOM.flush();
475+
assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');
476+
host.append('b');
477+
ShadyDOM.flush();
478+
assert.strictEqual(getComposedHTML(host), '<a>Hello</a>b');
479+
});
480+
481+
test('append - mutate shadow', function() {
482+
ShadyDOM.wrapIfNeeded(host).innerHTML = '<a>Hello</a>';
483+
ShadyDOM.wrapIfNeeded(ShadyDOM.wrapIfNeeded(host).shadowRoot).innerHTML = '<slot></slot>';
484+
ShadyDOM.flush();
485+
assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');
486+
ShadyDOM.wrapIfNeeded(ShadyDOM.wrapIfNeeded(host).shadowRoot).append(
487+
document.createElement('b'), 'c', document.createElement('d'));
488+
ShadyDOM.flush();
489+
assert.strictEqual(getComposedHTML(host), '<a>Hello</a><b></b>c<d></d>');
490+
});
491+
492+
test('append - mutate shadow - single string', function() {
493+
ShadyDOM.wrapIfNeeded(host).innerHTML = '<a>Hello</a>';
494+
ShadyDOM.wrapIfNeeded(ShadyDOM.wrapIfNeeded(host).shadowRoot).innerHTML = '<slot></slot>';
495+
ShadyDOM.flush();
496+
assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');
497+
ShadyDOM.wrapIfNeeded(ShadyDOM.wrapIfNeeded(host).shadowRoot).append('b');
498+
ShadyDOM.flush();
499+
assert.strictEqual(getComposedHTML(host), '<a>Hello</a>b');
500+
});
501+
502+
test('prepend - mutate host', function() {
503+
ShadyDOM.wrapIfNeeded(host).innerHTML = '<a>Hello</a>';
504+
ShadyDOM.wrapIfNeeded(ShadyDOM.wrapIfNeeded(host).shadowRoot).innerHTML = '<slot></slot>';
505+
ShadyDOM.flush();
506+
assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');
507+
host.prepend(document.createElement('b'), 'c', document.createElement('d'));
508+
ShadyDOM.flush();
509+
assert.strictEqual(getComposedHTML(host), '<b></b>c<d></d><a>Hello</a>');
510+
});
511+
512+
test('prepend - mutate host - single string', function() {
513+
ShadyDOM.wrapIfNeeded(host).innerHTML = '<a>Hello</a>';
514+
ShadyDOM.wrapIfNeeded(ShadyDOM.wrapIfNeeded(host).shadowRoot).innerHTML = '<slot></slot>';
515+
ShadyDOM.flush();
516+
assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');
517+
host.prepend('b');
518+
ShadyDOM.flush();
519+
assert.strictEqual(getComposedHTML(host), 'b<a>Hello</a>');
520+
});
521+
522+
test('prepend - mutate shadow', function() {
523+
ShadyDOM.wrapIfNeeded(host).innerHTML = '<a>Hello</a>';
524+
ShadyDOM.wrapIfNeeded(ShadyDOM.wrapIfNeeded(host).shadowRoot).innerHTML = '<slot></slot>';
525+
ShadyDOM.flush();
526+
assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');
527+
ShadyDOM.wrapIfNeeded(ShadyDOM.wrapIfNeeded(host).shadowRoot).prepend(
528+
document.createElement('b'), 'c', document.createElement('d'));
529+
ShadyDOM.flush();
530+
assert.strictEqual(getComposedHTML(host), '<b></b>c<d></d><a>Hello</a>');
531+
});
532+
533+
test('prepend - mutate shadow - single string', function() {
534+
ShadyDOM.wrapIfNeeded(host).innerHTML = '<a>Hello</a>';
535+
ShadyDOM.wrapIfNeeded(ShadyDOM.wrapIfNeeded(host).shadowRoot).innerHTML = '<slot></slot>';
536+
ShadyDOM.flush();
537+
assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');
538+
ShadyDOM.wrapIfNeeded(ShadyDOM.wrapIfNeeded(host).shadowRoot).prepend('b');
539+
ShadyDOM.flush();
540+
assert.strictEqual(getComposedHTML(host), 'b<a>Hello</a>');
541+
});
542+
543+
test('replaceChildren - mutate host', function() {
544+
ShadyDOM.wrapIfNeeded(host).innerHTML = '<a>Hello</a>';
545+
ShadyDOM.wrapIfNeeded(ShadyDOM.wrapIfNeeded(host).shadowRoot).innerHTML = '<slot></slot>';
546+
ShadyDOM.flush();
547+
assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');
548+
host.replaceChildren(document.createElement('b'), 'c', document.createElement('d'));
549+
ShadyDOM.flush();
550+
assert.strictEqual(getComposedHTML(host), '<b></b>c<d></d>');
551+
});
552+
553+
test('replaceChildren - mutate host - single string', function() {
554+
ShadyDOM.wrapIfNeeded(host).innerHTML = '<a>Hello</a>';
555+
ShadyDOM.wrapIfNeeded(ShadyDOM.wrapIfNeeded(host).shadowRoot).innerHTML = '<slot></slot>';
556+
ShadyDOM.flush();
557+
assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');
558+
host.replaceChildren('b');
559+
ShadyDOM.flush();
560+
assert.strictEqual(getComposedHTML(host), 'b');
561+
});
562+
563+
test('replaceChildren - mutate shadow', function() {
564+
ShadyDOM.wrapIfNeeded(host).innerHTML = '<a>Hello</a>';
565+
ShadyDOM.wrapIfNeeded(ShadyDOM.wrapIfNeeded(host).shadowRoot).innerHTML = '<slot></slot>';
566+
ShadyDOM.flush();
567+
assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');
568+
ShadyDOM.wrapIfNeeded(ShadyDOM.wrapIfNeeded(host).shadowRoot).replaceChildren(
569+
document.createElement('b'), 'c', document.createElement('d'));
570+
ShadyDOM.flush();
571+
assert.strictEqual(getComposedHTML(host), '<b></b>c<d></d>');
572+
});
573+
574+
test('replaceChildren - mutate shadow - single string', function() {
575+
ShadyDOM.wrapIfNeeded(host).innerHTML = '<a>Hello</a>';
576+
ShadyDOM.wrapIfNeeded(ShadyDOM.wrapIfNeeded(host).shadowRoot).innerHTML = '<slot></slot>';
577+
ShadyDOM.flush();
578+
assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');
579+
ShadyDOM.wrapIfNeeded(ShadyDOM.wrapIfNeeded(host).shadowRoot).replaceChildren('b');
580+
ShadyDOM.flush();
581+
assert.strictEqual(getComposedHTML(host), 'b');
582+
});
583+
461584
test('querySelectorAll .shadowRoot', function() {
462585
ShadyDOM.wrapIfNeeded(host).innerHTML = '<div id="main"></div>';
463586
ShadyDOM.wrapIfNeeded(ShadyDOM.wrapIfNeeded(host).shadowRoot).innerHTML = '<slot></slot><span id="main"></span>' +
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<!doctype html>
2+
<!--
3+
@license
4+
Copyright (c) 2020 The Polymer Project Authors. All rights reserved.
5+
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
6+
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
7+
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
8+
Code distributed by Google as part of the polymer project is also
9+
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
10+
-->
11+
<html>
12+
<head>
13+
<title>ParentNode append</title>
14+
<script>
15+
for (const item of [
16+
Document.prototype,
17+
DocumentFragment.prototype,
18+
Element.prototype,
19+
]) {
20+
delete item.append;
21+
}
22+
</script>
23+
<script src="../../node_modules/@webcomponents/webcomponentsjs/bundles/webcomponents-pf_dom.js"></script>
24+
<script src="../wct-config.js"></script>
25+
<script src="../../node_modules/wct-browser-legacy/browser.js"></script>
26+
</head>
27+
<body>
28+
<script>
29+
suite('ParentNode append', function() {
30+
suite('Document', function() {
31+
test('Appends an element', () => {
32+
const doc = document.implementation.createHTMLDocument('');
33+
while (doc.firstChild) {
34+
doc.removeChild(doc.firstChild);
35+
}
36+
37+
const child = document.createElement('html');
38+
doc.append(child);
39+
40+
assert(doc.childNodes.length === 1);
41+
assert(doc.childNodes[0] === child);
42+
});
43+
});
44+
45+
suite('DocumentFragment', function() {
46+
test('Appends an element', () => {
47+
const container = document.createDocumentFragment();
48+
container.appendChild(document.createElement('div'));
49+
50+
const child = document.createElement('div');
51+
container.append(child);
52+
53+
assert(child.parentNode === container);
54+
assert(container.childNodes.length === 2);
55+
assert(container.childNodes[1] === child);
56+
});
57+
58+
test('Appends a text node', () => {
59+
const container = document.createDocumentFragment();
60+
container.appendChild(document.createElement('div'));
61+
62+
container.append('This is some text.');
63+
64+
assert(container.childNodes.length === 2);
65+
assert(container.childNodes[1].nodeType === Node.TEXT_NODE);
66+
assert(container.childNodes[1].textContent === 'This is some text.');
67+
});
68+
69+
test('Appends multiple types of nodes', () => {
70+
const container = document.createDocumentFragment();
71+
container.appendChild(document.createElement('div'));
72+
73+
const child1 = document.createElement('div');
74+
const child3 = document.createElement('div');
75+
container.append(child1, 'This is some text.', child3);
76+
77+
assert(container.childNodes.length === 4);
78+
assert(container.childNodes[1] === child1);
79+
assert(container.childNodes[2].nodeType === Node.TEXT_NODE);
80+
assert(container.childNodes[2].textContent === 'This is some text.');
81+
assert(container.childNodes[3] === child3);
82+
});
83+
});
84+
85+
suite('Element', function() {
86+
test('Appends an element', () => {
87+
const container = document.createElement('div');
88+
container.appendChild(document.createElement('div'));
89+
90+
const child = document.createElement('div');
91+
container.append(child);
92+
93+
assert(child.parentNode === container);
94+
assert(container.childNodes.length === 2);
95+
assert(container.childNodes[1] === child);
96+
});
97+
98+
test('Appends a text node', () => {
99+
const container = document.createElement('div');
100+
container.appendChild(document.createElement('div'));
101+
102+
container.append('This is some text.');
103+
104+
assert(container.childNodes.length === 2);
105+
assert(container.childNodes[1].nodeType === Node.TEXT_NODE);
106+
assert(container.childNodes[1].textContent === 'This is some text.');
107+
});
108+
109+
test('Appends multiple types of nodes', () => {
110+
const container = document.createElement('div');
111+
container.appendChild(document.createElement('div'));
112+
113+
const child1 = document.createElement('div');
114+
const child3 = document.createElement('div');
115+
container.append(child1, 'This is some text.', child3);
116+
117+
assert(container.childNodes.length === 4);
118+
assert(container.childNodes[1] === child1);
119+
assert(container.childNodes[2].nodeType === Node.TEXT_NODE);
120+
assert(container.childNodes[2].textContent === 'This is some text.');
121+
assert(container.childNodes[3] === child3);
122+
});
123+
});
124+
});
125+
</script>
126+
</body>
127+
</html>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!doctype html>
2+
<script src="../../../../node_modules/wct-browser-legacy/browser.js"></script>
3+
<script>
4+
WCT.loadSuites([
5+
'./append.html',
6+
'./prepend.html',
7+
'./replace-children.html',
8+
]);
9+
</script>

0 commit comments

Comments
 (0)