Adds basic legacy support for ShadyDOM.unPatch (WIP)

* Polymer.dom uses ShadyDOM.wrap
* LegacyElementMixin _attachDOM uses ShadyDOM.wrap
This commit is contained in:
Steven Orvell
2018-10-26 15:13:56 -07:00
parent 4fcacaab09
commit e752636cb7
4 changed files with 506 additions and 33 deletions

View File

@@ -23,6 +23,8 @@ import { get } from '../utils/path.js';
let styleInterface = window.ShadyCSS;
let wrap = (n) => n;
/**
* Element class mixin that provides Polymer's "legacy" API intended to be
* backward-compatible to the greatest extent possible with the API
@@ -490,7 +492,7 @@ export const LegacyElementMixin = dedupingMixin((base) => {
* @this {Element}
*/
get domHost() {
let root = this.getRootNode();
let root = wrap(this).getRootNode();
return (root instanceof DocumentFragment) ? /** @type {ShadowRoot} */ (root).host : root;
}
@@ -636,8 +638,8 @@ export const LegacyElementMixin = dedupingMixin((base) => {
*/
isLightDescendant(node) {
const thisNode = /** @type {Node} */ (this);
return thisNode !== node && thisNode.contains(node) &&
thisNode.getRootNode() === node.getRootNode();
return thisNode !== node && wrap(thisNode).contains(node) &&
wrap(thisNode).getRootNode() === wrap(node).getRootNode();
}
/**
@@ -647,7 +649,7 @@ export const LegacyElementMixin = dedupingMixin((base) => {
* @return {boolean} true if node is in this element's local DOM tree.
*/
isLocalDescendant(node) {
return this.root === node.getRootNode();
return this.root === wrap(node).getRootNode();
}
/**
@@ -825,10 +827,10 @@ export const LegacyElementMixin = dedupingMixin((base) => {
bool = !node.hasAttribute(name);
}
if (bool) {
node.setAttribute(name, '');
wrap(node).setAttribute(name, '');
return true;
} else {
node.removeAttribute(name);
wrap(node).removeAttribute(name);
return false;
}
}
@@ -983,6 +985,22 @@ export const LegacyElementMixin = dedupingMixin((base) => {
}
if (window.ShadyDOM && window.ShadyDOM.inUse && window.ShadyDOM.noPatch) {
wrap = ShadyDOM.wrap;
LegacyElement.prototype._attachDom = function(dom) {
const w = this.__wrapper = ShadyDOM.wrap(this);
if (dom) {
if (!w.shadowRoot) {
w.attachShadow({mode: 'open'});
}
w.shadowRoot.appendChild(dom);
return w.shadowRoot;
}
return null;
};
}
LegacyElement.prototype.is = '';
return LegacyElement;

View File

@@ -41,7 +41,7 @@ export const matchesSelector = function(node, selector) {
* `target` is a `Node`.
*
*/
export class DomApi {
export let DomApi = class {
/**
* @param {Node} node Node for which to create a Polymer.dom helper object.
@@ -194,7 +194,7 @@ export class DomApi {
let node = this.node;
return node._activeElement !== undefined ? node._activeElement : node.activeElement;
}
}
};
function forwardMethods(proto, methods) {
for (let i=0; i < methods.length; i++) {
@@ -368,22 +368,103 @@ DomApi.prototype.textContent;
/** @type {string} */
DomApi.prototype.innerHTML;
forwardMethods(DomApi.prototype, [
'cloneNode', 'appendChild', 'insertBefore', 'removeChild',
'replaceChild', 'setAttribute', 'removeAttribute',
'querySelector', 'querySelectorAll'
]);
forwardReadOnlyProperties(DomApi.prototype, [
'parentNode', 'firstChild', 'lastChild',
'nextSibling', 'previousSibling', 'firstElementChild',
'lastElementChild', 'nextElementSibling', 'previousElementSibling',
'childNodes', 'children', 'classList'
]);
if (window.ShadyDOM && window.ShadyDOM.inUse && window.ShadyDOM.noPatch) {
class Wrapper extends ShadyDOM.Wrapper {
observeNodes(callback) {
return new FlattenedNodesObserver(
/** @type {!HTMLElement} */(this.node), callback, dom);
}
forwardProperties(DomApi.prototype, [
'textContent', 'innerHTML'
]);
unobserveNodes(observerHandle) {
observerHandle.disconnect();
}
notifyObserver() {}
deepContains(node) {
if (this.contains(node)) {
return true;
}
let n = node;
let doc = node.ownerDocument;
// walk from node to `this` or `document`
while (n && n !== doc && n !== this.node) {
// use logical parentnode, or native ShadowRoot host
n = this.parentNode || n.host;
}
return n === this.node;
}
getOwnerRoot() {
return this.getRootNode();
}
getDistributedNodes() {
return (this.node.localName === 'slot') ?
this.assignedNodes({flatten: true}) :
[];
}
getDestinationInsertionPoints() {
let ip$ = [];
let n = this.assignedSlot;
while (n) {
ip$.push(n);
n = ShadyDOM.wrap(n).assignedSlot;
}
return ip$;
}
importNode(node, deep) {
let doc = this.node instanceof Document ? this.node :
this.node.ownerDocument;
return ShadyDOM.wrap(doc).importNode(node, deep);
}
getEffectiveChildNodes() {
return FlattenedNodesObserver.getFlattenedNodes(
/** @type {!HTMLElement} */ (this), dom);
}
queryDistributedElements(selector) {
let c$ = this.getEffectiveChildNodes();
let list = [];
for (let i=0, l=c$.length, c; (i<l) && (c=c$[i]); i++) {
if ((c.nodeType === Node.ELEMENT_NODE) &&
matchesSelector(c, selector)) {
list.push(c);
}
}
return list;
}
get activeElement() {
let node = this.node;
return node._activeElement !== undefined ? node._activeElement : node.activeElement;
}
}
DomApi = Wrapper;
} else {
forwardMethods(DomApi.prototype, [
'cloneNode', 'appendChild', 'insertBefore', 'removeChild',
'replaceChild', 'setAttribute', 'removeAttribute',
'querySelector', 'querySelectorAll'
]);
forwardReadOnlyProperties(DomApi.prototype, [
'parentNode', 'firstChild', 'lastChild',
'nextSibling', 'previousSibling', 'firstElementChild',
'lastElementChild', 'nextElementSibling', 'previousElementSibling',
'childNodes', 'children', 'classList'
]);
forwardProperties(DomApi.prototype, [
'textContent', 'innerHTML'
]);
}
/**
* Legacy DOM and Event manipulation API wrapper factory used to abstract
@@ -400,6 +481,9 @@ forwardProperties(DomApi.prototype, [
* @return {!DomApi|!EventApi} Wrapper providing either node API or event API
*/
export const dom = function(obj) {
if (obj instanceof DomApi || obj instanceof EventApi) {
return obj;
}
obj = obj || document;
if (!obj.__domApi) {
let helper;

View File

@@ -63,7 +63,7 @@ function isSlot(node) {
* @summary Class that listens for changes (additions or removals) to
* "flattened nodes" on a given `node`.
*/
export class FlattenedNodesObserver {
export let FlattenedNodesObserver = class {
/**
* Returns the list of flattened nodes for the given `node`.
@@ -79,15 +79,17 @@ export class FlattenedNodesObserver {
* @return {!Array<!Node>} The list of flattened nodes for the given `node`.
* @nocollapse See https://github.com/google/closure-compiler/issues/2763
*/
static getFlattenedNodes(node) {
// eslint-disable-next-line
static getFlattenedNodes(node, wrapper = (n) => n) {
const wrapped = wrapper(node);
if (isSlot(node)) {
node = /** @type {!HTMLSlotElement} */(node); // eslint-disable-line no-self-assign
return node.assignedNodes({flatten: true});
return wrapped.assignedNodes({flatten: true});
} else {
return Array.from(node.childNodes).map((node) => {
return Array.from(wrapped.childNodes).map((node) => {
if (isSlot(node)) {
node = /** @type {!HTMLSlotElement} */(node); // eslint-disable-line no-self-assign
return node.assignedNodes({flatten: true});
return wrapper(node).assignedNodes({flatten: true});
} else {
return [node];
}
@@ -100,11 +102,13 @@ export class FlattenedNodesObserver {
* @param {?function(this: Element, { target: !HTMLElement, addedNodes: !Array<!Element>, removedNodes: !Array<!Element> }):void} callback Function called when there are additions
* or removals from the target's list of flattened nodes.
*/
constructor(target, callback) {
// eslint-disable-next-line
constructor(target, callback, wrapper = n=> n) {
/**
* @type {MutationObserver}
* @private
*/
this._wrap = wrapper;
this._shadyChildrenObserver = null;
/**
* @type {MutationObserver}
@@ -132,6 +136,10 @@ export class FlattenedNodesObserver {
this._schedule();
}
_wrap(node) {
return node;
}
/**
* Activates an observer. This method is automatically called when
* a `FlattenedNodesObserver` is created. It should only be called to
@@ -142,9 +150,9 @@ export class FlattenedNodesObserver {
connect() {
if (isSlot(this._target)) {
this._listenSlots([this._target]);
} else if (this._target.children) {
} else if (this._wrap(this._target).children) {
this._listenSlots(
/** @type {!NodeList<!Node>} */ (this._target.children));
/** @type {!NodeList<!Node>} */ (this._wrap(this._target).children));
if (window.ShadyDOM) {
this._shadyChildrenObserver =
ShadyDOM.observeChildren(this._target, (mutations) => {
@@ -172,9 +180,9 @@ export class FlattenedNodesObserver {
disconnect() {
if (isSlot(this._target)) {
this._unlistenSlots([this._target]);
} else if (this._target.children) {
} else if (this._wrap(this._target).children) {
this._unlistenSlots(
/** @type {!NodeList<!Node>} */ (this._target.children));
/** @type {!NodeList<!Node>} */ (this._wrap(this._target).children));
if (window.ShadyDOM && this._shadyChildrenObserver) {
ShadyDOM.unobserveChildren(this._shadyChildrenObserver);
this._shadyChildrenObserver = null;
@@ -305,4 +313,15 @@ export class FlattenedNodesObserver {
}
}
}
};
if (window.ShadyDOM && window.ShadyDOM.inUse && window.ShadyDOM.noPatch) {
FlattenedNodesObserver = class extends FlattenedNodesObserver {
static _wrap(node) {
return Polymer.dom(node);
}
};
}

View File

@@ -0,0 +1,352 @@
<!doctype html>
<!--
@license
Copyright (c) 2017 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
-->
<html>
<head>
<meta charset="utf-8">
<script>
ShadyDOM = {force: true, noPatch: true};
</script>
<script src="../../node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
<script src="wct-browser-config.js"></script>
<script src="../../node_modules/wct-browser-legacy/browser.js"></script>
<script type="module" src="../../polymer-legacy.js"></script>
</head>
<body>
<dom-module id="x-slot">
<template>
<div id="container"><slot id="slot"></slot></div>
</template>
<script type="module">
import { Polymer } from '../../polymer-legacy.js';
Polymer({
is: 'x-slot'
});
</script>
</dom-module>
<dom-module id="x-container-slot">
<template>
<x-slot id="container"><div id="first">first</div><slot id="slot"></slot><div id="last">last</div></x-slot>
</template>
<script type="module">
import { Polymer } from '../../polymer-legacy.js';
Polymer({
is: 'x-container-slot'
});
</script>
</dom-module>
<dom-module id="x-event-scoped">
<template>
<div id="scoped"></div>
</template>
<script type="module">
import { Polymer } from '../../polymer-legacy.js';
Polymer({
is: 'x-event-scoped',
fireComposed: function() {
return this.fire('composed', null, {node: this.$.scoped});
}
});
</script>
</dom-module>
<dom-module id="x-focusable-in-shadow">
<template>
<input id="focusable"></input>
</template>
<script type="module">
import { Polymer } from '../../polymer-legacy.js';
Polymer({
is: 'x-focusable-in-shadow'
});
</script>
</dom-module>
<test-fixture id="scoped">
<template>
<x-event-scoped></x-event-scoped>
</template>
</test-fixture>
<test-fixture id="slot">
<template>
<x-container-slot></x-container-slot>
</template>
</test-fixture>
<test-fixture id="focusableInShadow">
<template>
<x-focusable-in-shadow></x-focusable-in-shadow>
</template>
</test-fixture>
<script type="module">
import { dom } from '../../lib/legacy/polymer.dom.js';
import { useShadow, useNativeCustomElements, useNativeCSSProperties } from '../../lib/utils/settings.js';
suite('extended dom api', function() {
test('getEffectiveChildNodes', function() {
var el = fixture('slot');
var div = document.createElement('div');
dom(el).appendChild(div);
if (window.ShadyDOM) {
ShadyDOM.flush();
}
assert.deepEqual(dom(el).getEffectiveChildNodes(),
[div]);
assert.deepEqual(dom(el.$.container).getEffectiveChildNodes(),
[el.$.first, div, el.$.last]);
assert.deepEqual(dom(el.$.container.$.container).getEffectiveChildNodes(),
[el.$.first, div, el.$.last]);
});
test('queryDistributedElements', function() {
var el = fixture('slot');
var div = document.createElement('div');
dom(el).appendChild(div);
if (window.ShadyDOM) {
ShadyDOM.flush();
}
assert.deepEqual(dom(el).queryDistributedElements('foo'),
[]);
assert.deepEqual(dom(el).queryDistributedElements('div'),
[div]);
assert.deepEqual(dom(el.$.container).queryDistributedElements('#first'),
[el.$.first]);
assert.deepEqual(dom(el.$.container.$.container).queryDistributedElements('#last'),
[el.$.last]);
});
});
suite('distribution', function() {
test('getDistributedNodes', function() {
var el = fixture('slot');
var div = document.createElement('div');
dom(el).appendChild(div);
if (window.ShadyDOM) {
ShadyDOM.flush();
}
assert.deepEqual(dom(el.$.slot).getDistributedNodes(), [div]);
assert.deepEqual(dom(el.$.container.$.slot).getDistributedNodes(),
[el.$.first, div, el.$.last]);
});
test('getDestinationInsertionPoints', function() {
var el = fixture('slot');
var div = document.createElement('div');
dom(el).appendChild(div);
if (window.ShadyDOM) {
ShadyDOM.flush();
}
assert.deepEqual(dom(el.$.first).getDestinationInsertionPoints(),
[el.$.container.$.slot]);
assert.deepEqual(dom(div).getDestinationInsertionPoints(),
[el.$.slot, el.$.container.$.slot]);
});
});
// suite('events', function() {
// test('localTarget, rootTarget, path', function(done) {
// var el = fixture('scoped');
// el.addEventListener('composed', function(e) {
// assert.equal(dom(e).rootTarget, el.$.scoped);
// assert.equal(dom(e).localTarget, el);
// let nodes = [];
// let p = el.$.scoped;
// while (p) {
// nodes.push(p);
// p = p.parentNode || p.host;
// }
// nodes.push(window);
// assert.deepEqual(Array.from(dom(e).path), nodes);
// done();
// });
// el.fireComposed();
// });
// });
// suite('activeElement getter', function() {
// test('Retrieves `_activeElement` (ShadyDOM) or `activeElement`.', function() {
// var focusableInShadow = fixture('focusableInShadow');
// focusableInShadow.$.focusable.focus();
// var rootNode = focusableInShadow.getRootNode();
// assert.equal(dom(rootNode).activeElement, focusableInShadow);
// assert.equal(dom(focusableInShadow.shadowRoot).activeElement, focusableInShadow.$.focusable);
// });
// });
suite('legacy api', function() {
test('getEffectiveChildNodes', function() {
var el = fixture('slot');
var div = document.createElement('div');
var t = document.createTextNode('yo');
dom(el).appendChild(div);
dom(el).appendChild(t);
if (window.ShadyDOM) {
ShadyDOM.flush();
}
assert.deepEqual(el.getEffectiveChildNodes(),
[div, t]);
assert.deepEqual(el.$.container.getEffectiveChildNodes(),
[el.$.first, div, t, el.$.last]);
});
test('getEffectiveChildren', function() {
var el = fixture('slot');
var div = document.createElement('div');
dom(el).appendChild(div);
if (window.ShadyDOM) {
ShadyDOM.flush();
}
assert.deepEqual(el.getEffectiveChildNodes(),
[div]);
assert.deepEqual(el.$.container.getEffectiveChildNodes(),
[el.$.first, div, el.$.last]);
});
test('getEffectiveTextContent', function() {
var el = fixture('slot');
var t1 = document.createTextNode('a');
var t2 = document.createTextNode('b');
dom(el).appendChild(t1);
dom(el).appendChild(t2);
if (window.ShadyDOM) {
ShadyDOM.flush();
}
assert.deepEqual(el.getEffectiveTextContent(), 'ab');
assert.deepEqual(el.$.container.getEffectiveTextContent(), 'firstablast');
});
test('getContentChildNodes', function() {
var el = fixture('slot');
var div1 = document.createElement('div');
var t = document.createTextNode('');
var div2 = document.createElement('div');
dom(el).appendChild(div1);
dom(el).appendChild(t);
dom(el).appendChild(div2);
if (window.ShadyDOM) {
ShadyDOM.flush();
}
assert.deepEqual(el.getContentChildNodes(),
[div1, t, div2]);
assert.deepEqual(el.getContentChildNodes('slot'),
[div1, t, div2]);
});
test('getContentChildren', function() {
var el = fixture('slot');
var div1 = document.createElement('div');
var t = document.createTextNode('');
var div2 = document.createElement('div');
dom(el).appendChild(div1);
dom(el).appendChild(t);
dom(el).appendChild(div2);
if (window.ShadyDOM) {
ShadyDOM.flush();
}
assert.deepEqual(el.getContentChildren(),
[div1, div2]);
assert.deepEqual(el.getContentChildren('slot'),
[div1, div2]);
});
test('queryDistributedElements', function() {
var el = fixture('slot');
var div = document.createElement('div');
dom(el).appendChild(div);
if (window.ShadyDOM) {
ShadyDOM.flush();
}
assert.deepEqual(el.queryDistributedElements('foo'),
[]);
assert.deepEqual(el.queryDistributedElements('div'),
[div]);
assert.deepEqual(el.$.container.queryDistributedElements('#first'),
[el.$.first]);
});
test('queryEffectiveChildren', function() {
var el = fixture('slot');
var div = document.createElement('div');
dom(el).appendChild(div);
if (window.ShadyDOM) {
ShadyDOM.flush();
}
assert.equal(el.queryEffectiveChildren('foo'),
null);
assert.deepEqual(el.queryEffectiveChildren('div'),
div);
assert.deepEqual(el.$.container.queryEffectiveChildren('#first'),
el.$.first);
});
test('queryAllEffectiveChildren', function() {
var el = fixture('slot');
var div1 = document.createElement('div');
var div2 = document.createElement('div');
dom(el).appendChild(div1);
dom(el).appendChild(div2);
if (window.ShadyDOM) {
ShadyDOM.flush();
}
assert.deepEqual(el.queryAllEffectiveChildren('foo'),
[]);
assert.deepEqual(el.queryAllEffectiveChildren('div'),
[div1, div2]);
assert.deepEqual(el.$.container.queryAllEffectiveChildren('div'),
[el.$.first, div1, div2, el.$.last]);
});
test('isLightDescendant', function() {
var el = fixture('slot');
var div1 = document.createElement('div');
dom(el).appendChild(div1);
if (window.ShadyDOM) {
ShadyDOM.flush();
}
assert.equal(el.isLightDescendant(div1), true);
assert.equal(el.isLightDescendant(el.$.container), false);
});
test('isLocalDescendant', function() {
var el = fixture('slot');
var div1 = document.createElement('div');
dom(el).appendChild(div1);
if (window.ShadyDOM) {
ShadyDOM.flush();
}
assert.equal(el.isLocalDescendant(div1), false);
assert.equal(el.isLocalDescendant(el.$.container), true);
});
test('domHost', function() {
var el = fixture('slot');
assert.equal(el.domHost, document);
assert.equal(el.$.container.domHost, el);
});
test('legacy settings', function() {
assert.equal(useShadow, !(window.ShadyDOM));
assert.equal(useNativeCustomElements, !(window.customElements.polyfillWrapFlushCallback));
assert.equal(useNativeCSSProperties, Boolean(!window.ShadyCSS || window.ShadyCSS.nativeCss));
});
});
</script>
</body>
</html>