diff --git a/lib/legacy/legacy-element-mixin.js b/lib/legacy/legacy-element-mixin.js index 861fefb4c0..213c3078b4 100644 --- a/lib/legacy/legacy-element-mixin.js +++ b/lib/legacy/legacy-element-mixin.js @@ -20,6 +20,7 @@ import { Debouncer } from '../utils/debounce.js'; import { timeOut, microTask } from '../utils/async.js'; import { get } from '../utils/path.js'; import { wrap } from '../utils/wrap.js'; +import { scopeSubtree } from '../utils/scope-subtree.js'; let styleInterface = window.ShadyCSS; @@ -701,12 +702,13 @@ export const LegacyElementMixin = dedupingMixin((base) => { /** * No-op for backwards compatibility. This should now be handled by * ShadyCss library. - * @param {*} container Unused - * @param {*} shouldObserve Unused - * @return {void} + * @param {!Element} container Container element to scope + * @param {boolean=} shouldObserve if true, start a mutation observer for added nodes to the container + * @return {?MutationObserver} Returns a new MutationObserver on `container` if `shouldObserve` is true. * @override */ - scopeSubtree(container, shouldObserve) { // eslint-disable-line no-unused-vars + scopeSubtree(container, shouldObserve = false) { + return scopeSubtree(container, shouldObserve); } /** diff --git a/lib/utils/scope-subtree.js b/lib/utils/scope-subtree.js new file mode 100644 index 0000000000..7932d27f81 --- /dev/null +++ b/lib/utils/scope-subtree.js @@ -0,0 +1,78 @@ +/** +@license +Copyright (c) 2019 The Polymer Project Authors. All rights reserved. +This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt +The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt +The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt +Code distributed by Google as part of the polymer project is also +subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt +*/ + +import './boot.js'; + +const ShadyDOM = window.ShadyDOM; +const ShadyCSS = window.ShadyCSS; + +/** + * Ensure that elements in a ShadowDOM container are scoped correctly. + * This function is only needed when ShadyDOM is used and unpatched DOM APIs are used in third party code. + * This can happen in noPatch mode or when specialized APIs like ranges or tables are used to mutate DOM. + * + * @param {!Element} container Container element to scope + * @param {boolean=} shouldObserve if true, start a mutation observer for added nodes to the container + * @return {?MutationObserver} Returns a new MutationObserver on `container` if `shouldObserve` is true. + */ +export function scopeSubtree(container, shouldObserve = false) { + // If using native ShadowDOM, abort + if (!ShadyDOM || !ShadyCSS) { + return null; + } + // ShadyCSS handles DOM mutations when ShadyDOM does not handle scoping itself + if (!ShadyDOM['handlesDynamicScoping']) { + return null; + } + const ScopingShim = ShadyCSS['ScopingShim']; + // if ScopingShim is not available, abort + if (!ScopingShim) { + return null; + } + // capture correct scope for container + const containerScope = ScopingShim['scopeForNode'](container); + + const scopify = (node) => { + // NOTE: native qSA does not honor scoped DOM, but it is faster, and the same behavior as Polymer v1 + const elements = Array.from(ShadyDOM['nativeMethods']['querySelectorAll'].call(node, '*')); + elements.push(node); + for (let i = 0; i < elements.length; i++) { + const el = elements[i]; + const currentScope = ScopingShim['currentScopeForNode'](el); + if (currentScope !== containerScope) { + if (currentScope !== '') { + ScopingShim['unscopeNode'](el, currentScope); + } + ScopingShim['scopeNode'](el, containerScope); + } + } + }; + + // scope everything in container + scopify(container); + + if (shouldObserve) { + const mo = new MutationObserver((mxns) => { + for (let i = 0; i < mxns.length; i++) { + const mxn = mxns[i]; + for (let j = 0; j < mxn.addedNodes.length; j++) { + const addedNode = mxn.addedNodes[j]; + if (addedNode.nodeType === Node.ELEMENT_NODE) { + scopify(addedNode); + } + } + } + }); + mo.observe(container, {childList: true, subtree: true}); + return mo; + } else { + return null; + } +} \ No newline at end of file diff --git a/test/runner.html b/test/runner.html index 338dd11253..e174cab993 100644 --- a/test/runner.html +++ b/test/runner.html @@ -88,7 +88,8 @@ 'unit/html-tag.html', 'unit/legacy-data.html', // 'unit/multi-style.html' - 'unit/class-properties.html' + 'unit/class-properties.html', + 'unit/styling-scoped-nopatch.html' ]; function combinations(suites, flags) { diff --git a/test/unit/styling-scoped-nopatch.html b/test/unit/styling-scoped-nopatch.html new file mode 100644 index 0000000000..b1f82b1fea --- /dev/null +++ b/test/unit/styling-scoped-nopatch.html @@ -0,0 +1,133 @@ + + + +
+ + + + + + + + + + + + +