Skip to content

Commit 6be58b0

Browse files
authored
Merge pull request #5537 from Polymer/scopeSubtree
Implement scopeSubtree for ShadyDOM noPatch mode
2 parents 971d32d + 338d420 commit 6be58b0

File tree

4 files changed

+219
-5
lines changed

4 files changed

+219
-5
lines changed

lib/legacy/legacy-element-mixin.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { Debouncer } from '../utils/debounce.js';
2020
import { timeOut, microTask } from '../utils/async.js';
2121
import { get } from '../utils/path.js';
2222
import { wrap } from '../utils/wrap.js';
23+
import { scopeSubtree } from '../utils/scope-subtree.js';
2324

2425
let styleInterface = window.ShadyCSS;
2526

@@ -701,12 +702,13 @@ export const LegacyElementMixin = dedupingMixin((base) => {
701702
/**
702703
* No-op for backwards compatibility. This should now be handled by
703704
* ShadyCss library.
704-
* @param {*} container Unused
705-
* @param {*} shouldObserve Unused
706-
* @return {void}
705+
* @param {!Element} container Container element to scope
706+
* @param {boolean=} shouldObserve if true, start a mutation observer for added nodes to the container
707+
* @return {?MutationObserver} Returns a new MutationObserver on `container` if `shouldObserve` is true.
707708
* @override
708709
*/
709-
scopeSubtree(container, shouldObserve) { // eslint-disable-line no-unused-vars
710+
scopeSubtree(container, shouldObserve = false) {
711+
return scopeSubtree(container, shouldObserve);
710712
}
711713

712714
/**

lib/utils/scope-subtree.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/**
2+
@license
3+
Copyright (c) 2019 The Polymer Project Authors. All rights reserved.
4+
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
5+
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
6+
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
7+
Code distributed by Google as part of the polymer project is also
8+
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
9+
*/
10+
11+
import './boot.js';
12+
13+
const ShadyDOM = window.ShadyDOM;
14+
const ShadyCSS = window.ShadyCSS;
15+
16+
/**
17+
* Ensure that elements in a ShadowDOM container are scoped correctly.
18+
* This function is only needed when ShadyDOM is used and unpatched DOM APIs are used in third party code.
19+
* This can happen in noPatch mode or when specialized APIs like ranges or tables are used to mutate DOM.
20+
*
21+
* @param {!Element} container Container element to scope
22+
* @param {boolean=} shouldObserve if true, start a mutation observer for added nodes to the container
23+
* @return {?MutationObserver} Returns a new MutationObserver on `container` if `shouldObserve` is true.
24+
*/
25+
export function scopeSubtree(container, shouldObserve = false) {
26+
// If using native ShadowDOM, abort
27+
if (!ShadyDOM || !ShadyCSS) {
28+
return null;
29+
}
30+
// ShadyCSS handles DOM mutations when ShadyDOM does not handle scoping itself
31+
if (!ShadyDOM['handlesDynamicScoping']) {
32+
return null;
33+
}
34+
const ScopingShim = ShadyCSS['ScopingShim'];
35+
// if ScopingShim is not available, abort
36+
if (!ScopingShim) {
37+
return null;
38+
}
39+
// capture correct scope for container
40+
const containerScope = ScopingShim['scopeForNode'](container);
41+
42+
const scopify = (node) => {
43+
// NOTE: native qSA does not honor scoped DOM, but it is faster, and the same behavior as Polymer v1
44+
const elements = Array.from(ShadyDOM['nativeMethods']['querySelectorAll'].call(node, '*'));
45+
elements.push(node);
46+
for (let i = 0; i < elements.length; i++) {
47+
const el = elements[i];
48+
const currentScope = ScopingShim['currentScopeForNode'](el);
49+
if (currentScope !== containerScope) {
50+
if (currentScope !== '') {
51+
ScopingShim['unscopeNode'](el, currentScope);
52+
}
53+
ScopingShim['scopeNode'](el, containerScope);
54+
}
55+
}
56+
};
57+
58+
// scope everything in container
59+
scopify(container);
60+
61+
if (shouldObserve) {
62+
const mo = new MutationObserver((mxns) => {
63+
for (let i = 0; i < mxns.length; i++) {
64+
const mxn = mxns[i];
65+
for (let j = 0; j < mxn.addedNodes.length; j++) {
66+
const addedNode = mxn.addedNodes[j];
67+
if (addedNode.nodeType === Node.ELEMENT_NODE) {
68+
scopify(addedNode);
69+
}
70+
}
71+
}
72+
});
73+
mo.observe(container, {childList: true, subtree: true});
74+
return mo;
75+
} else {
76+
return null;
77+
}
78+
}

test/runner.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@
8888
'unit/html-tag.html',
8989
'unit/legacy-data.html',
9090
// 'unit/multi-style.html'
91-
'unit/class-properties.html'
91+
'unit/class-properties.html',
92+
'unit/styling-scoped-nopatch.html'
9293
];
9394

9495
function combinations(suites, flags) {

test/unit/styling-scoped-nopatch.html

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
<!doctype html>
2+
<!--
3+
@license
4+
Copyright (c) 2019 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+
<meta charset="utf-8">
14+
<script>
15+
window.ShadyDOM = {force: true, noPatch: true};
16+
</script>
17+
<script src="../../node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>
18+
<script src="wct-browser-config.js"></script>
19+
<script src="../../node_modules/wct-browser-legacy/browser.js"></script>
20+
</head>
21+
<body>
22+
23+
<script type="module">
24+
import {Polymer, html} from '../../polymer-legacy.js';
25+
Polymer({
26+
is: 'scope-subtree-legacy',
27+
_template: html`
28+
<style>
29+
#container * {
30+
border: 10px solid black;
31+
}
32+
</style>
33+
<div id="container"></div>`
34+
});
35+
</script>
36+
37+
<script type="module">
38+
import {PolymerElement, html} from '../../polymer-element.js';
39+
class ScopeSubtreeElement extends PolymerElement {
40+
static get template() {
41+
return html`
42+
<style>
43+
#container * {
44+
border: 10px solid black;
45+
}
46+
</style>
47+
<div id="container"></div>`;
48+
}
49+
}
50+
customElements.define('scope-subtree-element', ScopeSubtreeElement);
51+
</script>
52+
53+
<test-fixture id="legacy">
54+
<template>
55+
<scope-subtree-legacy></scope-subtree-legacy>
56+
</template>
57+
</test-fixture>
58+
59+
<test-fixture id="element">
60+
<template>
61+
<scope-subtree-element></scope-subtree-element>
62+
</template>
63+
</test-fixture>
64+
65+
<script type="module">
66+
import { scopeSubtree } from '../../lib/utils/scope-subtree.js';
67+
68+
function assertComputed(element, value, property, pseudo) {
69+
var computed = getComputedStyle(element, pseudo);
70+
property = property || 'border-top-width';
71+
if (Array.isArray(value)) {
72+
assert.oneOf(computed[property], value, 'computed style incorrect for ' + property);
73+
} else {
74+
assert.equal(computed[property], value, 'computed style incorrect for ' + property);
75+
}
76+
}
77+
78+
suite('LegacyElement.scopeSubtree', function() {
79+
let el;
80+
setup(function() {
81+
el = fixture('legacy');
82+
});
83+
test('elements are scoped', function() {
84+
const div = document.createElement('div');
85+
const innerDiv = document.createElement('div');
86+
div.appendChild(innerDiv);
87+
el.$.container.appendChild(div);
88+
el.scopeSubtree(el.$.container);
89+
assertComputed(div, '10px');
90+
assertComputed(innerDiv, '10px');
91+
});
92+
test('second argument creates a mutation observer', async function() {
93+
const mo = el.scopeSubtree(el.$.container, true);
94+
assert.instanceOf(mo, MutationObserver);
95+
const div = document.createElement('div');
96+
const innerDiv = document.createElement('div');
97+
div.appendChild(innerDiv);
98+
el.$.container.appendChild(div);
99+
await new Promise((resolve) => setTimeout(resolve, 0));
100+
assertComputed(div, '10px');
101+
assertComputed(innerDiv, '10px');
102+
});
103+
});
104+
105+
suite('PolymerElement and scopeSubtree util', function() {
106+
let el;
107+
setup(function() {
108+
el = fixture('element');
109+
});
110+
test('elements are scoped', function() {
111+
const div = document.createElement('div');
112+
const innerDiv = document.createElement('div');
113+
div.appendChild(innerDiv);
114+
el.$.container.appendChild(div);
115+
scopeSubtree(el.$.container);
116+
assertComputed(div, '10px');
117+
assertComputed(innerDiv, '10px');
118+
});
119+
test('second argument creates a mutation observer', async function() {
120+
const mo = scopeSubtree(el.$.container, true);
121+
assert.instanceOf(mo, MutationObserver);
122+
const div = document.createElement('div');
123+
const innerDiv = document.createElement('div');
124+
div.appendChild(innerDiv);
125+
el.$.container.appendChild(div);
126+
await new Promise((resolve) => setTimeout(resolve, 0));
127+
assertComputed(div, '10px');
128+
assertComputed(innerDiv, '10px');
129+
});
130+
});
131+
</script>
132+
</body>
133+
</html>

0 commit comments

Comments
 (0)